Completed
Push — master ( 0faaef...a96626 )
by Nikola
02:09
created

RateLimitMiddleware::setRateLimitHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
crap 1
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 7
    public function __construct(RateLimiterInterface $rateLimiter, IdentityResolverInterface $identityResolver, Options $options)
54
    {
55 7
        $this->rateLimiter = $rateLimiter;
56 7
        $this->identityResolver = $identityResolver;
57 7
        $this->options = $options;
58 7
    }
59
60 7
    public static function createDefault(RateLimiterInterface $rateLimiter, array $options = [])
61
    {
62 7
        return new self(
63
            $rateLimiter,
64 7
            new IpAddressIdentityResolver(),
65 7
            Options::fromArray($options)
66
        );
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 7
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $out = null)
73
    {
74 7
        if ($this->isWhitelisted($request)) {
75 1
            return $this->next($request, $response, $out);
76
        }
77
78 6
        $this->identity = $this->resolveIdentity($request);
79
80
        try {
81 6
            $this->rateLimiter->hit($this->identity);
82
83 6
            return $this->onBelowLimit($request, $response, $out);
84 3
        } catch (RateLimitExceededException $ex) {
85 3
            return $this->onLimitExceeded($request, $response);
86
        }
87
    }
88
89 7
    private function isWhitelisted(RequestInterface $request) : bool
90
    {
91 7
        $whitelist = $this->options->getWhitelist();
92
93 7
        return $whitelist($request);
94
    }
95
96 6
    private function resolveIdentity(RequestInterface $request) : string
97
    {
98 6
        return $this->identityResolver->getIdentity($request);
99
    }
100
101 3
    private function onLimitExceeded(RequestInterface $request, ResponseInterface $response) : ResponseInterface
102
    {
103
        $response = $this
104 3
            ->setRateLimitHeaders($response)
105 3
            ->withStatus(self::LIMIT_EXCEEDED_HTTP_STATUS_CODE)
106
        ;
107
108 3
        $limitExceededHandler = $this->options->getLimitExceededHandler();
109 3
        $response = $limitExceededHandler($request, $response);
110
111 3
        return $response;
112
    }
113
114 6
    private function onBelowLimit(RequestInterface $request, ResponseInterface $response, callable $out = null) : ResponseInterface
115
    {
116 6
        $response = $this->setRateLimitHeaders($response);
117
118 6
        return $this->next($request, $response, $out);
119
    }
120
121 7
    private function next(RequestInterface $request, ResponseInterface $response, callable $out = null)
122
    {
123 7
        return $out ? $out($request, $response) : $response;
124
    }
125
126 6
    private function setRateLimitHeaders(ResponseInterface $response) : ResponseInterface
127
    {
128
        return $response
129 6
            ->withHeader(self::HEADER_LIMIT, (string) $this->rateLimiter->getLimit())
130 6
            ->withHeader(self::HEADER_REMAINING, (string) $this->rateLimiter->getRemainingAttempts($this->identity))
131 6
            ->withHeader(self::HEADER_RESET, (string) $this->rateLimiter->getResetAt($this->identity))
132
        ;
133
    }
134
}
135