Skip to content

Commit 33e2614

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

12 files changed

+867
-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+
public abstract 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: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
public function getStatusCode(): int
45+
{
46+
return $this->response->getInfo('http_code');
47+
}
48+
49+
public function getHeaders(): array
50+
{
51+
$headers = [];
52+
53+
foreach ($this->response->getInfo('response_headers') as $h) {
54+
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) {
55+
$headers = [];
56+
} elseif (2 === \count($m = explode(':', $h, 2))) {
57+
$headers[strtolower($m[0])][] = ltrim($m[1]);
58+
}
59+
}
60+
61+
return $headers;
62+
}
63+
64+
/**
65+
* @return resource|null The PHP stream resource where the content is buffered, if it is
66+
*/
67+
public function getContent()
68+
{
69+
return $this->content;
70+
}
71+
72+
public function createChunk(string $data): ChunkInterface
73+
{
74+
return new DataChunk($this->offset, $data);
75+
}
76+
77+
public function cancel(): ChunkInterface
78+
{
79+
$this->info['canceled'] = true;
80+
$this->info['error'] = 'Response has been canceled.';
81+
$this->response->cancel();
82+
83+
return new LastChunk();
84+
}
85+
86+
public function getInfo(string $type = null)
87+
{
88+
if (null !== $type) {
89+
return $this->info[$type] ?? $this->response->getInfo($type);
90+
}
91+
92+
return $this->info + $this->response->getInfo();
93+
}
94+
95+
public function setInfo(string $type, $value): self
96+
{
97+
if (null === $value) {
98+
unset($this->info[$type]);
99+
} else {
100+
$this->info[$type] = $value;
101+
}
102+
103+
return $this;
104+
}
105+
106+
public function getResponse(): ResponseInterface
107+
{
108+
return $this->response;
109+
}
110+
111+
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
112+
{
113+
$this->info['previous_info'][] = $this->response->getInfo();
114+
115+
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
116+
}
117+
118+
public function replaceResponse(ResponseInterface $response): ResponseInterface
119+
{
120+
$this->info['previous_info'][] = $this->response->getInfo();
121+
122+
return $this->response = $response;
123+
}
124+
125+
public function passthru(callable $passthru = null): self
126+
{
127+
$this->passthru = $passthru;
128+
129+
return $this;
130+
}
131+
}

0 commit comments

Comments
 (0)