Skip to content

Commit

Permalink
Add support for HTTP2 and HTTP3
Browse files Browse the repository at this point in the history
  • Loading branch information
pprkut committed Jun 10, 2024
1 parent dd85454 commit bbedc67
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,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 @@ -374,11 +374,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.
* 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.1) {
$headers['Expect'] = $this->get_expect_header($data);
}

Expand Down Expand Up @@ -446,10 +446,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/RequestsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ public function dataRequestInvalidUrl() {
];
}

/**
* Data Provider.
*
* @return array
*/
public function dataProtocolVerison() {
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],
];
}

/**
* Tests receiving an exception when the request() method received an invalid input type as `$type`.
*
Expand Down Expand Up @@ -287,18 +301,21 @@ public function testHeaderParsing() {
}
}

public function testProtocolVersionParsing() {
/**
* @dataProvider dataProtocolVerison
*/
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
71 changes: 58 additions & 13 deletions tests/Transport/CurlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public function testDoesntOverwriteExpectHeaderIfManuallySet() {
/**
* @small
*/
public function testDoesntSetExpectHeaderIfBodyExactly1MbButProtocolIsnt11() {
public function testDoesNotSetEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs11() {
$options = [
'protocol_version' => 1.0,
'protocol_version' => 1.1,
];
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions($options));

Expand All @@ -69,7 +69,37 @@ public function testDoesntSetExpectHeaderIfBodyExactly1MbButProtocolIsnt11() {
/**
* @small
*/
public function testSetsEmptyExpectHeaderWithDefaultSettings() {
public function testDoesNotSetEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs20() {
$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->assertFalse(isset($result['headers']['Expect']));
}

/**
* @small
*/
public function testDoesNotSetEmptyExpectHeaderIfBodyExactly1MbAndProtocolIs30() {
$options = [
'protocol_version' => 3.0,
'timeout' => 30,
'connect_timeout' => 30

Check failure on line 90 in tests/Transport/CurlTest.php

View workflow job for this annotation

GitHub Actions / PHPCS

Each array item in a multi-line array declaration must end in a comma
];
$request = Requests::post($this->httpbin('/post', true), [], str_repeat('x', 1048576), $this->getOptions($options));

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

$this->assertFalse(isset($result['headers']['Expect']));
}

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

$result = json_decode($request->body, true);
Expand All @@ -80,29 +110,38 @@ public function testSetsEmptyExpectHeaderWithDefaultSettings() {
/**
* @small
*/
public function testSetsEmptyExpectHeaderIfBodyIsANestedArrayLessThan1Mb() {
public function testSetsEmptyExpectHeaderIfBodyIsANestedArrayLessThan1MbAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
$data = [
str_repeat('x', 148576),
[
str_repeat('x', 548576),
],
];
$request = Requests::post($this->httpbin('/post'), [], $data, $this->getOptions());
$request = Requests::post($this->httpbin('/post'), [], $data, $this->getOptions($options));

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

$this->assertFalse(isset($result['headers']['Expect']));
}

public function testSetsExpectHeaderIfBodyIsExactlyA1MbString() {
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions());
public function testSetsExpectHeaderIfBodyIsExactlyA1MbStringAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions($options));

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

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

public function testSetsExpectHeaderIfBodyIsANestedArrayGreaterThan1Mb() {
public function testSetsExpectHeaderIfBodyIsANestedArrayGreaterThan1MbAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
$data = [
str_repeat('x', 148576),
[
Expand All @@ -112,15 +151,18 @@ public function testSetsExpectHeaderIfBodyIsANestedArrayGreaterThan1Mb() {
],
],
];
$request = Requests::post($this->httpbin('/post'), [], $data, $this->getOptions());
$request = Requests::post($this->httpbin('/post'), [], $data, $this->getOptions($options));

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

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

public function testSetsExpectHeaderIfBodyExactly1Mb() {
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions());
public function testSetsExpectHeaderIfBodyExactly1MbAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048576), $this->getOptions($options));

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

Expand All @@ -130,8 +172,11 @@ public function testSetsExpectHeaderIfBodyExactly1Mb() {
/**
* @small
*/
public function testSetsEmptyExpectHeaderIfBodySmallerThan1Mb() {
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048575), $this->getOptions());
public function testSetsEmptyExpectHeaderIfBodySmallerThan1MbAndProtocolIs10() {
$options = [
'protocol_version' => 1.0,
];
$request = Requests::post($this->httpbin('/post'), [], str_repeat('x', 1048575), $this->getOptions($options));

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

Expand Down

0 comments on commit bbedc67

Please sign in to comment.