Skip to content

Commit 14a6308

Browse files
Merge branch '4.4' into 5.0
* 4.4: (26 commits) [HttpClient] NativeHttpClient should not send >1.1 protocol version [HttpClient] fix support for non-blocking resource streams [Mailer] Make sure you can pass custom headers to Mailgun [Mailer] Remove line breaks in email attachment content Update links to documentation [Validator] Add the missing translations for the Arabic (ar) locale ensure to expect no validation for the right reasons [Security-Guard] fixed 35203 missing name tag in param docblock [HttpClient] fix casting responses to PHP streams [PhpUnitBridge] Add test case for @expectedDeprecation annotation [PhpUnitBridge][SymfonyTestsListenerTrait] Remove $testsWithWarnings stack [Mailer] Fix addresses management in Sendgrid API payload [Mailer][MailchimpBridge] Fix missing attachments when sending via Mandrill API [Mailer][MailchimpBridge] Fix incorrect sender address when sender has name [HttpClient] fix capturing SSL certificates with NativeHttpClient Update year in license files [TwigBridge][Form] Added missing help messages in form themes Update year in license files Update year in license files fix version when "anonymous: lazy" was introduced ...
2 parents 9d8c013 + 6407c83 commit 14a6308

File tree

8 files changed

+65
-21
lines changed

8 files changed

+65
-21
lines changed

CurlHttpClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ public function request(string $method, string $url, array $options = []): Respo
288288
$pushedResponse = $pushedResponse->response;
289289
$pushedResponse->__construct($this->multi, $url, $options, $this->logger);
290290
} else {
291-
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s".', $url));
291+
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url));
292292
$pushedResponse = null;
293293
}
294294
}
@@ -412,7 +412,7 @@ private static function acceptPushForRequest(string $method, array $options, Pus
412412
return false;
413413
}
414414

415-
foreach (['proxy', 'no_proxy', 'bindto'] as $k) {
415+
foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) {
416416
if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
417417
return false;
418418
}

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2018-2019 Fabien Potencier
1+
Copyright (c) 2018-2020 Fabien Potencier
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal

NativeHttpClient.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public function request(string $method, string $url, array $options = []): Respo
169169
$this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache;
170170
}
171171

172-
$this->logger && $this->logger->info(sprintf('Request: %s %s', $method, implode('', $url)));
172+
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
173173

174174
[$host, $port, $url['authority']] = self::dnsResolve($url, $this->multi, $info, $onProgress);
175175

@@ -187,7 +187,7 @@ public function request(string $method, string $url, array $options = []): Respo
187187

188188
$context = [
189189
'http' => [
190-
'protocol_version' => $options['http_version'] ?: '1.1',
190+
'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'),
191191
'method' => $method,
192192
'content' => $options['body'],
193193
'ignore_errors' => true,
@@ -357,7 +357,7 @@ private static function createRedirectResolver(array $options, string $host, ?ar
357357
});
358358

359359
if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
360-
$redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
360+
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
361361
return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
362362
});
363363
}

Response/NativeResponse.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,13 @@ private function open(): void
162162
restore_error_handler();
163163
}
164164

165-
stream_set_blocking($h, false);
166-
$this->context = $this->resolveRedirect = null;
167-
168-
if (isset($context['ssl']['peer_certificate_chain'])) {
165+
if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) {
169166
$this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain'];
170167
}
171168

169+
stream_set_blocking($h, false);
170+
$this->context = $this->resolveRedirect = null;
171+
172172
// Create dechunk and inflate buffers
173173
if (isset($this->headers['content-length'])) {
174174
$this->remaining = (int) $this->headers['content-length'][0];

Response/ResponseTrait.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,11 @@ public function toStream(bool $throw = true)
204204
$this->getHeaders($throw);
205205
}
206206

207-
return StreamWrapper::createResource($this, null, $this->content, $this->handle && 'stream' === get_resource_type($this->handle) ? $this->handle : null);
207+
$stream = StreamWrapper::createResource($this);
208+
stream_get_meta_data($stream)['wrapper_data']
209+
->bindHandles($this->handle, $this->content);
210+
211+
return $stream;
208212
}
209213

210214
/**

Response/StreamWrapper.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,17 @@ class StreamWrapper
3737
/** @var resource|null */
3838
private $handle;
3939

40+
private $blocking = true;
41+
private $timeout;
4042
private $eof = false;
4143
private $offset = 0;
4244

4345
/**
4446
* Creates a PHP stream resource from a ResponseInterface.
4547
*
46-
* @param resource|null $contentBuffer The seekable resource where the response body is buffered
47-
* @param resource|null $selectHandle The resource handle that should be monitored when
48-
* stream_select() is used on the created stream
49-
*
5048
* @return resource
5149
*/
52-
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null, $contentBuffer = null, $selectHandle = null)
50+
public static function createResource(ResponseInterface $response, HttpClientInterface $client = null)
5351
{
5452
if (null === $client && !method_exists($response, 'stream')) {
5553
throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__));
@@ -63,8 +61,6 @@ public static function createResource(ResponseInterface $response, HttpClientInt
6361
$context = [
6462
'client' => $client ?? $response,
6563
'response' => $response,
66-
'content' => $contentBuffer,
67-
'handle' => $selectHandle,
6864
];
6965

7066
return fopen('symfony://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context])) ?: null;
@@ -78,6 +74,17 @@ public function getResponse(): ResponseInterface
7874
return $this->response;
7975
}
8076

77+
/**
78+
* @param resource|null $handle The resource handle that should be monitored when
79+
* stream_select() is used on the created stream
80+
* @param resource|null $content The seekable resource where the response body is buffered
81+
*/
82+
public function bindHandles(&$handle, &$content): void
83+
{
84+
$this->handle = &$handle;
85+
$this->content = &$content;
86+
}
87+
8188
public function stream_open(string $path, string $mode, int $options): bool
8289
{
8390
if ('r' !== $mode) {
@@ -91,8 +98,6 @@ public function stream_open(string $path, string $mode, int $options): bool
9198
$context = stream_context_get_options($this->context)['symfony'] ?? null;
9299
$this->client = $context['client'] ?? null;
93100
$this->response = $context['response'] ?? null;
94-
$this->content = $context['content'] ?? null;
95-
$this->handle = $context['handle'] ?? null;
96101
$this->context = null;
97102

98103
if (null !== $this->client && null !== $this->response) {
@@ -147,7 +152,7 @@ public function stream_read(int $count)
147152
return $data;
148153
}
149154

150-
foreach ($this->client->stream([$this->response]) as $chunk) {
155+
foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) {
151156
try {
152157
$this->eof = true;
153158
$this->eof = !$chunk->isTimeout();
@@ -178,6 +183,19 @@ public function stream_read(int $count)
178183
return '';
179184
}
180185

186+
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
187+
{
188+
if (STREAM_OPTION_BLOCKING === $option) {
189+
$this->blocking = (bool) $arg1;
190+
} elseif (STREAM_OPTION_READ_TIMEOUT === $option) {
191+
$this->timeout = $arg1 + $arg2 / 1e6;
192+
} else {
193+
return false;
194+
}
195+
196+
return true;
197+
}
198+
181199
public function stream_tell(): int
182200
{
183201
return $this->offset;
@@ -238,6 +256,8 @@ public function stream_seek(int $offset, int $whence = SEEK_SET): bool
238256
public function stream_cast(int $castAs)
239257
{
240258
if (STREAM_CAST_FOR_SELECT === $castAs) {
259+
$this->response->getHeaders(false);
260+
241261
return $this->handle ?? false;
242262
}
243263

Tests/HttpClientTestCase.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,20 @@ public function testToStream404()
7575
$response = $client->request('GET', 'http://localhost:8057/404');
7676
$stream = $response->toStream();
7777
}
78+
79+
public function testNonBlockingStream()
80+
{
81+
$client = $this->getHttpClient(__FUNCTION__);
82+
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
83+
$stream = $response->toStream();
84+
85+
$this->assertTrue(stream_set_blocking($stream, false));
86+
$this->assertSame('<1>', fread($stream, 8192));
87+
$this->assertFalse(feof($stream));
88+
89+
$this->assertTrue(stream_set_blocking($stream, true));
90+
$this->assertSame('<2>', fread($stream, 8192));
91+
$this->assertSame('', fread($stream, 8192));
92+
$this->assertTrue(feof($stream));
93+
}
7894
}

Tests/MockHttpClientTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ protected function getHttpClient(string $testCase): HttpClientInterface
171171

172172
return $client;
173173

174+
case 'testNonBlockingStream':
175+
$responses[] = new MockResponse((function () { yield '<1>'; yield ''; yield '<2>'; })(), ['response_headers' => $headers]);
176+
break;
177+
174178
case 'testMaxDuration':
175179
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
176180
$mock->expects($this->any())

0 commit comments

Comments
 (0)