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

RequestsPerWindowRateLimiter   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 101
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 13
lcom 1
cbo 5
dl 0
loc 101
ccs 39
cts 39
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A __invoke() 0 18 3
A resolveIdentity() 0 4 1
A getCurrent() 0 4 1
A isLimitExceeded() 0 4 1
A initialHit() 0 4 1
A hit() 0 4 1
A onLimitExceeded() 0 12 1
A onBelowLimit() 0 6 2
A setRateLimitHeaders() 0 8 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