Skip to content

Commit 4f9b313

Browse files
Merge branch '7.2' into 7.3
* 7.2: [Process] Fix process status tracking Prevent empty request body stream in HttplugClient and Psr18Client [Lock] Fix Predis error handling [HttpClient] Fix buffering AsyncResponse with no passthru [TypeInfo] Fix promoted property phpdoc reading [HttpClient] Fix retrying requests with Psr18Client and NTLM connections [HttpClient] Fix uploading files > 2GB [Mime] use isRendered method to avoid rendering an email twice
2 parents 818c5c2 + d281c96 commit 4f9b313

File tree

5 files changed

+90
-34
lines changed

5 files changed

+90
-34
lines changed

CurlHttpClient.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,12 @@ public function request(string $method, string $url, array $options = []): Respo
238238
}
239239

240240
if (!\is_string($body)) {
241+
if (isset($options['auth_ntlm'])) {
242+
$curlopts[\CURLOPT_FORBID_REUSE] = true; // Reusing NTLM connections requires seeking capability, which only string bodies support
243+
}
244+
241245
if (\is_resource($body)) {
242-
$curlopts[\CURLOPT_INFILE] = $body;
246+
$curlopts[\CURLOPT_READDATA] = $body;
243247
} else {
244248
$curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body) {
245249
static $eof = false;
@@ -318,6 +322,9 @@ public function request(string $method, string $url, array $options = []): Respo
318322
}
319323

320324
foreach ($curlopts as $opt => $value) {
325+
if (\CURLOPT_INFILESIZE === $opt && $value >= 1 << 31) {
326+
$opt = 115; // 115 === CURLOPT_INFILESIZE_LARGE, but it's not defined in PHP
327+
}
321328
if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) {
322329
$constantName = $this->findConstantName($opt);
323330
throw new TransportException(\sprintf('Curl option "%s" is not supported.', $constantName ?? $opt));
@@ -474,7 +481,7 @@ private function validateExtraCurlOptions(array $options): void
474481
\CURLOPT_RESOLVE => 'resolve',
475482
\CURLOPT_NOSIGNAL => 'timeout',
476483
\CURLOPT_HTTPHEADER => 'headers',
477-
\CURLOPT_INFILE => 'body',
484+
\CURLOPT_READDATA => 'body',
478485
\CURLOPT_READFUNCTION => 'body',
479486
\CURLOPT_INFILESIZE => 'body',
480487
\CURLOPT_POSTFIELDS => 'body',

HttplugClient.php

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,23 +224,44 @@ private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null
224224
{
225225
try {
226226
$body = $request->getBody();
227+
$headers = $request->getHeaders();
227228

228-
if ($body->isSeekable()) {
229-
try {
230-
$body->seek(0);
231-
} catch (\RuntimeException) {
232-
// ignore
233-
}
229+
$size = $request->getHeader('content-length')[0] ?? -1;
230+
if (0 > $size && 0 < $size = $body->getSize() ?? -1) {
231+
$headers['Content-Length'] = [$size];
234232
}
235233

236-
$headers = $request->getHeaders();
237-
if (!$request->hasHeader('content-length') && 0 <= $size = $body->getSize() ?? -1) {
238-
$headers['Content-Length'] = [$size];
234+
if (0 === $size) {
235+
$body = '';
236+
} elseif (0 < $size && $size < 1 << 21) {
237+
if ($body->isSeekable()) {
238+
try {
239+
$body->seek(0);
240+
} catch (\RuntimeException) {
241+
// ignore
242+
}
243+
}
244+
245+
$body = $body->getContents();
246+
} else {
247+
$body = static function (int $size) use ($body) {
248+
if ($body->isSeekable()) {
249+
try {
250+
$body->seek(0);
251+
} catch (\RuntimeException) {
252+
// ignore
253+
}
254+
}
255+
256+
while (!$body->eof()) {
257+
yield $body->read($size);
258+
}
259+
};
239260
}
240261

241262
$options = [
242263
'headers' => $headers,
243-
'body' => static fn (int $size) => $body->read($size),
264+
'body' => $body,
244265
'buffer' => $buffer,
245266
];
246267

Psr18Client.php

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,44 @@ public function sendRequest(RequestInterface $request): ResponseInterface
8888
{
8989
try {
9090
$body = $request->getBody();
91+
$headers = $request->getHeaders();
9192

92-
if ($body->isSeekable()) {
93-
try {
94-
$body->seek(0);
95-
} catch (\RuntimeException) {
96-
// ignore
97-
}
93+
$size = $request->getHeader('content-length')[0] ?? -1;
94+
if (0 > $size && 0 < $size = $body->getSize() ?? -1) {
95+
$headers['Content-Length'] = [$size];
9896
}
9997

100-
$headers = $request->getHeaders();
101-
if (!$request->hasHeader('content-length') && 0 <= $size = $body->getSize() ?? -1) {
102-
$headers['Content-Length'] = [$size];
98+
if (0 === $size) {
99+
$body = '';
100+
} elseif (0 < $size && $size < 1 << 21) {
101+
if ($body->isSeekable()) {
102+
try {
103+
$body->seek(0);
104+
} catch (\RuntimeException) {
105+
// ignore
106+
}
107+
}
108+
109+
$body = $body->getContents();
110+
} else {
111+
$body = static function (int $size) use ($body) {
112+
if ($body->isSeekable()) {
113+
try {
114+
$body->seek(0);
115+
} catch (\RuntimeException) {
116+
// ignore
117+
}
118+
}
119+
120+
while (!$body->eof()) {
121+
yield $body->read($size);
122+
}
123+
};
103124
}
104125

105126
$options = [
106127
'headers' => $headers,
107-
'body' => static fn (int $size) => $body->read($size),
128+
'body' => $body,
108129
];
109130

110131
if ('1.0' === $request->getProtocolVersion()) {

Response/AsyncResponse.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\HttpClient\Response;
1313

1414
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
15-
use Symfony\Component\HttpClient\Chunk\FirstChunk;
1615
use Symfony\Component\HttpClient\Chunk\LastChunk;
1716
use Symfony\Component\HttpClient\Exception\TransportException;
1817
use Symfony\Contracts\HttpClient\ChunkInterface;
@@ -245,7 +244,7 @@ public static function stream(iterable $responses, ?float $timeout = null, ?stri
245244
$wrappedResponses[] = $r->response;
246245

247246
if ($r->stream) {
248-
yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap);
247+
yield from self::passthruStream($response = $r->response, $r, $asyncMap, new LastChunk());
249248

250249
if (!isset($asyncMap[$response])) {
251250
array_pop($wrappedResponses);
@@ -276,15 +275,9 @@ public static function stream(iterable $responses, ?float $timeout = null, ?stri
276275
}
277276

278277
if (!$r->passthru) {
279-
if (null !== $chunk->getError() || $chunk->isLast()) {
280-
unset($asyncMap[$response]);
281-
} elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
282-
$chunk = new ErrorChunk($r->offset, new TransportException(\sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
283-
$r->info['error'] = $chunk->getError();
284-
$r->response->cancel();
285-
}
278+
$r->stream = (static fn () => yield $chunk)();
279+
yield from self::passthruStream($response, $r, $asyncMap);
286280

287-
yield $r => $chunk;
288281
continue;
289282
}
290283

@@ -347,13 +340,13 @@ private static function passthru(HttpClientInterface $client, self $r, ChunkInte
347340
}
348341
$r->stream = $stream;
349342

350-
yield from self::passthruStream($response, $r, null, $asyncMap);
343+
yield from self::passthruStream($response, $r, $asyncMap);
351344
}
352345

353346
/**
354347
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
355348
*/
356-
private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator
349+
private static function passthruStream(ResponseInterface $response, self $r, ?\SplObjectStorage $asyncMap, ?ChunkInterface $chunk = null): \Generator
357350
{
358351
while (true) {
359352
try {

Tests/AsyncDecoratorTraitTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,20 @@ public function testBufferPurePassthru()
231231

232232
$this->assertStringContainsString('SERVER_PROTOCOL', $response->getContent());
233233
$this->assertStringContainsString('HTTP_HOST', $response->getContent());
234+
235+
$client = new class(parent::getHttpClient(__FUNCTION__)) implements HttpClientInterface {
236+
use AsyncDecoratorTrait;
237+
238+
public function request(string $method, string $url, array $options = []): ResponseInterface
239+
{
240+
return new AsyncResponse($this->client, $method, $url, $options);
241+
}
242+
};
243+
244+
$response = $client->request('GET', 'http://localhost:8057/');
245+
246+
$this->assertStringContainsString('SERVER_PROTOCOL', $response->getContent());
247+
$this->assertStringContainsString('HTTP_HOST', $response->getContent());
234248
}
235249

236250
public function testRetryTimeout()

0 commit comments

Comments
 (0)