Skip to content

Commit 02ea9b8

Browse files
dbuostrolucky
authored andcommitted
Initial commit
0 parents  commit 02ea9b8

11 files changed

+307
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_size = 4
7+
indent_style = space
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

.gitattributes

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/.editorconfig export-ignore
2+
/.gitattributes export-ignore
3+
/.gitignore export-ignore
4+
/.github export-ignore
5+
/.phpcs.xml.dist export-ignore
6+
/.phpunit.xml.dist export-ignore
7+
/tests export-ignore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# GitHub Actions Documentation: https://docs.github.com/en/actions
2+
3+
name: "build"
4+
5+
on:
6+
push:
7+
branches:
8+
- "main"
9+
tags:
10+
- "*"
11+
pull_request:
12+
branches:
13+
- "main"
14+
15+
jobs:
16+
coding-standards:
17+
name: "Coding standards"
18+
runs-on: "ubuntu-latest"
19+
steps:
20+
- name: "Checkout repository"
21+
uses: "actions/checkout@v2"
22+
23+
- name: "Install PHP"
24+
uses: "shivammathur/setup-php@v2"
25+
with:
26+
php-version: "latest"
27+
coverage: "none"
28+
tools: "composer-normalize"
29+
30+
- name: "Install dependencies (Composer)"
31+
uses: "ramsey/composer-install@v2"
32+
33+
- name: "Check coding standards (PHP_CodeSniffer)"
34+
shell: "bash"
35+
run: "vendor/bin/phpcs"
36+
37+
- name: "Check that composer.json is normalized"
38+
run: "composer-normalize"
39+
40+
unit-tests:
41+
name: "Unit tests"
42+
runs-on: "ubuntu-latest"
43+
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
php-version:
48+
- "7.4"
49+
- "8.0"
50+
- "8.1"
51+
dependencies:
52+
- "lowest"
53+
- "highest"
54+
55+
steps:
56+
- name: "Checkout repository"
57+
uses: "actions/checkout@v2"
58+
59+
- name: "Install PHP"
60+
uses: "shivammathur/setup-php@v2"
61+
with:
62+
php-version: "${{ matrix.php-version }}"
63+
coverage: "none"
64+
65+
- name: "Install dependencies (Composer)"
66+
uses: "ramsey/composer-install@v2"
67+
with:
68+
dependency-versions: "${{ matrix.dependencies }}"
69+
70+
- name: "Run unit tests (PHPUnit)"
71+
shell: "bash"
72+
run: "vendor/bin/simple-phpunit"

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
build/
2+
vendor/
3+
composer.lock
4+
phpspec.yml
5+
phpunit.xml
6+
.phpunit.result.cache

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2022 PHP HTTP Team <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# throttle-plugin
2+
PHP-HTTP plugin for throttling/rate limiting with the [symfony/rate-limiter](https://symfony.com/doc/current/rate_limiter.html)
3+
4+
> Warning: Plugin currently utilizes usleep() and hence is blocking whole process while waiting
5+
6+
## Install
7+
8+
Via [Composer](https://getcomposer.org/doc/00-intro.md)
9+
10+
```bash
11+
composer require phphttp-plugin/throttle
12+
```
13+
## Usage
14+
15+
```php
16+
new \Http\Client\Common\PluginClient($psr18Client, [
17+
new \Http\Client\Common\Plugin\ThrottlePluginn(
18+
(new \Symfony\Component\RateLimiter\RateLimiterFactory(
19+
['id' => 'foo', 'policy' => 'fixed_window', 'limit' => 2, 'interval' => '3 seconds'],
20+
new \Symfony\Component\RateLimiter\Storage\InMemoryStorage(),
21+
))->create(),
22+
),
23+
]);
24+
```
25+
26+
## Licensing
27+
28+
MIT license. Please see [License File](LICENSE) for more information.

composer.json

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "php-http/throttle-plugin",
3+
"description": "Throttle/request limiter plugin for HTTPlug",
4+
"license": "MIT",
5+
"keywords": [
6+
"http",
7+
"httplug",
8+
"plugin",
9+
"ratelimit",
10+
"throttle"
11+
],
12+
"authors": [
13+
{
14+
"name": "Gabriel Ostrolucký",
15+
"email": "[email protected]"
16+
}
17+
],
18+
"homepage": "https://github.com/php-http/throttle-plugin",
19+
"require": {
20+
"php": ">=7.4",
21+
"php-http/client-common": ">=1.3",
22+
"symfony/rate-limiter": ">=5.2"
23+
},
24+
"require-dev": {
25+
"nyholm/psr7": "^1.0",
26+
"php-http/mock-client": "*",
27+
"ramsey/coding-standard": "^2.0",
28+
"symfony/phpunit-bridge": ">=6.0"
29+
},
30+
"minimum-stability": "dev",
31+
"prefer-stable": true,
32+
"autoload": {
33+
"psr-4": {
34+
"Http\\Client\\Common\\Plugin\\": "src/"
35+
}
36+
},
37+
"autoload-dev": {
38+
"psr-4": {
39+
"Tests\\Http\\Client\\Common\\Plugin\\": "tests/"
40+
}
41+
},
42+
"config": {
43+
"allow-plugins": {
44+
"dealerdirect/phpcodesniffer-composer-installer": true,
45+
"ergebnis/composer-normalize": true
46+
}
47+
}
48+
}

phpcs.xml.dist

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0"?>
2+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
3+
4+
<arg name="extensions" value="php"/>
5+
<arg name="colors"/>
6+
<arg value="sp"/>
7+
8+
<file>./src</file>
9+
10+
<rule ref="Ramsey">
11+
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFallbackGlobalName"/>
12+
</rule>
13+
14+
</ruleset>

phpunit.xml.dist

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="./vendor/bin/.phpunit/phpunit/phpunit.xsd"
4+
bootstrap="./vendor/autoload.php"
5+
colors="true"
6+
verbose="true">
7+
8+
<php>
9+
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0" />
10+
</php>
11+
12+
<testsuites>
13+
<testsuite name="unit-tests">
14+
<directory>./src</directory>
15+
</testsuite>
16+
</testsuites>
17+
18+
</phpunit>

src/ThrottlePlugin.php

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\Client\Common\Plugin;
6+
7+
use Http\Client\Common\Plugin;
8+
use Http\Promise\Promise;
9+
use Psr\Http\Message\RequestInterface;
10+
use Symfony\Component\RateLimiter\LimiterInterface;
11+
12+
final class ThrottlePlugin implements Plugin
13+
{
14+
private LimiterInterface $rateLimiter;
15+
16+
public function __construct(LimiterInterface $rateLimiter)
17+
{
18+
$this->rateLimiter = $rateLimiter;
19+
}
20+
21+
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
22+
{
23+
$this->rateLimiter->reserve()->wait();
24+
25+
return $next($request);
26+
}
27+
}

tests/ThrottlePluginTest.php

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Http\Client\Common\Plugin;
6+
7+
use Http\Client\Common\Plugin\ThrottlePlugin;
8+
use Http\Client\Common\PluginClient;
9+
use Http\Mock\Client;
10+
use Nyholm\Psr7\Factory\HttplugFactory;
11+
use Nyholm\Psr7\Request;
12+
use PHPUnit\Framework\TestCase;
13+
use Symfony\Bridge\PhpUnit\ClockMock;
14+
use Symfony\Component\RateLimiter\RateLimit;
15+
use Symfony\Component\RateLimiter\RateLimiterFactory;
16+
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;
17+
18+
/**
19+
* @group time-sensitive
20+
*/
21+
class ThrottlePluginTest extends TestCase
22+
{
23+
private Client $mockClient;
24+
private PluginClient $client;
25+
26+
protected function setUp(): void
27+
{
28+
ClockMock::register(RateLimit::class);
29+
$this->mockClient = new Client(new HttplugFactory());
30+
$this->client = new PluginClient($this->mockClient, [
31+
new ThrottlePlugin(
32+
(new RateLimiterFactory(
33+
['id' => 'foo', 'policy' => 'fixed_window', 'limit' => 2, 'interval' => '3 seconds'],
34+
new InMemoryStorage(),
35+
))->create(),
36+
),
37+
]);
38+
}
39+
40+
public function testNoThrottle(): void
41+
{
42+
$time = time();
43+
$this->client->sendRequest(new Request('GET', ''));
44+
$this->client->sendRequest(new Request('GET', ''));
45+
$this->assertEqualsWithDelta($time, time(), 1);
46+
}
47+
48+
public function testThrottle(): void
49+
{
50+
$time = time();
51+
$this->client->sendRequest(new Request('GET', ''));
52+
$this->client->sendRequest(new Request('GET', ''));
53+
$this->client->sendRequest(new Request('GET', ''));
54+
$this->assertEqualsWithDelta($time, ($timeAfterThrottle = time()) - 3, 1);
55+
56+
$this->client->sendRequest(new Request('GET', ''));
57+
$this->assertEqualsWithDelta($timeAfterThrottle, time(), 1);
58+
}
59+
}

0 commit comments

Comments
 (0)