Skip to content

Commit a730dfc

Browse files
[HttpClient] add AsyncDecoratorTrait to ease processing responses without breaking async
1 parent f9411ab commit a730dfc

19 files changed

+976
-202
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
@@ -111,8 +111,12 @@ public function getError(): ?string
111111
/**
112112
* @return bool Whether the wrapped error has been thrown or not
113113
*/
114-
public function didThrow(): bool
114+
public function didThrow(bool $didThrow = null): bool
115115
{
116+
if (null !== $didThrow && $this->didThrow !== $didThrow) {
117+
return !$this->didThrow = $didThrow;
118+
}
119+
116120
return $this->didThrow;
117121
}
118122

src/Symfony/Component/HttpClient/Internal/HttplugWaitLoop.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Psr\Http\Message\ResponseFactoryInterface;
1616
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
1717
use Psr\Http\Message\StreamFactoryInterface;
18-
use Symfony\Component\HttpClient\Response\ResponseTrait;
18+
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
1919
use Symfony\Component\HttpClient\Response\StreamWrapper;
2020
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
2121
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -119,7 +119,7 @@ public function createPsr7Response(ResponseInterface $response, bool $buffer = f
119119
}
120120
}
121121

122-
if (isset(class_uses($response)[ResponseTrait::class])) {
122+
if (isset(class_uses($response)[CommonResponseTrait::class])) {
123123
$body = $this->streamFactory->createStreamFromResource($response->toStream(false));
124124
} elseif (!$buffer) {
125125
$body = $this->streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $this->client));

src/Symfony/Component/HttpClient/Psr18Client.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
use Psr\Http\Message\StreamInterface;
2828
use Psr\Http\Message\UriFactoryInterface;
2929
use Psr\Http\Message\UriInterface;
30-
use Symfony\Component\HttpClient\Response\ResponseTrait;
30+
use Symfony\Component\HttpClient\Response\CommonResponseTrait;
3131
use Symfony\Component\HttpClient\Response\StreamWrapper;
3232
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
3333
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -104,7 +104,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface
104104
}
105105
}
106106

107-
$body = isset(class_uses($response)[ResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
107+
$body = isset(class_uses($response)[CommonResponseTrait::class]) ? $response->toStream(false) : StreamWrapper::createResource($response, $this->client);
108108
$body = $this->streamFactory->createStreamFromResource($body);
109109

110110
if ($body->isSeekable()) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
*/
3636
final class AmpResponse implements ResponseInterface
3737
{
38-
use ResponseTrait;
38+
use CommonResponseTrait;
39+
use TransportResponseTrait;
3940

4041
private $multi;
4142
private $options;
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