Completed
Push — master ( 4cf3c5...0c7238 )
by Jitendra
11s
created

Throttle::findRetryKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
1
<?php
2
3
namespace PhalconExt\Http\Middleware;
4
5
use Phalcon\Http\Request;
6
use Phalcon\Http\Response;
7
use PhalconExt\Http\BaseMiddleware;
8
9
/**
10
 * A request throttling middleware.
11
 *
12
 * @author  Jitendra Adhikari <[email protected]>
13
 * @license MIT
14
 *
15
 * @link    https://github.com/adhocore/phalcon-ext
16
 */
17
class Throttle extends BaseMiddleware
18
{
19
    /** @var string */
20
    protected $redis;
21
22
    protected $configKey = 'throttle';
23
24
    /**
25
     * Handle the throttle.
26
     *
27
     * @param Request  $request
28
     * @param Response $response
29
     *
30
     * @return bool
31
     */
32
    public function before(Request $request, Response $response): bool
33
    {
34
        if (null === $retryKey = $this->findRetryKey($request)) {
35
            return true;
36
        }
37
38
        $this->disableView();
39
40
        $after = \ceil($this->di('redis')->getTtl($retryKey) / 60);
41
42
        $response
43
            ->setContent("Too many requests. Try again in $after min.")
44
            ->setHeader('Retry-After', $after)
45
            ->setStatusCode(429)
46
            ->send();
47
48
        return false;
49
    }
50
51
    /**
52
     * Find the redis key that contains hits counter which has exceeded threshold for throttle.
53
     *
54
     * @param Request $request
55
     *
56
     * @return null|string
57
     */
58
    protected function findRetryKey(Request $request): ?string
59
    {
60
        $retryKey = null;
61
        $redis    = $this->di('redis');
62
        $baseKey  = $this->getKey($request);
63
64
        foreach ($this->config['maxHits'] as $minutes => $maxHits) {
65
            $key = "$baseKey:$minutes";
66
67
            if ($this->shouldThrottle($redis, $key, $minutes, $maxHits)) {
68
                $retryKey = $key;
69
            }
70
        }
71
72
        return $retryKey;
73
    }
74
75
    /**
76
     * Get the unique key for this client.
77
     *
78
     * @param Request $request
79
     *
80
     * @return string
81
     */
82
    protected function getKey(Request $request): string
83
    {
84
        $key = $request->getClientAddress(true);
85
86
        if ($this->config['checkUserAgent'] ?? false) {
87
            $key .= ':' . \md5($request->getUserAgent());
88
        }
89
90
        return ($this->config['prefix'] ?? '') . $key;
91
    }
92
93
    /**
94
     * Check if we should throttle. Update hits counter if not.
95
     *
96
     * @param Request $request
97
     *
98
     * @return bool
99
     */
100
    protected function shouldThrottle($redis, string $key, int $minutes, int $maxHits): bool
101
    {
102
        $hits = $redis->get($key) ?: 0;
103
        $ttl  = $hits ? $redis->getTtl($key) : $minutes * 60;
104
105
        if ($hits >= $maxHits) {
106
            return true;
107
        }
108
109
        $redis->save($key, $hits + 1, $ttl);
110
111
        return false;
112
    }
113
}
114