Completed
Push — master ( f43e52...716ed5 )
by Nikola
10s
created

RequestsPerWindowRateLimiter::hit()   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 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\IdentityGeneratorInterface;
19
use RateLimit\Options\RequestsPerWindowOptions;
20
21
/**
22
 * @author Nikola Posa <[email protected]>
23
 */
24
final class RequestsPerWindowRateLimiter extends AbstractRateLimiter
25
{
26
    const LIMIT_EXCEEDED_HTTP_STATUS_CODE = 429; //HTTP 429 "Too Many Requests" (RFC 6585)
27
28
    const HEADER_LIMIT = 'X-RateLimit-Limit';
29
    const HEADER_REMAINING = 'X-RateLimit-Remaining';
30
    const HEADER_RESET = 'X-RateLimit-Reset';
31
32
    /**
33
     * @var RequestsPerWindowOptions
34
     */
35
    private $options;
36
37
    /**
38
     * @var string
39
     */
40
    private $identity;
41
42 6
    public function __construct(StorageInterface $storage, IdentityGeneratorInterface $identityGenerator, RequestsPerWindowOptions $options)
43
    {
44 6
        parent::__construct($storage, $identityGenerator);
45
        
46 6
        $this->options = $options;
47 6
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 6
    public function __invoke(RequestInterface $request, ResponseInterface $response, callable $out = null)
53
    {
54 6
        $this->resolveIdentity($request);
55
56 6
        if ($this->isLimitExceeded()) {
57 3
            return $this->onLimitExceeded($request, $response);
58
        }
59
60 6
        $this->hit();
61
62 6
        return $this->onBelowLimit($request, $response, $out);
63
    }
64
65 6
    private function resolveIdentity(RequestInterface $request)
66
    {
67 6
        $this->identity = $this->identityGenerator->getIdentity($request);
68 6
    }
69
70 6
    private function getCurrent() : int
71
    {
72 6
        $current = $this->storage->get($this->identity, false);
73
74 6
        if (false === $current) {
75 6
            $current = 0;
76
77 6
            $this->storage->set($this->identity, $current, $this->options->getWindow());
78
        }
79
80 6
        return $current;
81
    }
82
83 6
    private function isLimitExceeded() : bool
84
    {
85 6
        $current = $this->getCurrent();
86 6
        return ($current >= $this->options->getLimit());
87
    }
88
89 6
    private function hit()
90
    {
91 6
        $this->storage->increment($this->identity, 1);
92 6
    }
93
94 3
    private function onLimitExceeded(RequestInterface $request, ResponseInterface $response) : ResponseInterface
95
    {
96
        $response = $this
97 3
            ->setRateLimitHeaders($response)
98 3
            ->withStatus(self::LIMIT_EXCEEDED_HTTP_STATUS_CODE)
99
        ;
100
101 3
        $limitExceededHandler = $this->options->getLimitExceededHandler();
102 3
        $response = $limitExceededHandler($request, $response);
103
104 3
        return $response;
105
    }
106
107 6
    private function onBelowLimit(RequestInterface $request, ResponseInterface $response, callable $out = null) : ResponseInterface
108
    {
109 6
        $response = $this->setRateLimitHeaders($response);
110
111 6
        return $out ? $out($request, $response) : $response;
112
    }
113
114 6
    private function setRateLimitHeaders(ResponseInterface $response) : ResponseInterface
115
    {
116
        return $response
117 6
            ->withHeader(self::HEADER_LIMIT, (string) $this->options->getLimit())
118 6
            ->withHeader(self::HEADER_REMAINING, (string) ($this->options->getLimit() - $this->getCurrent()))
119 6
            ->withHeader(self::HEADER_RESET, (string) (time() + $this->storage->ttl($this->identity)))
120
        ;
121
    }
122
}
123