Completed
Pull Request — master (#12)
by
unknown
12:17 queued 02:05
created

RateLimitMiddleware::next()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 6
1
<?php
2
/**
3
 * This file is part of the Rate Limit package.
4
 *
5
 * Copyright (c) Nikola Posa
6
 *
7
 * For full copyright and license information, please refer to the LICENSE file,
8
 * located at the package root folder.
9
 */
10
11
declare(strict_types=1);
12
13
namespace RateLimit\Middleware;
14
15
use RateLimit\Exception\RateLimitExceededException;
16
use RateLimit\RateLimiterInterface;
17
use RateLimit\Middleware\Identity\IdentityResolverInterface;
18
use RateLimit\Middleware\Identity\IpAddressIdentityResolver;
19
use Psr\Http\Message\RequestInterface;
20
use Psr\Http\Message\ResponseInterface;
21
22
/**
23
 * @author Nikola Posa <[email protected]>
24
 */
25
final class RateLimitMiddleware
26
{
27
    const LIMIT_EXCEEDED_HTTP_STATUS_CODE = 429; //HTTP 429 "Too Many Requests" (RFC 6585)
28
29
    const HEADER_LIMIT = 'X-RateLimit-Limit';
30
    const HEADER_REMAINING = 'X-RateLimit-Remaining';
31
    const HEADER_RESET = 'X-RateLimit-Reset';
32
33
    /**
34
     * @var RateLimiterInterface
35
     */
36
    private $rateLimiter;
37
38
    /**
39
     * @var IdentityResolverInterface
40
     */
41
    private $identityResolver;
42
43
    /**
44
     * @var Options
45
     */
46
    private $options;
47
48
    /**
49
     * @var string
50
     */
51
    private $identity;
52
53
    public function __construct(RateLimiterInterface $rateLimiter, IdentityResolverInterface $identityResolver, Options $options)
54
    {
55
        $this->rateLimiter = $rateLimiter;
56
        $this->identityResolver = $identityResolver;
57
        $this->options = $options;
58
    }
59
60
    public static function createDefault(RateLimiterInterface $rateLimiter, array $options = [])
61
    {
62
        return new self(
63
            $rateLimiter,
64
            new IpAddressIdentityResolver(),
65
            Options::fromArray($options)
66
        );
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $out = null)
73
    {
74
        if ($this->isWhitelisted($request)) {
75
            return $this->next($request, $response, $out);
76
        }
77
78
        $this->identity = $this->resolveIdentity($request);
79
80
        try {
81
            $this->rateLimiter->hit($this->identity);
82
83
            return $this->onBelowLimit($request, $response, $out);
84
        } catch (RateLimitExceededException $ex) {
85
            return $this->onLimitExceeded($request, $response);
86
        }
87
    }
88
89
    private function isWhitelisted(RequestInterface $request) : bool
90
    {
91
        $whitelist = $this->options->getWhitelist();
92
93
        return $whitelist($request);
94
    }
95
96
    private function resolveIdentity(RequestInterface $request) : string
97
    {
98
        return $this->identityResolver->getIdentity($request);
99
    }
100
101
    private function onLimitExceeded(RequestInterface $request, ResponseInterface $response) : ResponseInterface
102
    {
103
        $response = $this
104
            ->setRateLimitHeaders($response)
105
            ->withStatus(self::LIMIT_EXCEEDED_HTTP_STATUS_CODE)
106
        ;
107
108
        $limitExceededHandler = $this->options->getLimitExceededHandler();
109
        $response = $limitExceededHandler($request, $response);
110
111
        return $response;
112
    }
113
114
    private function onBelowLimit(RequestInterface $request, ResponseInterface $response, callable $out = null) : ResponseInterface
115
    {
116
        $response = $this->setRateLimitHeaders($response);
117
118
        return $this->next($request, $response, $out);
119
    }
120
121
    private function next(RequestInterface $request, ResponseInterface $response, callable $out = null)
122
    {
123
        return $out ? $out($request, $response) : $response;
124
    }
125
126
    private function setRateLimitHeaders(ResponseInterface $response) : ResponseInterface
127
    {
128
        return $response
129
            ->withHeader(self::HEADER_LIMIT, (string) $this->rateLimiter->getLimit())
130
            ->withHeader(self::HEADER_REMAINING, (string) $this->rateLimiter->getRemainingAttempts($this->identity))
131
            ->withHeader(self::HEADER_RESET, (string) $this->rateLimiter->getResetAt($this->identity))
132
        ;
133
    }
134
}
135