{"action":"opened","issue":{"url":"https://api.github.com/repos/guzzle/guzzle/issues/1249","labels_url":"https://api.github.com/repos/guzzle/guzzle/issues/1249/labels{/name}","comments_url":"https://api.github.com/repos/guzzle/guzzle/issues/1249/comments","events_url":"https://api.github.com/repos/guzzle/guzzle/issues/1249/events","html_url":"https://github.com/guzzle/guzzle/issues/1249","id":105141274,"number":1249,"title":"HTTP/2 Support","user":{"login":"dshafik","id":58074,"avatar_url":"https://avatars.githubusercontent.com/u/58074?v=3","gravatar_id":"","url":"https://api.github.com/users/dshafik","html_url":"https://github.com/dshafik","followers_url":"https://api.github.com/users/dshafik/followers","following_url":"https://api.github.com/users/dshafik/following{/other_user}","gists_url":"https://api.github.com/users/dshafik/gists{/gist_id}","starred_url":"https://api.github.com/users/dshafik/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dshafik/subscriptions","organizations_url":"https://api.github.com/users/dshafik/orgs","repos_url":"https://api.github.com/users/dshafik/repos","events_url":"https://api.github.com/users/dshafik/events{/privacy}","received_events_url":"https://api.github.com/users/dshafik/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-09-07T03:56:23Z","updated_at":"2015-09-07T03:56:23Z","closed_at":null,"body":"This issue is intended to document the current and ongoing status of HTTP/2 support in Guzzle.\r\n\r\n## Current Status\r\n\r\n### Using Curl\r\n\r\nThe only handler that can support HTTP/2 at present is Curl (see below for streams handler info).\r\n\r\nFor Curl to support HTTP/2 needs to be built with `--with-nghttp2` and be `>= 7.33.0`. For multiplex support, you need `>= 7.43.0`. \r\n\r\nTo detect support for HTTP/2 in libcurl from PHP, you must use:\r\n\r\n```php\r\nif (curl_version()[\"features\"] & CURL_VERSION_HTTP2 !== 0) {\r\n    // HTTP/2 support\r\n}\r\n```\r\n\r\nThe `CURL_VERSION_HTTP2` constant was added in PHP 5.5.24, by using the constant value, `65536`, or adding something like this to cover all HTTP/2 related constants:\r\n\r\n```php\r\ndefined('CURL_VERSION_HTTP2') || define('CURL_VERSION_HTTP2', 65536);\r\ndefined('CURL_HTTP_VERSION_2_0') || define('CURL_HTTP_VERSION_2_0', 3);\r\ndefined('CURL_HTTP_VERSION_2') || define('CURL_HTTP_VERSION_2', CURL_HTTP_VERSION_2_0);\r\ndefined('CURLPIPE_NOTHING') || define(CURLPIPE_NOTHING, 0);\r\ndefined('CURLPIPE_HTTP1') || define('CURLPIPE_HTTP1', 1);\r\ndefined('CURLPIPE_MULTIPLEX') || define('CURLPIPE_MULTIPLEX', 2);\r\n```\r\n\r\nAt present, with the merge of #1246, it is now possible to use HTTP/2 for synchronous requests:\r\n\r\n```php\r\nuse GuzzleHttp\\Client;\r\n\r\n$client = new Client();\r\n$client->get('https://http2.akamai.com/demo/tile-0.png', [\r\n    'curl' => [\r\n        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,\r\n    ],\r\n    'debug' => true,\r\n]);\r\n```\r\n\r\nThis will yield debug output including the following, indicating it's using HTTP/2 successfully:\r\n\r\n```\r\n* Using HTTP2, server supports multi-use\r\n* Connection state changed (HTTP/2 confirmed)\r\n* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0\r\n* Using Stream ID: 1 (easy handle 0x7f8084840800)\r\n> GET /demo/tile-0.png HTTP/1.1\r\nHost: http2.akamai.com\r\nUser-Agent: GuzzleHttp/6.0.2 curl/7.44.0 PHP/7.0.0-dev\r\n\r\n* http2_recv: 16384 bytes buffer at 0x7f8084841170 (stream 1)\r\n* http2_recv: 16384 bytes buffer at 0x7f8084841170 (stream 1)\r\n* http2_recv: 16384 bytes buffer at 0x7f8084841170 (stream 1)\r\n* http2_recv: returns 1314 for stream 1\r\n< HTTP/2.0 200\r\n```\r\n\r\nUnfortunately, if you attempt to use it with libcurl compiled without http2 support, then the following happens:\r\n\r\nFirst the response from the request is output to STDOUT (because `CURLOPT_RETURNTRANSFER` is set to `false` and it seems to fail to use the streams stuff), then the following exception is thrown:\r\n\r\n```php\r\nGuzzleHttp\\Exception\\RequestException: cURL error 0: The cURL request was retried 3 times and did not succeed. The most likely reason for the failure is that cURL was unable to rewind the body of the request and subsequent retries resulted in the same error. Turn on the debug option to see what went wrong. See https://bugs.php.net/bug.php?id=47204 for more information. (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) in /Users/dshafik/src/php-curl-http2-multiplex/vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php on line 170\r\n\r\nCall Stack:\r\n    0.0075     230648   1. {main}() src/php-curl-http2-multiplex/http2-guzzle-sync.php:0\r\n    0.0900    1089168   2. GuzzleHttp\\Client->get() src/php-curl-http2-multiplex/http2-guzzle-sync.php:12\r\n    0.0900    1089688   3. GuzzleHttp\\Client->__call() src/php-curl-http2-multiplex/http2-guzzle-sync.php:12\r\n    0.0900    1089760   4. GuzzleHttp\\Client->request() src/php-curl-http2-multiplex/vendor/guzzlehttp/guzzle/src/Client.php:88\r\n    0.1536    1527176   5. GuzzleHttp\\Promise\\Promise->wait() src/php-curl-http2-multiplex/vendor/guzzlehttp/guzzle/src/Client.php:130\r\n```\r\n\r\n#### Backwards Compatibility\r\n\r\nWe should transparently downgrade to HTTP/1.1, and `CURLPIPE_HTTP1`, if libcurl does not support HTTP/2, and/or multiplexing respectively.\r\n\r\n### Using Streams\r\n\r\nTo implement HTTP/2 for the streams adapter would require either: \r\n\r\n1. HTTP/2 support in the http/s streams\r\n2. A new http2 stream, either in core, or as an extension\r\n3. A user land implementation and custom stream. It _is_ possible to override an existing stream (unregister, and register custom one), so it could be transparent, or just use `h2://`or `http2://` or some such (or both).\r\n4. A new http2 extension, which can be used to implement an http2 supported stream\r\n\r\n## Testing HTTP/2\r\n\r\nTo properly test HTTP/2 support, we need to do a few different test approaches:\r\n\r\n### HTTP/2 Client -> HTTP/2 Server\r\n\r\nThe current `server.js` only supports HTTP/1.1, which means that we cannot test actual HTTP/2 requests against it. However, there is a fairly drop-in replacement for node's `http` module, which is `http2` found here: <https://github.com/molnarg/node-http2>. I currently have update `GuzzleHttp\\Tests\\Server` to allow you to specify which version of HTTP being used, and spin up `server.js` or a new `server2.js` — however these scripts are _nearly_ identical and it would be trivial to handle that as an argument sent to `server.js`. The entire diff looks like this:\r\n\r\n```diff\r\n--- tests/server.js\t2015-07-13 08:00:16.000000000 +1000\r\n+++ tests/server2.js\t2015-09-06 14:30:09.000000000 +1000\r\n@@ -1,7 +1,9 @@\r\n /**\r\n- * Guzzle node.js test server to return queued responses to HTTP requests and\r\n+ * Guzzle node.js test HTTP/2 server to return queued responses to HTTP requests and\r\n  * expose a RESTful API for enqueueing responses and retrieving the requests\r\n  * that have been received.\r\n+ * \r\n+ * Run `npm install` to install dependencies\r\n  *\r\n  * - Delete all requests that have been received:\r\n  *      > DELETE /guzzle-server/requests\r\n@@ -38,8 +40,9 @@\r\n  * @license See the LICENSE file that was distributed with this source code.\r\n  */\r\n \r\n-var http = require('http');\r\n+var http = require('http2');\r\n var url = require('url');\r\n+var fs = require('fs');\r\n \r\n /**\r\n  * Guzzle node.js server\r\n@@ -199,8 +202,12 @@\r\n   };\r\n \r\n   this.start = function() {\r\n-\r\n-    that.server = http.createServer(function(req, res) {\r\n+    var options = {\r\n+      key: fs.readFileSync(__dirname + '/node_modules/http2/example/localhost.key'),\r\n+      cert: fs.readFileSync(__dirname + '/node_modules/http2/example/localhost.crt')\r\n+    };\r\n+    \r\n+    that.server = http.createServer(options, function(req, res) {\r\n \r\n       var parts = url.parse(req.url, false);\r\n       var request = {\r\n```\r\n\r\n### HTTP/2 Client -> HTTP/1.1 Server\r\n\r\nCurrently libcurl always does an HTTP/1.1 request and attempts to upgrade the connection to HTTP/2, this method is available so that you can always make HTTP/2 requests regardless of server support, and it will automatically fall back to HTTP/1.1. Support for prior-knowledge requests are planned for libcurl, so it might be possible to do native HTTP/2 rather than HTTP/1.1+Upgrade in the future.\r\n\r\nThis means we should also run HTTP/2 intended requests against the existing HTTP/1.1 server. \r\n\r\nIt would be great if it were possible to detect failure to upgrade and to switch to `CURLPIPE_HTTP1` transparently if they were attempting to use `CURLPIPE_MULTIPLEX`.\r\n\r\n## Desired API\r\n\r\nEnabling HTTP/2 should be as simple as setting the request option `version => 2` (or `2.0`). Due to the HTTP/2 upgrade negotiation, it's entirely BC to actually set this as the _default_ (see performance related section below).\r\n\r\nThis means that it will automatically use HTTP/1.1 if HTTP/2 is not available, we do not have to handle this ourselves.\r\n\r\nOnce the `version` is set, concurrent requests should _attempt_ to use HTTP/2 multiplexing. Failing to do so should switch to HTTP pipelining transparently.\r\n\r\nWhile the overhead of `CURLPIPE_HTTP1` is greater, it drastically faster than _not_ using it, giving closer to the performance expected with `CURLPIPE_MULTIPLEX`.\r\n\r\n### Server Push\r\n\r\nServer push is the ability for the HTTP server to inject responses into the clients cache in response to a different request that it expects will also be wanted. An example of this would be requesting an HTML page, and having the server also push you the CSS, JS, and images, rather than waiting for the client to have to parse the HTML and make additional requests for those resources.\r\n\r\nlibcurl supports this feature in `>= 7.44.0`, and it works by registering a callback to accept or reject the request. The callback accepts (among other things) an easy handle representing the request. Rejecting the request requires returning `CURL_PUSH_DENY`, otherwise if you return `CURL_PUSH_OK` it will add the easy handle to the multihandler and you can handle it as you would normally from that point.\r\n\r\n_If the callback is not defined_ then all server pushes are rejected automatically.\r\n\r\nPHP does not currently support this feature.\r\n\r\nAlso, while this feature is callback based, it might be the most difficult to implement consistently among handlers. I would recommend waiting till at least one other handler supports it before implementing.\r\n\r\n## HTTP/2 Performance & When To Use It\r\n\r\n_Currently_, sending a single HTTP request using HTTP/2 is _slower_ than using HTTP/1.1. I _believe_ this might be to do with the HTTP/1.1+Upgrade that is done. This means if you are doing just 1:1 request:host then you should use HTTP/1.1 at present.\r\n\r\n_However_ you are doing _multiple_ HTTPS requests to the same host, and using multiplexing, then you get a significant performance increase due to only needing a single SSL negotiation for all requests on the same multiplexed connection."}}