Browse Source

Improve support for HTTP/2 in Http\Client

Add `protocolVersion` as a constructor option. Previously making HTTP/2
requests was tedious and not exposing this option earlier was an
oversight.

Add support for HTTP2 requiring TLS. This mode is preferred over HTTP2
without TLS as most clients and server implementations require TLS for
HTTP2.

Fixes #13922
Mark Story 6 years ago
parent
commit
28ed0d3d6d

+ 4 - 0
src/Http/Client.php

@@ -114,6 +114,7 @@ class Client
         'ssl_verify_depth' => 5,
         'ssl_verify_host' => true,
         'redirect' => false,
+        'protocolVersion' => '1.1',
     ];
 
     /**
@@ -156,6 +157,7 @@ class Client
      * - adapter - The adapter class name or instance. Defaults to
      *   \Cake\Http\Client\Adapter\Curl if `curl` extension is loaded else
      *   \Cake\Http\Client\Adapter\Stream.
+     * - protocolVersion - The HTTP protocol version to use. Defaults to 1.1
      *
      * @param array $config Config options for scoped clients.
      * @throws \InvalidArgumentException
@@ -518,6 +520,8 @@ class Client
         }
 
         $request = new Request($url, $method, $headers, $data);
+        $request = $request->withProtocolVersion($this->getConfig('protocolVersion'));
+
         $cookies = isset($options['cookies']) ? $options['cookies'] : [];
         /** @var \Cake\Http\Client\Request $request */
         $request = $this->_cookies->addToRequest($request, $cookies);

+ 5 - 1
src/Http/Client/Adapter/Curl.php

@@ -148,11 +148,15 @@ class Curl implements AdapterInterface
                 return CURL_HTTP_VERSION_1_0;
             case '1.1':
                 return CURL_HTTP_VERSION_1_1;
+            case '2':
             case '2.0':
+                if (defined('CURL_HTTP_VERSION_2TLS')) {
+                    return CURL_HTTP_VERSION_2TLS;
+                }
                 if (defined('CURL_HTTP_VERSION_2_0')) {
                     return CURL_HTTP_VERSION_2_0;
                 }
-                throw new HttpException('libcurl 7.33 needed for HTTP 2.0 support');
+                throw new HttpException('libcurl 7.33 or greater required for HTTP/2 support');
         }
 
         return CURL_HTTP_VERSION_NONE;

+ 16 - 0
tests/TestCase/Http/Client/Adapter/CurlTest.php

@@ -309,4 +309,20 @@ class CurlTest extends TestCase
         ];
         $this->assertSame($expected, $result);
     }
+
+    /**
+     * Test converting client options into curl ones.
+     *
+     * @return void
+     */
+    public function testBuildOptionsProtocolVersion()
+    {
+        $this->skipIf(!defined('CURL_HTTP_VERSION_2TLS'), 'Requires libcurl 7.42');
+        $options = [];
+        $request = new Request('http://localhost/things', 'GET');
+        $request = $request->withProtocolVersion('2');
+
+        $result = $this->curl->buildOptions($request, $options);
+        $this->assertSame(CURL_HTTP_VERSION_2TLS, $result[CURLOPT_HTTP_VERSION]);
+    }
 }

+ 4 - 2
tests/TestCase/Http/ClientTest.php

@@ -54,6 +54,7 @@ class ClientTest extends TestCase
             'scheme' => 'http',
             'host' => 'example.org',
             'auth' => ['username' => 'mark', 'password' => 'secret'],
+            'protocolVersion' => '1.1',
         ];
         foreach ($expected as $key => $val) {
             $this->assertEquals($val, $result[$key]);
@@ -209,9 +210,10 @@ class ClientTest extends TestCase
             ->getMock();
         $mock->expects($this->once())
             ->method('send')
-            ->with($this->callback(function ($request) use ($cookies, $headers) {
+            ->with($this->callback(function ($request) use ($headers) {
                 $this->assertInstanceOf('Cake\Http\Client\Request', $request);
                 $this->assertEquals(Request::METHOD_GET, $request->getMethod());
+                $this->assertSame('2', $request->getProtocolVersion());
                 $this->assertEquals('http://cakephp.org/test.html', $request->getUri() . '');
                 $this->assertEquals('split=value', $request->getHeaderLine('Cookie'));
                 $this->assertEquals($headers['Content-Type'], $request->getHeaderLine('content-type'));
@@ -221,7 +223,7 @@ class ClientTest extends TestCase
             }))
             ->will($this->returnValue([$response]));
 
-        $http = new Client(['adapter' => $mock]);
+        $http = new Client(['adapter' => $mock, 'protocolVersion' => '2']);
         $result = $http->get('http://cakephp.org/test.html', [], [
             'headers' => $headers,
             'cookies' => $cookies,