diff --git a/CHANGELOG.md b/CHANGELOG.md index c73fe4534a..f8cbc89f4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file, in reverse ### Added -- Nothing. +- [#173](https://github.com/zendframework/zend-http/pull/173) adds support for HTTP/2 requests and responses. ### Changed diff --git a/docs/book/client/intro.md b/docs/book/client/intro.md index 253da98836..11249a1504 100644 --- a/docs/book/client/intro.md +++ b/docs/book/client/intro.md @@ -77,7 +77,7 @@ Parameter | Description `strictredirects` | Whether to strictly follow the RFC when redirecting (see this section) | boolean | FALSE `useragent` | User agent identifier string (sent in request headers) | string | `Zend\Http\Client` `timeout` | Connection timeout (seconds) | integer | 10 -`httpversion` | HTTP protocol version (usually '1.1' or '1.0') | string | 1.1 +`httpversion` | HTTP protocol version (usually '1.1', '1.0' or '2'; 2 is only supported starting in 2.10.0) | string | 1.1 `adapter` | Connection adapter class to use (see this section) | mixed | `Zend\Http\Client\Adapter\Socket` `keepalive` | Whether to enable keep-alive connections with the server. Useful and might improve performance if several consecutive requests to the same server are performed. | boolean | FALSE `storeresponse` | Whether to store last response for later retrieval with getLastResponse(). If set to FALSE, getLastResponse() will return NULL. | boolean | TRUE diff --git a/docs/book/request.md b/docs/book/request.md index d26677471b..a55295744d 100644 --- a/docs/book/request.md +++ b/docs/book/request.md @@ -75,7 +75,7 @@ Method signature | De `setUri(string|Uri $uri) : self` | Set the URI/URL for this request; this can be a string or an instance of `Zend\Uri\Http`. `getUri() : Uri` | Return the URI for this request object. `getUriString() : string` | Return the URI for this request object as a string. -`setVersion(string $version) : self` | Set the HTTP version for this object, one of 1.0 or 1.1 (`Request::VERSION_10`, `Request::VERSION_11`). +`setVersion(string $version) : self` | Set the HTTP version for this object, one of 1.0, 1.1 or 2 (`Request::VERSION_10`, `Request::VERSION_11`, `Request::VERSION_2`). HTTP/2 support was added in zend-http 2.10.0. `getVersion() : string` | Return the HTTP version for this request. `setQuery(Parameters $query) : self` | Provide an alternate Parameter Container implementation for query parameters in this object. (This is NOT the primary API for value setting; for that, see `getQuery()`). `getQuery(string|null $name, mixed|null $default) : null|string|Parameters` | Return the parameter container responsible for query parameters or a single query parameter based on `$name`. diff --git a/docs/book/response.md b/docs/book/response.md index af9e2aac44..4f32707428 100644 --- a/docs/book/response.md +++ b/docs/book/response.md @@ -77,8 +77,8 @@ Method signature | Descrip `renderStatusLine() : string` | Render the status line header `setHeaders(Headers $headers) : self` | Provide an alternate Parameter Container implementation for headers in this object. (This is NOT the primary API for value setting; for that, see `getHeaders()`.) `getHeaders() : Headers` | Return the container responsible for storing HTTP headers. This container exposes the primary API for manipulating headers set in the HTTP response. See the section on [Headers](headers.md) for more information. -`setVersion(string $version) : self` | Set the HTTP version for this object, one of 1.0 or 1.1 (`Request::VERSION_10`, `Request::VERSION_11`). -`getVersion() : string` | Return the HTTP version for this request. +`setVersion(string $version) : self` | Set the HTTP version for this object, one of 1.0, 1.1 or 2 (`Response::VERSION_10`, `Response::VERSION_11`, `Response::VERSION_2`). HTTP/2 support was added in zend-http 2.10.0. +`getVersion() : string` | Return the HTTP version for this response. `setStatusCode(int $code) : self` | Set HTTP status code. `getStatusCode() : int` | Retrieve HTTP status code. `setReasonPhrase(string $reasonPhrase) : self` | Set custom HTTP status message. diff --git a/src/AbstractMessage.php b/src/AbstractMessage.php index e09247a214..4fbd423fba 100644 --- a/src/AbstractMessage.php +++ b/src/AbstractMessage.php @@ -21,6 +21,7 @@ abstract class AbstractMessage extends Message */ const VERSION_10 = '1.0'; const VERSION_11 = '1.1'; + const VERSION_2 = '2'; /**#@-*/ /** @@ -34,16 +35,16 @@ abstract class AbstractMessage extends Message protected $headers; /** - * Set the HTTP version for this object, one of 1.0 or 1.1 - * (AbstractMessage::VERSION_10, AbstractMessage::VERSION_11) + * Set the HTTP version for this object, one of 1.0, 1.1 or 2 + * (AbstractMessage::VERSION_10, AbstractMessage::VERSION_11, AbstractMessage::VERSION_2) * - * @param string $version (Must be 1.0 or 1.1) + * @param string $version (Must be 1.0, 1.1 or 2) * @return AbstractMessage * @throws Exception\InvalidArgumentException */ public function setVersion($version) { - if ($version != self::VERSION_10 && $version != self::VERSION_11) { + if (! in_array($version, [self::VERSION_10, self::VERSION_11, self::VERSION_2])) { throw new Exception\InvalidArgumentException( 'Not valid or not supported HTTP version: ' . $version ); diff --git a/src/Response.php b/src/Response.php index f3547195c3..1dbfd92b90 100644 --- a/src/Response.php +++ b/src/Response.php @@ -249,7 +249,7 @@ public static function fromString($string) */ protected function parseStatusLine($line) { - $regex = '/^HTTP\/(?P1\.[01]) (?P\d{3})(?:[ ]+(?P.*))?$/'; + $regex = '/^HTTP\/(?P1\.[01]|2) (?P\d{3})(?:[ ]+(?P.*))?$/'; $matches = []; if (! preg_match($regex, $line, $matches)) { throw new Exception\InvalidArgumentException( diff --git a/test/ResponseTest.php b/test/ResponseTest.php index 4df72fb148..6d1f48abef 100644 --- a/test/ResponseTest.php +++ b/test/ResponseTest.php @@ -16,23 +16,79 @@ class ResponseTest extends TestCase { - public function testResponseFactoryFromStringCreatesValidResponse() + public function validHttpVersions() { - $string = 'HTTP/1.0 200 OK' . "\r\n\r\n" . 'Foo Bar'; + yield 'http/1.0' => ['1.0']; + yield 'http/1.1' => ['1.1']; + yield 'http/2' => ['2']; + } + + public function validResponseHttpVersionProvider() + { + $responseTemplate = "HTTP/%s 200 OK\r\n\r\nFoo Bar"; + foreach ($this->validHttpVersions() as $testCase => $data) { + $version = array_shift($data); + yield $testCase => [ + 'response' => sprintf($responseTemplate, $version), + 'expectedVersion' => $version, + 'expectedStatus' => '200', + 'expectedContent' => 'Foo Bar', + ]; + } + } + + /** + * @dataProvider validResponseHttpVersionProvider + * @param string $string Response string + * @param string $expectedVersion + * @param string $expectedStatus + * @param string $expectedContent + */ + public function testResponseFactoryFromStringCreatesValidResponse( + $string, + $expectedVersion, + $expectedStatus, + $expectedContent + ) { $response = Response::fromString($string); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('Foo Bar', $response->getContent()); + $this->assertEquals($expectedVersion, $response->getVersion()); + $this->assertEquals($expectedStatus, $response->getStatusCode()); + $this->assertEquals($expectedContent, $response->getContent()); } - public function testResponseCanRenderStatusLine() + /** + * @dataProvider validHttpVersions + * @param string $version + */ + public function testResponseCanRenderStatusLineUsingDefaultReasonPhrase($version) { + $expected = sprintf('HTTP/%s 404 Not Found', $version); $response = new Response(); - $response->setVersion(1.1); + $response->setVersion($version); $response->setStatusCode(Response::STATUS_CODE_404); - $this->assertEquals('HTTP/1.1 404 Not Found', $response->renderStatusLine()); + $this->assertEquals($expected, $response->renderStatusLine()); + } + /** + * @dataProvider validHttpVersions + * @param string $version + */ + public function testResponseCanRenderStatusLineUsingCustomReasonPhrase($version) + { + $expected = sprintf('HTTP/%s 404 Foo Bar', $version); + $response = new Response(); + $response->setVersion($version); + $response->setStatusCode(Response::STATUS_CODE_404); $response->setReasonPhrase('Foo Bar'); - $this->assertEquals('HTTP/1.1 404 Foo Bar', $response->renderStatusLine()); + $this->assertEquals($expected, $response->renderStatusLine()); + } + + public function testInvalidHTTP2VersionString() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A valid response status line was not found in the provided string'); + $string = 'HTTP/2.0 200 OK' . "\r\n\r\n" . 'Foo Bar'; + $response = \Zend\Http\Response::fromString($string); } public function testResponseUsesHeadersContainerByDefault()