Completed
Push — master ( b39d79...4a8824 )
by Nikola
01:54
created

RequestsPerWindowRateLimiter::resolveIdentity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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;
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 9
    public function __construct(StorageInterface $storage, IdentityResolverInterface $identityResolver, RequestsPerWindowOptions $options)
44
    {
45 9
        parent::__construct($storage, $identityResolver);
46
        
47 9
        $this->options = $options;
48 9
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53 6
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $out = null)
54
    {
55 6
        $this->resolveIdentity($request);
56
57
        try {
58 6
            $current = $this->getCurrent();
59
60 4
            if ($this->isLimitExceeded($current)) {
61 3
                return $this->onLimitExceeded($request, $response);
62
            }
63
64 1
            $this->hit();
65 6
        } catch (StorageValueNotFoundException $ex) {
66 6
            $this->initialHit();
67
        }
68
69 6
        return $this->onBelowLimit($request, $response, $out);
70
    }
71
72 6
    private function resolveIdentity(RequestInterface $request)
73
    {
74 6
        $this->identity = $this->identityResolver->getIdentity($request);
75 6
    }
76
77 6
    private function getCurrent() : int
78
    {
79 6
        return $this->storage->get($this->identity);
80
    }
81
82 4
    private function isLimitExceeded($current) : bool
83
    {
84 4
        return ($current >= $this->options->getLimit());
85
    }
86
87 6
    private function initialHit()
88
    {
89 6
        $this->storage->set($this->identity, 1, $this->options->getWindow());
90 6
    }
91
92 1
    private function hit()
93
    {
94 1
        $this->storage->increment($this->identity, 1);
95 1
    }
96
97 3
    private function onLimitExceeded(RequestInterface $request, ResponseInterface $response) : ResponseInterface
98
    {
99
        $response = $this
100 3
            ->setRateLimitHeaders($response)
101 3
            ->withStatus(self::LIMIT_EXCEEDED_HTTP_STATUS_CODE)
102
        ;
103
104 3
        $limitExceededHandler = $this->options->getLimitExceededHandler();
105 3
        $response = $limitExceededHandler($request, $response);
106
107 3
        return $response;
108
    }
109
110 6
    private function onBelowLimit(RequestInterface $request, ResponseInterface $response, callable $out = null) : ResponseInterface
111
    {
112 6
        $response = $this->setRateLimitHeaders($response);
113
114 6
        return $out ? $out($request, $response) : $response;
115
    }
116
117 6
    private function setRateLimitHeaders(ResponseInterface $response) : ResponseInterface
118
    {
119
        return $response
120 6
            ->withHeader(self::HEADER_LIMIT, (string) $this->options->getLimit())
121 6
            ->withHeader(self::HEADER_REMAINING, (string) ($this->options->getLimit() - $this->getCurrent()))
122 6
            ->withHeader(self::HEADER_RESET, (string) (time() + $this->storage->ttl($this->identity)))
123
        ;
124
    }
125
}
126