Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for HTTP2 and HTTP3 #918

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
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