├── .gitignore
├── src
├── Exception
│ ├── NotFoundException.php
│ ├── ToxicExistsException.php
│ ├── InvalidProxyException.php
│ ├── InvalidToxicException.php
│ ├── ProxyExistsException.php
│ ├── Exception.php
│ └── UnexpectedStatusCodeException.php
├── StreamDirections.php
├── StatusCodes.php
├── ToxicTypes.php
├── Toxic.php
├── UrlHelpers.php
├── Proxy.php
└── Toxiproxy.php
├── docker
└── php
│ ├── xdebug.ini
│ └── php.ini
├── phpstan.neon
├── examples
├── composer.json
├── bandwidth.php
├── slow-close.php
├── latency.php
└── timeout.php
├── .github
└── workflows
│ └── ci.yml
├── docker-compose.yml
├── phpcs.xml
├── phpunit.xml.dist
├── Dockerfile
├── tests
├── Test
│ ├── HttpMockHelpers.php
│ └── BaseTestCase.php
├── ProxyTest.php
└── ToxiproxyTest.php
├── LICENSE
├── README.md
└── composer.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | /_reports
4 | /_docs
5 | /examples/vendor
--------------------------------------------------------------------------------
/src/Exception/NotFoundException.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | */src/Kernel.php
9 |
10 |
--------------------------------------------------------------------------------
/docker/php/php.ini:
--------------------------------------------------------------------------------
1 | ; Recommended production values
2 | display_errors = Off
3 | display_startup_errors = Off
4 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
5 | html_errors = Off
6 | log_errors = On
7 | max_input_time = 60
8 | output_buffering = 4096
9 | register_argc_argv = Off
10 | request_order = "GP"
11 | session.gc_divisor = 1000
12 | short_open_tag = Off
13 | track_errors = Off
14 | variables_order = "GPCS"
15 |
16 | ; Custom
17 | date.timezone = UTC
18 | memory_limit = -1
19 |
--------------------------------------------------------------------------------
/examples/bandwidth.php:
--------------------------------------------------------------------------------
1 | create('ihsw_example_redis_master', '127.0.0.1:6379');
13 | $toxic = $proxy->create(ToxicTypes::BANDWIDTH->value, StreamDirections::UPSTREAM->value, 1.0, [
14 | 'rate' => 1000,
15 | ]);
16 | printf(
17 | "Listening on IP %s and port %s on behalf of 6379, with a connection that's limited to 1000KB/s\n",
18 | $proxy->getListenIp(),
19 | $proxy->getListenPort(),
20 | );
21 |
22 | $toxiproxy->delete($proxy);
23 |
--------------------------------------------------------------------------------
/examples/slow-close.php:
--------------------------------------------------------------------------------
1 | create('ihsw_example_redis_master', '127.0.0.1:6379');
13 | $toxic = $proxy->create(ToxicTypes::SLOW_CLOSE->value, StreamDirections::UPSTREAM->value, 1.0, [
14 | 'delay' => 1000,
15 | ]);
16 | printf(
17 | "Listening on IP %s and port %s on behalf of 6379, with a connection that takes 1000ms to close\n",
18 | $proxy->getListenIp(),
19 | $proxy->getListenPort(),
20 | );
21 |
22 | $toxiproxy->delete($proxy);
23 |
--------------------------------------------------------------------------------
/examples/latency.php:
--------------------------------------------------------------------------------
1 | create('ihsw_example_redis_master', '127.0.0.1:6379');
13 | $toxic = $proxy->create(ToxicTypes::LATENCY->value, StreamDirections::UPSTREAM->value, 1.0, [
14 | 'latency' => 100,
15 | 'jitter' => 50,
16 | ]);
17 | printf(
18 | "Listening on IP %s and port %s on behalf of 6379, with latency between 100-150ms\n",
19 | $proxy->getListenIp(),
20 | $proxy->getListenPort(),
21 | );
22 |
23 | $toxiproxy->delete($proxy);
24 |
--------------------------------------------------------------------------------
/examples/timeout.php:
--------------------------------------------------------------------------------
1 | create('ihsw_example_redis_master', '127.0.0.1:6379');
13 | $toxic = $proxy->create(ToxicTypes::TIMEOUT->value, StreamDirections::UPSTREAM->value, 1.0, [
14 | 'timeout' => 5*1000,
15 | ]);
16 | printf(
17 | "Listening on IP %s and port %s on behalf of 6379, with a connection that does nothing but timeout after 5000ms\n",
18 | $proxy->getListenIp(),
19 | $proxy->getListenPort(),
20 | );
21 |
22 | $toxiproxy->delete($proxy);
23 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
20 | src
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax=docker/dockerfile:1.3
2 |
3 | ARG PHP_VERSION=8.4
4 | ARG XDEBUG_VERSION="-3.4.2"
5 |
6 | FROM php:${PHP_VERSION}-cli as dev
7 |
8 | ARG DEBIAN_FRONTEND=noninteractivef
9 | ARG XDEBUG_VERSION
10 | ENV COMPOSER_FLAGS="--prefer-dist --no-interaction"
11 | ENV COMPOSER_ALLOW_SUPERUSER 1
12 |
13 | COPY docker/php/php.ini /usr/local/etc/php/php.ini
14 | COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/
15 |
16 | RUN apt update -q \
17 | && apt install -y --no-install-recommends git zip unzip libzip4 libzip-dev zlib1g-dev \
18 | && docker-php-ext-install zip \
19 | && apt-get remove --autoremove -y libzip-dev zlib1g-dev \
20 | && rm -rf /var/lib/apt/lists/*
21 |
22 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer
23 |
24 | RUN pecl install xdebug${XDEBUG_VERSION} \
25 | && docker-php-ext-enable xdebug
26 |
27 | WORKDIR /code
28 |
29 | COPY composer.* /code/
30 | RUN composer install $COMPOSER_FLAGS --no-scripts
31 |
32 | COPY . /code/
33 |
34 |
--------------------------------------------------------------------------------
/tests/Test/HttpMockHelpers.php:
--------------------------------------------------------------------------------
1 | $responses
16 | */
17 | protected static function mockHttpClientFactory(?array $responses): HttpClient
18 | {
19 | $mock = new HttpMockHandler($responses);
20 | $handler = HttpHandlerStack::create($mock);
21 | return new HttpClient(
22 | [
23 | 'handler' => $handler,
24 | 'http_errors' => false,
25 | ],
26 | );
27 | }
28 |
29 | /**
30 | * @param string[][] $headers
31 | */
32 | protected static function httpResponseFactory(int $statusCode, string $body, array $headers = []): HttpResponse
33 | {
34 | return new HttpResponse($statusCode, $headers, $body);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 ihsw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Toxiproxy PHP Client
2 | ====================
3 |
4 | [Toxiproxy](https://github.com/shopify/toxiproxy) makes it easy and trivial to test network conditions, for example low-bandwidth and high-latency situations. `toxiproxy-php-client` includes everything needed to get started with configuring Toxiproxy upstream connection and listen endpoints.
5 |
6 | *Note: `toxiproxy-php-client` is currently compatible with `toxiproxy-2.0+`.*
7 |
8 | Installing via Composer
9 | -----------------------
10 |
11 | The recommended way to install `toxiproxy-php-client` is through [Composer](http://getcomposer.org/).
12 |
13 | Once that is installed and you have added `ihsw/toxiproxy-php-client` to your `composer.json` configuration, you can require the autoloader and start using the library.
14 |
15 | Here is an example for creating a proxy that limits a Redis connection to 1000KB/s.
16 |
17 | ```php
18 | create("ihsw_example_redis_master", "127.0.0.1:6379");
28 | $toxic = $proxy->create(ToxicTypes::BANDWIDTH->value, StreamDirections::UPSTREAM->value, 1.0, [
29 | "rate" => 1000
30 | ]);
31 | printf(
32 | "Listening on IP %s and port %s on behalf of 6379, with a connection that's limited to 1000KB/s\n",
33 | $proxy->getListenIp(),
34 | $proxy->getListenPort()
35 | );
36 |
37 | $toxiproxy->delete($proxy);
38 |
39 | ```
40 |
41 | Documentation
42 | -------------
43 |
44 | Additional examples can be found in the `examples` directory for expected usage.
45 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ihsw/toxiproxy-php-client",
3 | "description": "PHP client for shopify/toxiproxy",
4 | "license": "MIT",
5 | "keywords": [
6 | "toxiproxy",
7 | "php",
8 | "client"
9 | ],
10 | "authors": [
11 | {
12 | "name": "Adrian Parker",
13 | "email": "ihsw.aparker@gmail.com"
14 | }
15 | ],
16 | "require": {
17 | "php": "^8.3",
18 | "ext-json": "*",
19 | "guzzlehttp/guzzle": "^7.9"
20 | },
21 | "require-dev": {
22 | "keboola/coding-standard": "^15.1",
23 | "phpunit/phpunit": "^12.1",
24 | "phpstan/phpstan": "^2.1",
25 | "phpstan/phpstan-phpunit": "^2.0",
26 | "squizlabs/php_codesniffer": "^3.12"
27 | },
28 | "autoload": {
29 | "psr-4": {
30 | "Ihsw\\Toxiproxy\\": "src"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Ihsw\\Toxiproxy\\Tests\\": "tests"
36 | }
37 | },
38 | "scripts": {
39 | "ci": [
40 | "@composer validate --no-check-publish --no-check-all",
41 | "@phpcs",
42 | "@phpstan",
43 | "@phpunit"
44 | ],
45 | "phpcs": "phpcs -n --ignore=vendor,cache,Kernel.php --extensions=php .",
46 | "phpcbf": "phpcbf --extensions=php src tests examples",
47 | "phpstan": "phpstan analyse --no-progress -c phpstan.neon",
48 | "phpunit": [
49 | "@putenv XDEBUG_MODE=coverage",
50 | "phpunit --coverage-clover /tmp/build-logs/clover.xml --coverage-xml=/tmp/build-logs/coverage-xml --log-junit=/tmp/build-logs/phpunit.junit.xml"
51 | ]
52 | },
53 | "config": {
54 | "lock": false,
55 | "sort-packages": true,
56 | "allow-plugins": {
57 | "dealerdirect/phpcodesniffer-composer-installer": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Toxic.php:
--------------------------------------------------------------------------------
1 | proxy = $proxy;
21 | $this->name = $name;
22 | $this->type = $type;
23 | $this->stream = $stream;
24 | $this->toxicity = 1.0;
25 | $this->attributes = [];
26 | }
27 |
28 | public function getName(): string
29 | {
30 | return $this->name;
31 | }
32 |
33 | public function getType(): string
34 | {
35 | return $this->type;
36 | }
37 |
38 | public function getStream(): string
39 | {
40 | return $this->stream;
41 | }
42 |
43 | public function getToxicity(): float
44 | {
45 | return $this->toxicity;
46 | }
47 |
48 | public function getProxy(): Proxy
49 | {
50 | return $this->proxy;
51 | }
52 |
53 | public function setToxicity(float $toxicity): self
54 | {
55 | $this->toxicity = $toxicity;
56 | return $this;
57 | }
58 |
59 | public function getAttributes(): array
60 | {
61 | return $this->attributes;
62 | }
63 |
64 | public function setAttributes(array $attributes): self
65 | {
66 | $this->attributes = $attributes;
67 | return $this;
68 | }
69 |
70 | public function jsonSerialize(): array
71 | {
72 | return [
73 | 'name' => $this->name,
74 | 'stream' => $this->stream,
75 | 'type' => $this->type,
76 | 'toxicity' => $this->toxicity,
77 | 'attributes' => $this->attributes,
78 | ];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/tests/Test/BaseTestCase.php:
--------------------------------------------------------------------------------
1 | createToxiproxy();
24 | foreach ($toxiproxy->getAll() as $proxy) {
25 | $toxiproxy->delete($proxy);
26 | }
27 | }
28 |
29 | /**
30 | * @param array|null $mockResponses
31 | */
32 | protected function createToxiproxy(?array $mockResponses = null): Toxiproxy
33 | {
34 | if ($mockResponses === null) {
35 | return new Toxiproxy(sprintf('http://%s:%s', $this->getToxiproxyHost(), $this->getToxiproxyPort()));
36 | }
37 |
38 | $toxiproxy = new Toxiproxy('');
39 | $toxiproxy->setHttpClient(self::mockHttpClientFactory($mockResponses));
40 | return $toxiproxy;
41 | }
42 |
43 | private function getToxiproxyHost(): string
44 | {
45 | return (string) getenv('TOXIPROXY_HOST');
46 | }
47 |
48 | private function getToxiproxyPort(): string
49 | {
50 | return (string) getenv('TOXIPROXY_PORT');
51 | }
52 |
53 |
54 | protected function getListen(int $listen = 34343): string
55 | {
56 | return sprintf('%s:%s', '127.0.0.1', $listen);
57 | }
58 |
59 | protected function createProxy(Toxiproxy $toxiproxy): Proxy
60 | {
61 | return $toxiproxy->create(self::PROXY_NAME, self::TEST_UPSTREAM, $this->getListen());
62 | }
63 |
64 | protected function createToxic(Proxy $proxy, string $type, array $attr): Toxic
65 | {
66 | return $proxy->create($type, StreamDirections::UPSTREAM->value, 1.0, $attr);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/UrlHelpers.php:
--------------------------------------------------------------------------------
1 | 'GET', 'uri' => '/proxies'];
16 | }
17 |
18 | /**
19 | * @return array{method: string, uri: string}
20 | */
21 | public function createProxyRoute(): array
22 | {
23 | return ['method' => 'POST', 'uri' => '/proxies'];
24 | }
25 |
26 | /**
27 | * @return array{method: string, uri: string}
28 | */
29 | public function populateRoute(): array
30 | {
31 | return ['method' => 'POST', 'uri' => '/populate'];
32 | }
33 |
34 | /**
35 | * @return array{method: string, uri: string}
36 | */
37 | public function getProxyRoute(string $name): array
38 | {
39 | return ['method' => 'GET', 'uri' => sprintf('/proxies/%s', $name)];
40 | }
41 |
42 | /**
43 | * @return array{method: string, uri: string}
44 | */
45 | public function updateProxyRoute(Proxy $proxy): array
46 | {
47 | return ['method' => 'POST', 'uri' => sprintf('/proxies/%s', $proxy->getName())];
48 | }
49 |
50 | /**
51 | * @return array{method: string, uri: string}
52 | */
53 | public function deleteProxyRoute(Proxy $proxy): array
54 | {
55 | return ['method' => 'DELETE', 'uri' => sprintf('/proxies/%s', $proxy->getName())];
56 | }
57 |
58 | /**
59 | * @return array{method: string, uri: string}
60 | */
61 | public function getToxicsRoute(Proxy $proxy): array
62 | {
63 | return ['method' => 'GET', 'uri' => sprintf('/proxies/%s/toxics', $proxy->getName())];
64 | }
65 |
66 | /**
67 | * @return array{method: string, uri: string}
68 | */
69 | public function createToxicRoute(Proxy $proxy): array
70 | {
71 | return ['method' => 'POST', 'uri' => sprintf('/proxies/%s/toxics', $proxy->getName())];
72 | }
73 |
74 | /**
75 | * @return array{method: string, uri: string}
76 | */
77 | public function getToxicRoute(Proxy $proxy, string $name): array
78 | {
79 | return ['method' => 'GET', 'uri' => sprintf('/proxies/%s/toxics/%s', $proxy->getName(), $name)];
80 | }
81 |
82 | /**
83 | * @return array{method: string, uri: string}
84 | */
85 | public function updateToxicRoute(Proxy $proxy, Toxic $toxic): array
86 | {
87 | return ['method' => 'POST', 'uri' => sprintf('/proxies/%s/toxics/%s', $proxy->getName(), $toxic->getName())];
88 | }
89 |
90 | /**
91 | * @return array{method: string, uri: string}
92 | */
93 | public function deleteToxicRoute(Proxy $proxy, Toxic $toxic): array
94 | {
95 | return ['method' => 'DELETE', 'uri' => sprintf('/proxies/%s/toxics/%s', $proxy->getName(), $toxic->getName())];
96 | }
97 |
98 | /**
99 | * @return array{method: string, uri: string}
100 | */
101 | public function resetRoute(): array
102 | {
103 | return ['method' => 'POST', 'uri' => '/reset'];
104 | }
105 |
106 | /**
107 | * @return array{method: string, uri: string}
108 | */
109 | public function versionRoute(): array
110 | {
111 | return ['method' => 'GET', 'uri' => '/version'];
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/ProxyTest.php:
--------------------------------------------------------------------------------
1 | createToxiproxy();
21 | $proxy = $this->createProxy($toxiproxy);
22 |
23 | $attr = [
24 | 'latency' => 1000,
25 | 'jitter' => 50,
26 | ];
27 | $toxic = $proxy->create(ToxicTypes::LATENCY->value, StreamDirections::UPSTREAM->value, 1.0, $attr);
28 | self::assertEquals(ToxicTypes::LATENCY->value, $toxic->getType());
29 | self::assertEquals(StreamDirections::UPSTREAM->value, $toxic->getStream());
30 | self::assertEquals(1.0, $toxic->getToxicity());
31 | self::assertEquals($attr, $toxic->getAttributes());
32 | }
33 |
34 | public function testCreateDuplicate(): void
35 | {
36 | $toxiproxy = $this->createToxiproxy();
37 | $proxy = $this->createProxy($toxiproxy);
38 | $this->createToxic(
39 | $proxy,
40 | ToxicTypes::LATENCY->value,
41 | [
42 | 'latency' => 1000,
43 | 'jitter' => 500,
44 | ],
45 | );
46 |
47 | $this->expectException(ToxicExistsException::class);
48 | $this->createToxic(
49 | $proxy,
50 | ToxicTypes::LATENCY->value,
51 | [
52 | 'latency' => 1000,
53 | 'jitter' => 500,
54 | ],
55 | );
56 | }
57 |
58 | public function testCreatedUnexpectedStatus(): void
59 | {
60 | $toxiproxy = $this->createToxiproxy(
61 | [
62 | self::httpResponseFactory(418, ''),
63 | ],
64 | );
65 |
66 | $this->expectException(UnexpectedStatusCodeException::class);
67 | $this->createToxic(new Proxy($toxiproxy, ''), '', []);
68 | }
69 |
70 | public function testGetAll(): void
71 | {
72 | $toxiproxy = $this->createToxiproxy();
73 | $proxy = $this->createProxy($toxiproxy);
74 | $toxic = $this->createToxic(
75 | $proxy,
76 | ToxicTypes::LATENCY->value,
77 | [
78 | 'latency' => 1000,
79 | 'jitter' => 500,
80 | ],
81 | );
82 |
83 | $toxics = $proxy->getAll();
84 | self::assertEquals($toxic, $toxics[0]);
85 | }
86 |
87 | public function testGetAllUnexpectedStatus(): void
88 | {
89 | $toxiproxy = $this->createToxiproxy(
90 | [
91 | self::httpResponseFactory(418, ''),
92 | ],
93 | );
94 |
95 | $this->expectException(UnexpectedStatusCodeException::class);
96 | $proxy = new Proxy($toxiproxy, '');
97 | $proxy->getAll();
98 | }
99 |
100 | public function testGet(): void
101 | {
102 | $toxiproxy = $this->createToxiproxy();
103 | $proxy = $this->createProxy($toxiproxy);
104 | $toxic = $this->createToxic(
105 | $proxy,
106 | ToxicTypes::LATENCY->value,
107 | [
108 | 'latency' => 1000,
109 | 'jitter' => 500,
110 | ],
111 | );
112 |
113 | self::assertEquals($toxic, $proxy->get($toxic->getName()));
114 | }
115 |
116 | public function testGetNotFound(): void
117 | {
118 | $toxiproxy = $this->createToxiproxy();
119 | $proxy = $this->createProxy($toxiproxy);
120 |
121 | $toxic = $proxy->get('non-existent');
122 | self::assertNull($toxic);
123 | }
124 |
125 | public function testGetUnexpectedStatus(): void
126 | {
127 | $toxiproxy = $this->createToxiproxy(
128 | [
129 | self::httpResponseFactory(418, ''),
130 | ],
131 | );
132 |
133 | $this->expectException(UnexpectedStatusCodeException::class);
134 | $proxy = new Proxy($toxiproxy, '');
135 | $proxy->get('');
136 | }
137 |
138 | public function testUpdate(): void
139 | {
140 | $toxiproxy = $this->createToxiproxy();
141 | $proxy = $this->createProxy($toxiproxy);
142 | $toxic = $this->createToxic(
143 | $proxy,
144 | ToxicTypes::LATENCY->value,
145 | [
146 | 'latency' => 1000,
147 | 'jitter' => 500,
148 | ],
149 | );
150 |
151 | $toxic->setAttributes(['latency' => 2000, 'jitter' => 1000])
152 | ->setToxicity(0.5);
153 | $updatedToxic = $proxy->update($toxic);
154 | self::assertEquals($updatedToxic, $toxic);
155 | }
156 |
157 | public function testUpdateNotFound(): void
158 | {
159 | $toxiproxy = $this->createToxiproxy();
160 | $proxy = $this->createProxy($toxiproxy);
161 | $toxic = $this->createToxic(
162 | $proxy,
163 | ToxicTypes::LATENCY->value,
164 | [
165 | 'latency' => 1000,
166 | 'jitter' => 500,
167 | ],
168 | );
169 |
170 | $proxy->delete($toxic);
171 | $this->expectException(NotFoundException::class);
172 | $proxy->update($toxic);
173 | }
174 |
175 | public function testUpdateUnexpectedStatus(): void
176 | {
177 | $toxiproxy = $this->createToxiproxy(
178 | [
179 | self::httpResponseFactory(418, ''),
180 | ],
181 | );
182 |
183 | $this->expectException(UnexpectedStatusCodeException::class);
184 | $proxy = new Proxy($toxiproxy, '');
185 | $proxy->update(new Toxic($proxy, '', '', ''));
186 | }
187 |
188 | public function testDelete(): void
189 | {
190 | $toxiproxy = $this->createToxiproxy();
191 | $proxy = $this->createProxy($toxiproxy);
192 | $toxic = $this->createToxic(
193 | $proxy,
194 | ToxicTypes::LATENCY->value,
195 | [
196 | 'latency' => 1000,
197 | 'jitter' => 500,
198 | ],
199 | );
200 | self::assertCount(1, $toxiproxy->getAll());
201 | self::assertCount(1, $proxy->getAll());
202 | $proxy->delete($toxic);
203 | self::assertCount(1, $toxiproxy->getAll());
204 | self::assertCount(0, $proxy->getAll());
205 | }
206 |
207 | public function testDeleteNotFound(): void
208 | {
209 | $toxiproxy = $this->createToxiproxy();
210 | $proxy = $this->createProxy($toxiproxy);
211 | $toxic = $this->createToxic(
212 | $proxy,
213 | ToxicTypes::LATENCY->value,
214 | [
215 | 'latency' => 1000,
216 | 'jitter' => 500,
217 | ],
218 | );
219 |
220 | $proxy->delete($toxic);
221 | $this->expectException(NotFoundException::class);
222 | $proxy->delete($toxic);
223 | }
224 |
225 | public function testDeleteUnexpectedStatus(): void
226 | {
227 | $toxiproxy = $this->createToxiproxy(
228 | [
229 | self::httpResponseFactory(418, ''),
230 | ],
231 | );
232 |
233 | $this->expectException(UnexpectedStatusCodeException::class);
234 | $proxy = new Proxy($toxiproxy, '');
235 | $proxy->delete(new Toxic($proxy, '', '', ''));
236 | }
237 |
238 | public function testGetListenIp(): void
239 | {
240 | $toxiproxy = $this->createToxiproxy();
241 | $proxy = $this->createProxy($toxiproxy);
242 | $listenIp = $proxy->getListenIp();
243 | self::assertNotEmpty($listenIp);
244 | }
245 |
246 | public function testGetListenPort(): void
247 | {
248 | $toxiproxy = $this->createToxiproxy();
249 | $proxy = $this->createProxy($toxiproxy);
250 | $listenPort = $proxy->getListenPort();
251 | self::assertNotEmpty($listenPort);
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/tests/ToxiproxyTest.php:
--------------------------------------------------------------------------------
1 | createToxiproxy();
20 | $listen = $this->getListen();
21 | $proxy = $toxiproxy->create(self::PROXY_NAME, self::TEST_UPSTREAM, $listen);
22 |
23 | self::assertEquals(self::PROXY_NAME, $proxy->getName());
24 | self::assertEquals(self::TEST_UPSTREAM, $proxy->getUpstream());
25 | self::assertEquals($listen, $proxy->getListen());
26 | self::assertTrue($proxy->isEnabled());
27 |
28 | $toxiproxy->delete($proxy);
29 | }
30 |
31 | public function testCreateDuplicate(): void
32 | {
33 | $toxiproxy = $this->createToxiproxy();
34 | $this->createProxy($toxiproxy);
35 |
36 | $this->expectException(ProxyExistsException::class);
37 | $this->createProxy($toxiproxy);
38 | }
39 |
40 | public function testCreateUnexpectedStatus(): void
41 | {
42 | $toxiproxy = $this->createToxiproxy(
43 | [
44 | self::httpResponseFactory(418, ''),
45 | ],
46 | );
47 |
48 | $this->expectException(UnexpectedStatusCodeException::class);
49 | $this->createProxy($toxiproxy);
50 | }
51 |
52 | public function testPopulate(): void
53 | {
54 | $toxiproxy = $this->createToxiproxy();
55 |
56 | // producing a list of proposed proxy-bodies and the expected proxies
57 | $proxyBodies = [
58 | [
59 | 'name' => self::PROXY_NAME,
60 | 'listen' => $this->getListen(),
61 | 'upstream' => self::TEST_UPSTREAM,
62 | 'enabled' => true,
63 | ],
64 | ];
65 | $expectedProxy = new Proxy($toxiproxy, $proxyBodies[0]['name']);
66 | $expectedProxy->setEnabled($proxyBodies[0]['enabled'])
67 | ->setListen($proxyBodies[0]['listen'])
68 | ->setUpstream($proxyBodies[0]['upstream']);
69 | $expectedProxies = [$expectedProxy];
70 |
71 | // populating
72 | $proxies = $toxiproxy->populate($proxyBodies);
73 |
74 | // comparing the results
75 | self::assertEquals($expectedProxies, $proxies);
76 | }
77 |
78 | public function testPopulateBadData(): void
79 | {
80 | $toxiproxy = $this->createToxiproxy();
81 |
82 | $this->expectException(InvalidProxyException::class);
83 | $toxiproxy->populate(['bullshit' => 'yes']);
84 | }
85 |
86 | public function testPopulateUnexpectedStatus(): void
87 | {
88 | $toxiproxy = $this->createToxiproxy(
89 | [
90 | self::httpResponseFactory(418, ''),
91 | ],
92 | );
93 |
94 | $this->expectException(UnexpectedStatusCodeException::class);
95 | $toxiproxy->populate([]);
96 | }
97 |
98 | public function testGet(): void
99 | {
100 | $toxiproxy = $this->createToxiproxy();
101 | $proxy = $this->createProxy($toxiproxy);
102 |
103 | self::assertEquals(
104 | $proxy->getName(),
105 | $toxiproxy->get($proxy->getName())?->getName(),
106 | );
107 | }
108 |
109 | public function testGetUnexpectedStatus(): void
110 | {
111 | $toxiproxy = $this->createToxiproxy(
112 | [
113 | self::httpResponseFactory(418, ''),
114 | ],
115 | );
116 |
117 | $this->expectException(UnexpectedStatusCodeException::class);
118 | $toxiproxy->get('');
119 | }
120 |
121 | public function testGetWithToxics(): void
122 | {
123 | $toxiproxy = $this->createToxiproxy();
124 | $proxy = $this->createProxy($toxiproxy);
125 |
126 | $toxics = [$this->createToxic(
127 | $proxy,
128 | ToxicTypes::LATENCY->value,
129 | [
130 | 'latency' => 1000,
131 | 'jitter' => 500,
132 | ],
133 | )];
134 | $receivedProxy = $toxiproxy->get($proxy->getName());
135 | self::assertSame(
136 | $toxics[0]->jsonSerialize(),
137 | $receivedProxy?->getToxics()[0]->jsonSerialize(),
138 | );
139 | }
140 |
141 | public function testGetNotFound(): void
142 | {
143 | $toxiproxy = $this->createToxiproxy();
144 | $proxy = $toxiproxy->get('non-existent');
145 | self::assertNull($proxy);
146 | }
147 |
148 | public function testGetAll(): void
149 | {
150 | $toxiproxy = $this->createToxiproxy();
151 | $proxy = $this->createProxy($toxiproxy);
152 | $proxies = $toxiproxy->getAll();
153 | self::assertEquals($proxy, $proxies[0]);
154 | }
155 |
156 | public function testGetAllUnexpectedStatus(): void
157 | {
158 | $toxiproxy = $this->createToxiproxy(
159 | [
160 | self::httpResponseFactory(418, ''),
161 | ],
162 | );
163 |
164 | $this->expectException(UnexpectedStatusCodeException::class);
165 | $toxiproxy->getAll();
166 | }
167 |
168 | public function testDelete(): void
169 | {
170 | $toxiproxy = $this->createToxiproxy();
171 | self::assertCount(0, $toxiproxy->getAll());
172 | $toxiproxy->delete($this->createProxy($toxiproxy));
173 | self::assertCount(0, $toxiproxy->getAll());
174 | }
175 |
176 | public function testDeleteNotFound(): void
177 | {
178 | $toxiproxy = $this->createToxiproxy();
179 | $proxy = $this->createProxy($toxiproxy);
180 | $toxiproxy->delete($proxy);
181 |
182 | $this->expectException(NotFoundException::class);
183 | $toxiproxy->delete($proxy);
184 | }
185 |
186 | public function testDeleteUnexpectedStatus(): void
187 | {
188 | $toxiproxy = $this->createToxiproxy(
189 | [
190 | self::httpResponseFactory(418, ''),
191 | ],
192 | );
193 |
194 | $this->expectException(UnexpectedStatusCodeException::class);
195 | $toxiproxy->delete(new Proxy($toxiproxy, ''));
196 | }
197 |
198 | public function testUpdate(): void
199 | {
200 | $toxiproxy = $this->createToxiproxy();
201 | $proxy = $this->createProxy($toxiproxy);
202 |
203 | $proxy->setListen($this->getListen(43434))
204 | ->setUpstream(self::TEST_UPSTREAM)
205 | ->setEnabled(false);
206 | $updatedProxy = $toxiproxy->update($proxy);
207 | self::assertEquals($proxy, $updatedProxy);
208 | }
209 |
210 | public function testUpdateDisabled(): void
211 | {
212 | $toxiproxy = $this->createToxiproxy();
213 |
214 | $proxy = $this->createProxy($toxiproxy);
215 | self::assertTrue($proxy->isEnabled());
216 |
217 | $proxy->setEnabled(false);
218 | $proxy = $toxiproxy->update($proxy);
219 | self::assertFalse($proxy->isEnabled());
220 | }
221 |
222 | public function testUpdateNotFound(): void
223 | {
224 | $toxiproxy = $this->createToxiproxy();
225 |
226 | $this->expectException(NotFoundException::class);
227 | $toxiproxy->update(new Proxy($toxiproxy, 'not-found'));
228 | }
229 |
230 | public function testUpdateUnexpectedStatus(): void
231 | {
232 | $toxiproxy = $this->createToxiproxy(
233 | [
234 | self::httpResponseFactory(418, ''),
235 | ],
236 | );
237 |
238 | $this->expectException(UnexpectedStatusCodeException::class);
239 | $toxiproxy->update(new Proxy($toxiproxy, ''));
240 | }
241 |
242 | public function testReset(): void
243 | {
244 | $toxiproxy = $this->createToxiproxy();
245 |
246 | // creating a proxy and a toxic, and disabling the proxy
247 | $proxy = $this->createProxy($toxiproxy);
248 | $this->createToxic(
249 | $proxy,
250 | ToxicTypes::LATENCY->value,
251 | [
252 | 'latency' => 1000,
253 | 'jitter' => 500,
254 | ],
255 | );
256 | $proxy->setEnabled(false);
257 | $toxiproxy->update($proxy);
258 |
259 | // resetting all proxies
260 | $toxiproxy->reset();
261 |
262 | // checking that this proxy is now re-enabled
263 | $proxy = $toxiproxy->get($proxy->getName());
264 | self::assertNotNull($proxy);
265 | self::assertTrue($proxy->isEnabled());
266 |
267 | // checking that this proxy has no toxics
268 | $toxics = $proxy->getAll();
269 | self::assertEmpty($toxics);
270 | }
271 |
272 | public function testResetUnexpectedStatus(): void
273 | {
274 | $toxiproxy = $this->createToxiproxy(
275 | [
276 | self::httpResponseFactory(418, ''),
277 | ],
278 | );
279 |
280 | $this->expectException(UnexpectedStatusCodeException::class);
281 | $toxiproxy->reset();
282 | }
283 |
284 | public function testVersion(): void
285 | {
286 | $toxiproxy = $this->createToxiproxy();
287 |
288 | self::assertNotEmpty($toxiproxy->version());
289 | }
290 |
291 | public function testVersionUnexpectedStatus(): void
292 | {
293 | $toxiproxy = $this->createToxiproxy(
294 | [
295 | self::httpResponseFactory(418, ''),
296 | ],
297 | );
298 |
299 | $this->expectException(UnexpectedStatusCodeException::class);
300 | $toxiproxy->version();
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/src/Proxy.php:
--------------------------------------------------------------------------------
1 |
35 | * }[] $toxicContents
36 | */
37 | public function __construct(Toxiproxy $toxiproxy, string $name, array $toxicContents = [])
38 | {
39 | $this->toxiproxy = $toxiproxy;
40 | $this->name = $name;
41 | $this->toxics = array_map(
42 | function ($toxicContent) {
43 | return $this->contentsToToxic($toxicContent);
44 | },
45 | $toxicContents,
46 | );
47 | $this->enabled = false;
48 | $this->upstream = '';
49 | $this->listen = '';
50 | }
51 |
52 | public function getName(): string
53 | {
54 | return $this->name;
55 | }
56 |
57 | public function setListen(string $listen): self
58 | {
59 | $this->listen = $listen;
60 | return $this;
61 | }
62 |
63 | public function getListen(): string
64 | {
65 | return $this->listen;
66 | }
67 |
68 | public function getUpstream(): string
69 | {
70 | return $this->upstream;
71 | }
72 |
73 | public function setUpstream(string $upstream): self
74 | {
75 | $this->upstream = $upstream;
76 | return $this;
77 | }
78 |
79 | public function isEnabled(): bool
80 | {
81 | return $this->enabled;
82 | }
83 |
84 | public function setEnabled(bool $enabled): self
85 | {
86 | $this->enabled = $enabled;
87 | return $this;
88 | }
89 |
90 | /**
91 | * @return Toxic[]
92 | */
93 | public function getToxics(): array
94 | {
95 | return $this->toxics;
96 | }
97 |
98 | public function jsonSerialize(): array
99 | {
100 | return [
101 | 'name' => $this->name,
102 | 'listen' => $this->listen,
103 | 'upstream' => $this->upstream,
104 | 'enabled' => $this->enabled,
105 | ];
106 | }
107 |
108 | private function responseToToxic(ResponseInterface $response): Toxic
109 | {
110 | /**
111 | * @var array{
112 | * name: string,
113 | * type: string,
114 | * stream: string,
115 | * toxicity: float,
116 | * attributes: array
117 | * } $toxicContents
118 | */
119 | $toxicContents = (array) json_decode((string) $response->getBody(), true);
120 | return $this->contentsToToxic($toxicContents);
121 | }
122 |
123 | /**
124 | * @param array{
125 | * name: string,
126 | * type: string,
127 | * stream: string,
128 | * toxicity: float,
129 | * attributes: array
130 | * } $contents
131 | */
132 | private function contentsToToxic(array $contents): Toxic
133 | {
134 | $toxic = new Toxic(
135 | $this,
136 | $contents['name'],
137 | $contents['type'],
138 | $contents['stream'],
139 | );
140 | $toxic->setToxicity($contents['toxicity'])
141 | ->setAttributes($contents['attributes']);
142 |
143 | return $toxic;
144 | }
145 |
146 | private function getHttpClient(): Client
147 | {
148 | return $this->toxiproxy->getHttpClient();
149 | }
150 |
151 | /**
152 | * @return Toxic[]
153 | */
154 | public function getAll(): array
155 | {
156 | $route = $this->getToxicsRoute($this);
157 | $response = $this->getHttpClient()->request((string) $route['method'], (string) $route['uri']);
158 | switch ($response->getStatusCode()) {
159 | case StatusCodes::OK->value:
160 | /**
161 | * @var array{
162 | * name: string,
163 | * type: string,
164 | * stream: string,
165 | * toxicity: float,
166 | * attributes: array
167 | * }[] $body
168 | */
169 | $body = (array) json_decode((string) $response->getBody(), true);
170 |
171 | return array_map(
172 | function ($contents) {
173 | return $this->contentsToToxic($contents);
174 | },
175 | array_values($body),
176 | );
177 | default:
178 | throw new UnexpectedStatusCodeException(
179 | sprintf(
180 | '%s: %s',
181 | $response->getStatusCode(),
182 | $response->getBody(),
183 | ),
184 | );
185 | }
186 | }
187 |
188 | /**
189 | * @throws ToxicExistsException|UnexpectedStatusCodeException
190 | */
191 | public function create(
192 | string $type,
193 | string $stream,
194 | float $toxicity,
195 | array $attributes,
196 | ?string $name = null,
197 | ): Toxic {
198 | $route = $this->createToxicRoute($this);
199 | $response = $this->getHttpClient()->request(
200 | (string) $route['method'],
201 | (string) $route['uri'],
202 | [
203 | 'body' => json_encode(
204 | [
205 | 'name' => $name,
206 | 'stream' => $stream,
207 | 'type' => $type,
208 | 'toxicity' => $toxicity,
209 | 'attributes' => $attributes,
210 | ],
211 | ),
212 | ],
213 | );
214 | switch ($response->getStatusCode()) {
215 | case StatusCodes::OK->value:
216 | case StatusCodes::NO_CONTENT->value:
217 | return $this->responseToToxic($response);
218 | case StatusCodes::CONFLICT->value:
219 | throw new ToxicExistsException((string) $response->getBody());
220 | default:
221 | throw new UnexpectedStatusCodeException(
222 | sprintf(
223 | '%s: %s',
224 | $response->getStatusCode(),
225 | $response->getBody(),
226 | ),
227 | );
228 | }
229 | }
230 |
231 | /**
232 | * @throws UnexpectedStatusCodeException
233 | */
234 | public function get(string $name): ?Toxic
235 | {
236 | $route = $this->getToxicRoute($this, $name);
237 | $response = $this->getHttpClient()->request((string) $route['method'], (string) $route['uri']);
238 | switch ($response->getStatusCode()) {
239 | case StatusCodes::OK->value:
240 | return $this->responseToToxic($response);
241 | case StatusCodes::NOT_FOUND->value:
242 | return null;
243 | default:
244 | throw new UnexpectedStatusCodeException(
245 | sprintf(
246 | '%s: %s',
247 | $response->getStatusCode(),
248 | $response->getBody(),
249 | ),
250 | );
251 | }
252 | }
253 |
254 | /**
255 | * @throws NotFoundException|UnexpectedStatusCodeException
256 | */
257 | public function update(Toxic $toxic): Toxic
258 | {
259 | $route = $this->updateToxicRoute($this, $toxic);
260 | $response = $this->getHttpClient()->request(
261 | (string) $route['method'],
262 | (string) $route['uri'],
263 | ['body' => json_encode($toxic)],
264 | );
265 | switch ($response->getStatusCode()) {
266 | case StatusCodes::OK->value:
267 | return $this->responseToToxic($response);
268 | case StatusCodes::NOT_FOUND->value:
269 | throw new NotFoundException((string) $response->getBody());
270 | default:
271 | throw new UnexpectedStatusCodeException(
272 | sprintf(
273 | '%s: %s',
274 | $response->getStatusCode(),
275 | $response->getBody(),
276 | ),
277 | );
278 | }
279 | }
280 |
281 | /**
282 | * @throws NotFoundException|UnexpectedStatusCodeException
283 | */
284 | public function delete(Toxic $toxic): void
285 | {
286 | $route = $this->deleteToxicRoute($this, $toxic);
287 | $response = $this->getHttpClient()->request((string) $route['method'], (string) $route['uri']);
288 | switch ($response->getStatusCode()) {
289 | case StatusCodes::NO_CONTENT->value:
290 | return;
291 | case StatusCodes::NOT_FOUND->value:
292 | throw new NotFoundException((string) $response->getBody());
293 | default:
294 | throw new UnexpectedStatusCodeException(
295 | sprintf(
296 | '%s: %s',
297 | $response->getStatusCode(),
298 | $response->getBody(),
299 | ),
300 | );
301 | }
302 | }
303 |
304 | public function getListenIp(): string
305 | {
306 | $ip = implode(':', explode(':', $this->listen, -1));
307 | if (str_starts_with($ip, '[')) {
308 | $ip = substr($ip, 1, -1);
309 | }
310 | return $ip;
311 | }
312 |
313 | public function getListenPort(): string
314 | {
315 | $ip = $this->getListenIp();
316 | $start = str_starts_with($this->listen, '[') ? 3 : 1;
317 | return substr($this->listen, $start + strlen($ip));
318 | }
319 | }
320 |
--------------------------------------------------------------------------------
/src/Toxiproxy.php:
--------------------------------------------------------------------------------
1 | httpClient = new Client(
24 | [
25 | 'base_uri' => $baseUrl,
26 | 'http_errors' => false,
27 | ],
28 | );
29 | }
30 |
31 | public function getHttpClient(): Client
32 | {
33 | return $this->httpClient;
34 | }
35 |
36 | public function setHttpClient(Client $client): self
37 | {
38 | $this->httpClient = $client;
39 | return $this;
40 | }
41 |
42 | private function responseToProxy(ResponseInterface $response): Proxy
43 | {
44 | /**
45 | * @var array{
46 | * name: string,
47 | * enabled: bool,
48 | * upstream: string,
49 | * listen: string,
50 | * toxics: array{
51 | * name: string,
52 | * type: string,
53 | * stream: string,
54 | * toxicity: float,
55 | * attributes: array
56 | * }[]
57 | * } $responseBody
58 | */
59 | $responseBody = (array) json_decode((string) $response->getBody(), true);
60 | return $this->contentsToProxy($responseBody);
61 | }
62 |
63 | /**
64 | * @param array{
65 | * name: string,
66 | * enabled: bool,
67 | * upstream: string,
68 | * listen: string,
69 | * toxics: array{
70 | * name: string,
71 | * type: string,
72 | * stream: string,
73 | * toxicity: float,
74 | * attributes: array
75 | * }[]
76 | * } $contents
77 | */
78 | private function contentsToProxy(array $contents): Proxy
79 | {
80 | $proxy = new Proxy($this, $contents['name'], $contents['toxics']);
81 | $proxy->setEnabled($contents['enabled'])
82 | ->setUpstream($contents['upstream'])
83 | ->setListen($contents['listen']);
84 |
85 | return $proxy;
86 | }
87 |
88 | /**
89 | * @throws ProxyExistsException|UnexpectedStatusCodeException
90 | */
91 | public function create(string $name, string $upstream, ?string $listen = null): Proxy
92 | {
93 | $route = $this->createProxyRoute();
94 | $response = $this->httpClient->request(
95 | $route['method'],
96 | $route['uri'],
97 | [
98 | 'body' => json_encode(['name' => $name, 'upstream' => $upstream, 'listen' => $listen]),
99 | ],
100 | );
101 | switch ($response->getStatusCode()) {
102 | case StatusCodes::CREATED->value:
103 | return $this->responseToProxy($response);
104 | case StatusCodes::CONFLICT->value:
105 | throw new ProxyExistsException((string) $response->getBody());
106 | default:
107 | throw new UnexpectedStatusCodeException(
108 | sprintf(
109 | '%s: %s',
110 | $response->getStatusCode(),
111 | $response->getBody(),
112 | ),
113 | );
114 | }
115 | }
116 |
117 | /**
118 | * @return Proxy[]
119 | * @throws InvalidProxyException|UnexpectedStatusCodeException
120 | */
121 | public function populate(array $proxyBodies): array
122 | {
123 | $route = $this->populateRoute();
124 | $response = $this->httpClient->request(
125 | $route['method'],
126 | $route['uri'],
127 | [
128 | 'body' => json_encode($proxyBodies),
129 | ],
130 | );
131 | switch ($response->getStatusCode()) {
132 | case StatusCodes::CREATED->value:
133 | /**
134 | * @var array{
135 | * proxies: array{
136 | * name: string,
137 | * enabled: bool,
138 | * upstream: string,
139 | * listen: string,
140 | * toxics: array{
141 | * name: string,
142 | * type: string,
143 | * stream: string,
144 | * toxicity: float,
145 | * attributes: array
146 | * }[]
147 | * }[]
148 | * } $contents
149 | */
150 | $contents = json_decode((string) $response->getBody(), true);
151 |
152 | return array_map(
153 | fn(array $contents) => $this->contentsToProxy($contents),
154 | $contents['proxies'],
155 | );
156 | case StatusCodes::BAD_REQUEST->value:
157 | throw new InvalidProxyException((string) $response->getBody());
158 | default:
159 | throw new UnexpectedStatusCodeException(
160 | sprintf(
161 | '%s: %s',
162 | $response->getStatusCode(),
163 | $response->getBody(),
164 | ),
165 | );
166 | }
167 | }
168 |
169 | /**
170 | * @throws UnexpectedStatusCodeException
171 | */
172 | public function get(string $name): ?Proxy
173 | {
174 | $route = $this->getProxyRoute($name);
175 | $response = $this->httpClient->request((string) $route['method'], (string) $route['uri']);
176 | switch ($response->getStatusCode()) {
177 | case StatusCodes::OK->value:
178 | return $this->responseToProxy($response);
179 | case StatusCodes::NOT_FOUND->value:
180 | return null;
181 | default:
182 | throw new UnexpectedStatusCodeException(
183 | sprintf(
184 | '%s: %s',
185 | $response->getStatusCode(),
186 | $response->getBody(),
187 | ),
188 | );
189 | }
190 | }
191 |
192 | /**
193 | * @return Proxy[]
194 | * @throws UnexpectedStatusCodeException
195 | */
196 | public function getAll(): array
197 | {
198 | $route = $this->getProxiesRoute();
199 | $response = $this->httpClient->request($route['method'], $route['uri']);
200 | switch ($response->getStatusCode()) {
201 | case StatusCodes::OK->value:
202 | /**
203 | * @var array{
204 | * name: string,
205 | * enabled: bool,
206 | * upstream: string,
207 | * listen: string,
208 | * toxics: array{
209 | * name: string,
210 | * type: string,
211 | * stream: string,
212 | * toxicity: float,
213 | * attributes: array
214 | * }[]
215 | * }[] $body
216 | */
217 | $body = (array) json_decode((string) $response->getBody(), true);
218 |
219 | return array_map(
220 | fn(array $contents) => $this->contentsToProxy($contents),
221 | array_values($body),
222 | );
223 | default:
224 | throw new UnexpectedStatusCodeException(
225 | sprintf(
226 | '%s: %s',
227 | $response->getStatusCode(),
228 | $response->getBody(),
229 | ),
230 | );
231 | }
232 | }
233 |
234 | /**
235 | * @throws NotFoundException|UnexpectedStatusCodeException
236 | */
237 | public function delete(Proxy $proxy): void
238 | {
239 | $route = $this->deleteProxyRoute($proxy);
240 | $response = $this->httpClient->request((string) $route['method'], (string) $route['uri']);
241 | switch ($response->getStatusCode()) {
242 | case StatusCodes::NO_CONTENT->value:
243 | return;
244 | case StatusCodes::NOT_FOUND->value:
245 | throw new NotFoundException((string) $response->getBody());
246 | default:
247 | throw new UnexpectedStatusCodeException(
248 | sprintf(
249 | '%s: %s',
250 | $response->getStatusCode(),
251 | $response->getBody(),
252 | ),
253 | );
254 | }
255 | }
256 |
257 | /**
258 | * @throws NotFoundException|UnexpectedStatusCodeException
259 | */
260 | public function update(Proxy $proxy): Proxy
261 | {
262 | $route = $this->updateProxyRoute($proxy);
263 | $response = $this->httpClient->request(
264 | (string) $route['method'],
265 | (string) $route['uri'],
266 | [
267 | 'body' => json_encode($proxy),
268 | ],
269 | );
270 | switch ($response->getStatusCode()) {
271 | case StatusCodes::OK->value:
272 | return $this->responseToProxy($response);
273 | case StatusCodes::NOT_FOUND->value:
274 | throw new NotFoundException((string) $response->getBody());
275 | default:
276 | throw new UnexpectedStatusCodeException(
277 | sprintf(
278 | '%s: %s',
279 | $response->getStatusCode(),
280 | $response->getBody(),
281 | ),
282 | );
283 | }
284 | }
285 |
286 | /**
287 | * @throws UnexpectedStatusCodeException
288 | */
289 | public function reset(): void
290 | {
291 | $route = $this->resetRoute();
292 | $response = $this->httpClient->request($route['method'], $route['uri']);
293 | switch ($response->getStatusCode()) {
294 | case StatusCodes::NO_CONTENT->value:
295 | return;
296 | default:
297 | throw new UnexpectedStatusCodeException(
298 | sprintf('Unexpected status code: %s', $response->getStatusCode()),
299 | );
300 | }
301 | }
302 |
303 | /**
304 | * @throws UnexpectedStatusCodeException
305 | */
306 | public function version(): string
307 | {
308 | $route = $this->versionRoute();
309 | $response = $this->httpClient->request($route['method'], $route['uri']);
310 | switch ($response->getStatusCode()) {
311 | case StatusCodes::OK->value:
312 | return (string) $response->getBody();
313 | default:
314 | throw new UnexpectedStatusCodeException(
315 | sprintf(
316 | '%s: %s',
317 | $response->getStatusCode(),
318 | $response->getBody(),
319 | ),
320 | );
321 | }
322 | }
323 | }
324 |
--------------------------------------------------------------------------------