Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ protected static function parse_response($headers, $url, $req_headers, $req_data
// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
$headers = preg_replace('/\n[ \t]/', ' ', $headers);
$headers = explode("\n", $headers);
preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
preg_match('#^HTTP/(1\.\d|2|3)[ \t]+(\d+)#i', array_shift($headers), $matches);
if (empty($matches)) {
throw new Exception('Response could not be parsed', 'noversion', $headers);
}
Expand Down
21 changes: 15 additions & 6 deletions src/Transport/Curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,11 @@ private function setup_handle($url, $headers, $data, $options) {
* add as much as a second to the time it takes for cURL to perform a request. To
* prevent this, we need to set an empty "Expect" header. To match the behaviour of
* Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
* HTTP/1.1.
* a protocol version newer than HTTP/1.0.
*
* https://curl.se/mail/lib-2017-07/0013.html
*/
if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
if (!isset($headers['Expect']) && $options['protocol_version'] > 1.0) {
$headers['Expect'] = $this->get_expect_header($data);
}

Expand Down Expand Up @@ -465,10 +465,19 @@ private function setup_handle($url, $headers, $data, $options) {
curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
}

if ($options['protocol_version'] === 1.1) {
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
} else {
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
switch ($options['protocol_version']) {
case 3.0:
// The CURL_HTTP_VERSION_3 constant is only available from PHP 8.4
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, 30);
break;
case 2.0:
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
break;
case 1.1:
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
break;
default:
curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
}

if ($options['blocking'] === true) {
Expand Down
23 changes: 20 additions & 3 deletions tests/Requests/RequestsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,35 @@ public function testHeaderParsing() {
}
}

public function testProtocolVersionParsing() {
/**
* Data Provider.
*
* @return array
*/
public function dataProtocolVersion() {
return [
'HTTP/1.0' => ['1.0', 1.0],
'HTTP/1.1' => ['1.1', 1.1],
'HTTP/2' => ['2', 2.0],
'HTTP/3' => ['3', 3.0],
];
}

/**
* @dataProvider dataProtocolVersion
*/
public function testProtocolVersionParsing($version, $expected) {
$transport = new RawTransportMock();
$transport->data =
"HTTP/1.0 200 OK\r\n" .
"HTTP/$version 200 OK\r\n" .
"Host: localhost\r\n\r\n";

$options = [
'transport' => $transport,
];

$response = Requests::get('http://example.com/', [], $options);
$this->assertSame(1.0, $response->protocol_version);
$this->assertSame($expected, $response->protocol_version);
}

public function testRawAccess() {
Expand Down
34 changes: 32 additions & 2 deletions tests/Transport/Curl/CurlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public function testDoesntOverwriteExpectHeaderIfManuallySet() {
/**
* @small
*/
public function testDoesntSetExpectHeaderIfBodyExactly1MbButProtocolIsnt11() {
public function testDoesNotSetEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
Expand All @@ -68,7 +68,37 @@ public function testDoesntSetExpectHeaderIfBodyExactly1MbButProtocolIsnt11() {
/**
* @small
*/
public function testSetsEmptyExpectHeaderWithDefaultSettings() {
public function testSetsEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs20() {
$this->markTestSkipped('HTTP/2 send fails with: cURL error 55: Send failure: Broken pipe');
$options = [
'protocol_version' => 2.0,
];
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions($options));

$result = json_decode($request->body, true);

$this->assertSame($result['headers']['Expect'], '');
}

/**
* @small
*/
public function testSetsEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs30() {
$this->markTestSkipped('HTTP/3 connection times out');
$options = [
'protocol_version' => 3.0,
];
$request = Requests::post($this->httpbin('/post', true), [], str_repeat('x', 1048576), $this->getOptions($options));

$result = json_decode($request->body, true);

$this->assertSame($result['headers']['Expect'], '');
}

/**
* @small
*/
public function testDoesNotSetEmptyExpectHeaderWithDefaultSettings() {
$request = Requests::post($this->httpbin('/post'), [], [], $this->getOptions());

$result = json_decode($request->body, true);
Expand Down
Loading