Completed
Push — master ( aa9401...ff4505 )
by Welling
01:49
created

RateLimitMiddleware::process()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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