Skip to content

Commit 5b70e5f

Browse files
[HttpClient] add AsyncDecoratorTrait to ease processing responses without breaking async
1 parent 2ed6a0d commit 5b70e5f

12 files changed

+920
-178
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use Symfony\Component\HttpClient\Response\AsyncResponse;
15+
use Symfony\Component\HttpClient\Response\ResponseStream;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
19+
20+
/**
21+
* Eases with processing responses while streaming them.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
trait AsyncDecoratorTrait
26+
{
27+
private $client;
28+
29+
public function __construct(HttpClientInterface $client = null)
30+
{
31+
$this->client = $client ?? HttpClient::create();
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*
37+
* @return AsyncResponse
38+
*/
39+
abstract public function request(string $method, string $url, array $options = []): ResponseInterface;
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function stream($responses, float $timeout = null): ResponseStreamInterface
45+
{
46+
if ($responses instanceof AsyncResponse) {
47+
$responses = [$responses];
48+
} elseif (!is_iterable($responses)) {
49+
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
50+
}
51+
52+
return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class));
53+
}
54+
}

src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,12 @@ public function getError(): ?string
110110
/**
111111
* @return bool Whether the wrapped error has been thrown or not
112112
*/
113-
public function didThrow(): bool
113+
public function didThrow(bool $didThrow = null): bool
114114
{
115+
if (null !== $didThrow && $this->didThrow !== $didThrow) {
116+
return !$this->didThrow = $didThrow;
117+
}
118+
115119
return $this->didThrow;
116120
}
117121

src/Symfony/Component/HttpClient/Response/AmpResponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*/
3535
final class AmpResponse implements ResponseInterface
3636
{
37+
use CommonResponseTrait;
3738
use ResponseTrait;
3839

3940
private $multi;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Response;
13+
14+
use Symfony\Component\HttpClient\Chunk\DataChunk;
15+
use Symfony\Component\HttpClient\Chunk\LastChunk;
16+
use Symfony\Contracts\HttpClient\ChunkInterface;
17+
use Symfony\Contracts\HttpClient\HttpClientInterface;
18+
use Symfony\Contracts\HttpClient\ResponseInterface;
19+
20+
/**
21+
* A DTO to work with AsyncResponse.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
final class AsyncContext
26+
{
27+
private $passthru;
28+
private $client;
29+
private $response;
30+
private $info = [];
31+
private $content;
32+
private $offset;
33+
34+
public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset)
35+
{
36+
$this->passthru = &$passthru;
37+
$this->client = $client;
38+
$this->response = &$response;
39+
$this->info = &$info;
40+
$this->content = $content;
41+
$this->offset = $offset;
42+
}
43+
44+
/**
45+
* Returns the HTTP status without consuming the response.
46+
*/
47+
public function getStatusCode(): int
48+
{
49+
return $this->response->getInfo('http_code');
50+
}
51+
52+
/**
53+
* Returns the headers without consuming the response.
54+
*/
55+
public function getHeaders(): array
56+
{
57+
$headers = [];
58+
59+
foreach ($this->response->getInfo('response_headers') as $h) {
60+
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) {
61+
$headers = [];
62+
} elseif (2 === \count($m = explode(':', $h, 2))) {
63+
$headers[strtolower($m[0])][] = ltrim($m[1]);
64+
}
65+
}
66+
67+
return $headers;
68+
}
69+
70+
/**
71+
* @return resource|null The PHP stream resource where the content is buffered, if it is
72+
*/
73+
public function getContent()
74+
{
75+
return $this->content;
76+
}
77+
78+
/**
79+
* Creates a new chunk of content.
80+
*/
81+
public function createChunk(string $data): ChunkInterface
82+
{
83+
return new DataChunk($this->offset, $data);
84+
}
85+
86+
/**
87+
* Cancels the request and returns the last chunk to yield.
88+
*/
89+
public function cancel(): ChunkInterface
90+
{
91+
$this->info['canceled'] = true;
92+
$this->info['error'] = 'Response has been canceled.';
93+
$this->response->cancel();
94+
95+
return new LastChunk();
96+
}
97+
98+
/**
99+
* Returns the current info of the response.
100+
*/
101+
public function getInfo(string $type = null)
102+
{
103+
if (null !== $type) {
104+
return $this->info[$type] ?? $this->response->getInfo($type);
105+
}
106+
107+
return $this->info + $this->response->getInfo();
108+
}
109+
110+
/**
111+
* Attaches an info to the response.
112+
*/
113+
public function setInfo(string $type, $value): self
114+
{
115+
if ('canceled' === $type && $value !== $this->info['canceled']) {
116+
throw new \LogicException('You cannot set the "canceled" info directly.');
117+
}
118+
119+
if (null === $value) {
120+
unset($this->info[$type]);
121+
} else {
122+
$this->info[$type] = $value;
123+
}
124+
125+
return $this;
126+
}
127+
128+
/**
129+
* Returns the currently processed response.
130+
*/
131+
public function getResponse(): ResponseInterface
132+
{
133+
return $this->response;
134+
}
135+
136+
/**
137+
* Replaces the currently processed response by doing a new request.
138+
*/
139+
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
140+
{
141+
$this->info['previous_info'][] = $this->response->getInfo();
142+
143+
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
144+
}
145+
146+
/**
147+
* Replaces the currently processed response by another one.
148+
*/
149+
public function replaceResponse(ResponseInterface $response): ResponseInterface
150+
{
151+
$this->info['previous_info'][] = $this->response->getInfo();
152+
153+
return $this->response = $response;
154+
}
155+
156+
/**
157+
* Replaces or removes the chunk filter iterator.
158+
*/
159+
public function passthru(callable $passthru = null): void
160+
{
161+
$this->passthru = $passthru;
162+
}
163+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy