Completed
Push — master ( 7d2025...325785 )
by Nikola
01:46
created

RequestsPerWindowRateLimiter::getCurrent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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;
14
15
use Psr\Http\Message\RequestInterface;
16
use Psr\Http\Message\ResponseInterface;
17
use RateLimit\Storage\StorageInterface;
18
use RateLimit\Identity\IdentityResolverInterface;
19
use RateLimit\Options\RequestsPerWindowOptions;
20
use RateLimit\Exception\StorageValueNotFoundException;
21
22
/**
23
 * @author Nikola Posa <[email protected]>
24
 */
25
final class RequestsPerWindowRateLimiter extends AbstractRateLimiter
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 RequestsPerWindowOptions
35
     */
36
    private $options;
37
38
    /**
39
     * @var string
40
     */
41
    private $identity;
42
43 10
    public function __construct(StorageInterface $storage, IdentityResolverInterface $identityResolver, RequestsPerWindowOptions $options)
44
    {
45 10
        parent::__construct($storage, $identityResolver);
46
        
47 10
        $this->options = $options;
48 10
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 7
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $out = null)
54
    {
55 7
        if ($this->whitelist($request)) {
56 1
            return $this->next($request, $response, $out);
57
        }
58
59 6
        $this->resolveIdentity($request);
60
61
        try {
62 6
            $current = $this->getCurrent();
63
64 4
            if ($this->isLimitExceeded($current)) {
65 3
                return $this->onLimitExceeded($request, $response);
66
            }
67
68 1
            $this->hit();
69 6
        } catch (StorageValueNotFoundException $ex) {
70 6
            $this->initialHit();
71
        }
72
73 6
        return $this->onBelowLimit($request, $response, $out);
74
    }
75
76 7
    private function whitelist(RequestInterface $request)
77
    {
78 7
        $whitelist = $this->options->getWhitelist();
79
80 7
        return $whitelist($request);
81
    }
82
83 6
    private function resolveIdentity(RequestInterface $request)
84
    {
85 6
        $this->identity = $this->identityResolver->getIdentity($request);
86 6
    }
87
88 6
    private function getCurrent() : int
89
    {
90 6
        return $this->storage->get($this->identity);
91
    }
92
93 4
    private function isLimitExceeded($current) : bool
94
    {
95 4
        return ($current >= $this->options->getLimit());
96
    }
97
98 6
    private function initialHit()
99
    {
100 6
        $this->storage->set($this->identity, 1, $this->options->getWindow());
101 6
    }
102
103 1
    private function hit()
104
    {
105 1
        $this->storage->increment($this->identity, 1);
106 1
    }
107
108 3
    private function onLimitExceeded(RequestInterface $request, ResponseInterface $response) : ResponseInterface
109
    {
110
        $response = $this
111 3
            ->setRateLimitHeaders($response)
112 3
            ->withStatus(self::LIMIT_EXCEEDED_HTTP_STATUS_CODE)
113
        ;
114
115 3
        $limitExceededHandler = $this->options->getLimitExceededHandler();
116 3
        $response = $limitExceededHandler($request, $response);
117
118 3
        return $response;
119
    }
120
121 6
    private function onBelowLimit(RequestInterface $request, ResponseInterface $response, callable $out = null) : ResponseInterface
122
    {
123 6
        $response = $this->setRateLimitHeaders($response);
124
125 6
        return $this->next($request, $response, $out);
126
    }
127
128 7
    private function next(RequestInterface $request, ResponseInterface $response, callable $out = null)
129
    {
130 7
        return $out ? $out($request, $response) : $response;
131
    }
132
133 6
    private function setRateLimitHeaders(ResponseInterface $response) : ResponseInterface
134
    {
135
        return $response
136 6
            ->withHeader(self::HEADER_LIMIT, (string) $this->options->getLimit())
137 6
            ->withHeader(self::HEADER_REMAINING, (string) ($this->options->getLimit() - $this->getCurrent()))
138 6
            ->withHeader(self::HEADER_RESET, (string) (time() + $this->storage->ttl($this->identity)))
139
        ;
140
    }
141
}
142