ThrottleRequests::resolveRequestSignature()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
rs 9.6666
cc 1
eloc 6
nc 1
nop 1
1
<?php
2
3
namespace Nord\Lumen\Core\Middleware;
4
5
use Closure;
6
use Illuminate\Cache\RateLimiter;
7
use Illuminate\Http\Request;
8
use Nord\Lumen\Core\Traits\CreatesHttpResponses;
9
use Symfony\Component\HttpFoundation\Response;
10
11
class ThrottleRequests
12
{
13
    use CreatesHttpResponses;
14
15
    /**
16
     * The rate limiter instance.
17
     *
18
     * @var RateLimiter
19
     */
20
    private $rateLimiter;
21
22
    /**
23
     * Create a new request throttler.
24
     *
25
     * @param RateLimiter $rateLimiter
26
     */
27
    public function __construct(RateLimiter $rateLimiter)
28
    {
29
        $this->rateLimiter = $rateLimiter;
30
    }
31
32
    /**
33
     * Handle an incoming request.
34
     *
35
     * @param Request $request
36
     * @param Closure $next
37
     * @param int     $maxAttempts
38
     * @param int     $decayMinutes
39
     *
40
     * @return mixed
41
     */
42
    public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1)
43
    {
44
        $key = $this->resolveRequestSignature($request);
45
46
        if ($this->rateLimiter->tooManyAttempts($key, $maxAttempts, $decayMinutes)) {
47
            return $this->buildResponse($key, $maxAttempts);
48
        }
49
50
        $this->rateLimiter->hit($key, $decayMinutes);
51
52
        $response = $next($request);
53
54
        return $this->addHeaders(
55
            $response, $maxAttempts,
56
            $this->calculateRemainingAttempts($key, $maxAttempts)
57
        );
58
    }
59
60
    /**
61
     * Resolve request signature.
62
     *
63
     * @param Request $request
64
     *
65
     * @return string
66
     */
67
    protected function resolveRequestSignature($request)
68
    {
69
        return sha1(
70
            $request->method() .
71
            '|' . $request->server('SERVER_NAME') .
72
            '|' . $request->path() .
73
            '|' . $request->ip()
74
        );
75
    }
76
77
    /**
78
     * Create a 'too many attempts' response.
79
     *
80
     * @param string $key
81
     * @param int    $maxAttempts
82
     *
83
     * @return Response
84
     */
85
    protected function buildResponse($key, $maxAttempts)
86
    {
87
        $response = $this->errorResponse('Too Many Requests', [], 429);
88
89
        $retryAfter = $this->rateLimiter->availableIn($key);
90
91
        return $this->addHeaders(
92
            $response, $maxAttempts,
93
            $this->calculateRemainingAttempts($key, $maxAttempts, $retryAfter),
94
            $retryAfter
95
        );
96
    }
97
98
    /**
99
     * Add the limit header information to the given response.
100
     *
101
     * @param Response $response
102
     * @param int      $maxAttempts
103
     * @param int      $remainingAttempts
104
     * @param int|null $retryAfter
105
     *
106
     * @return Response
107
     */
108
    protected function addHeaders(Response $response, $maxAttempts, $remainingAttempts, $retryAfter = null)
109
    {
110
        $headers = [
111
            'X-RateLimit-Limit' => $maxAttempts,
112
            'X-RateLimit-Remaining' => $remainingAttempts,
113
        ];
114
115
        if (!is_null($retryAfter)) {
116
            $headers['Retry-After'] = $retryAfter;
117
        }
118
119
        $response->headers->add($headers);
120
121
        return $response;
122
    }
123
124
    /**
125
     * Calculate the number of remaining attempts.
126
     *
127
     * @param string   $key
128
     * @param int      $maxAttempts
129
     * @param int|null $retryAfter
130
     *
131
     * @return int
132
     */
133
    protected function calculateRemainingAttempts($key, $maxAttempts, $retryAfter = null)
134
    {
135
        if (!is_null($retryAfter)) {
136
            return 0;
137
        }
138
139
        return $this->rateLimiter->retriesLeft($key, $maxAttempts);
140
    }
141
}
142