diff --git a/composer.json b/composer.json index 59736ddd..93a5d545 100644 --- a/composer.json +++ b/composer.json @@ -33,8 +33,7 @@ "react/event-loop": "^1.2", "react/promise": "^3 || ^2.3 || ^1.2.1", "react/socket": "^1.12", - "react/stream": "^1.2", - "ringcentral/psr7": "^1.2" + "react/stream": "^1.2" }, "require-dev": { "clue/http-proxy-react": "^1.8", diff --git a/examples/11-client-http-proxy.php b/examples/11-client-http-proxy.php index f450fbc2..2698f291 100644 --- a/examples/11-client-http-proxy.php +++ b/examples/11-client-http-proxy.php @@ -25,7 +25,7 @@ // demo fetching HTTP headers (or bail out otherwise) $browser->get('https://www.google.com/')->then(function (ResponseInterface $response) { - echo RingCentral\Psr7\str($response); + echo React\Http\Psr7\str($response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/12-client-socks-proxy.php b/examples/12-client-socks-proxy.php index ecedf242..ba9a658a 100644 --- a/examples/12-client-socks-proxy.php +++ b/examples/12-client-socks-proxy.php @@ -25,7 +25,7 @@ // demo fetching HTTP headers (or bail out otherwise) $browser->get('https://www.google.com/')->then(function (ResponseInterface $response) { - echo RingCentral\Psr7\str($response); + echo React\Http\Psr7\str($response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/13-client-ssh-proxy.php b/examples/13-client-ssh-proxy.php index 64d0c282..803ea15b 100644 --- a/examples/13-client-ssh-proxy.php +++ b/examples/13-client-ssh-proxy.php @@ -21,7 +21,7 @@ // demo fetching HTTP headers (or bail out otherwise) $browser->get('https://www.google.com/')->then(function (ResponseInterface $response) { - echo RingCentral\Psr7\str($response); + echo React\Http\Psr7\str($response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/14-client-unix-domain-sockets.php b/examples/14-client-unix-domain-sockets.php index e9718141..cb2eda83 100644 --- a/examples/14-client-unix-domain-sockets.php +++ b/examples/14-client-unix-domain-sockets.php @@ -4,7 +4,6 @@ use React\Http\Browser; use React\Socket\FixedUriConnector; use React\Socket\UnixConnector; -use RingCentral\Psr7; require __DIR__ . '/../vendor/autoload.php'; @@ -18,7 +17,7 @@ // demo fetching HTTP headers (or bail out otherwise) $browser->get('http://localhost/info')->then(function (ResponseInterface $response) { - echo Psr7\str($response); + echo React\Http\Psr7\str($response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/21-client-request-streaming-to-stdout.php b/examples/21-client-request-streaming-to-stdout.php index 2f24d035..1e7a7624 100644 --- a/examples/21-client-request-streaming-to-stdout.php +++ b/examples/21-client-request-streaming-to-stdout.php @@ -4,7 +4,6 @@ use Psr\Http\Message\ResponseInterface; use React\Stream\ReadableStreamInterface; use React\Stream\WritableResourceStream; -use RingCentral\Psr7; require __DIR__ . '/../vendor/autoload.php'; @@ -22,7 +21,7 @@ $info->write('Requesting ' . $url . '…' . PHP_EOL); $client->requestStreaming('GET', $url)->then(function (ResponseInterface $response) use ($info, $out) { - $info->write('Received' . PHP_EOL . Psr7\str($response)); + $info->write('Received' . PHP_EOL . React\Http\Psr7\str($response)); $body = $response->getBody(); assert($body instanceof ReadableStreamInterface); diff --git a/examples/22-client-stream-upload-from-stdin.php b/examples/22-client-stream-upload-from-stdin.php index b00fbc5e..218cb000 100644 --- a/examples/22-client-stream-upload-from-stdin.php +++ b/examples/22-client-stream-upload-from-stdin.php @@ -3,7 +3,6 @@ use Psr\Http\Message\ResponseInterface; use React\Http\Browser; use React\Stream\ReadableResourceStream; -use RingCentral\Psr7; require __DIR__ . '/../vendor/autoload.php'; @@ -20,7 +19,7 @@ echo 'Sending STDIN as POST to ' . $url . '…' . PHP_EOL; $client->post($url, array(), $in)->then(function (ResponseInterface $response) { - echo 'Received' . PHP_EOL . Psr7\str($response); + echo 'Received' . PHP_EOL . React\Http\Psr7\str($response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); diff --git a/examples/71-server-http-proxy.php b/examples/71-server-http-proxy.php index cf63c4ae..fa51c819 100644 --- a/examples/71-server-http-proxy.php +++ b/examples/71-server-http-proxy.php @@ -28,7 +28,7 @@ // left up as an exercise: use an HTTP client to send the outgoing request // and forward the incoming response to the original client request return React\Http\Message\Response::plaintext( - RingCentral\Psr7\str($outgoing) + React\Http\Psr7\str($outgoing) ); }); diff --git a/examples/91-client-benchmark-download.php b/examples/91-client-benchmark-download.php index 49693baf..e539a05c 100644 --- a/examples/91-client-benchmark-download.php +++ b/examples/91-client-benchmark-download.php @@ -30,7 +30,7 @@ $client->requestStreaming('GET', $url)->then(function (ResponseInterface $response) { echo 'Headers received' . PHP_EOL; - echo RingCentral\Psr7\str($response); + echo React\Http\Psr7\str($response); $stream = $response->getBody(); assert($stream instanceof ReadableStreamInterface); diff --git a/src/Browser.php b/src/Browser.php index 3e3458af..ec8771ac 100644 --- a/src/Browser.php +++ b/src/Browser.php @@ -2,17 +2,17 @@ namespace React\Http; +use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; -use RingCentral\Psr7\Uri; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Http\Io\Sender; use React\Http\Io\Transaction; use React\Http\Message\Request; +use React\Http\Psr7\Uri; use React\Promise\PromiseInterface; use React\Socket\ConnectorInterface; use React\Stream\ReadableStreamInterface; -use InvalidArgumentException; /** * @final This class is final and shouldn't be extended as it is likely to be marked final in a future release. diff --git a/src/Io/ClientRequestStream.php b/src/Io/ClientRequestStream.php index bdaa54f1..b001b0cb 100644 --- a/src/Io/ClientRequestStream.php +++ b/src/Io/ClientRequestStream.php @@ -4,13 +4,12 @@ use Evenement\EventEmitter; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use React\Http\Message\Response; +use React\Http\Psr7\Utils; use React\Promise; use React\Socket\ConnectionInterface; use React\Socket\ConnectorInterface; use React\Stream\WritableStreamInterface; -use RingCentral\Psr7 as gPsr; /** * @event response @@ -156,7 +155,7 @@ public function handleData($data) // buffer until double CRLF (or double LF for compatibility with legacy servers) if (false !== strpos($this->buffer, "\r\n\r\n") || false !== strpos($this->buffer, "\n\n")) { try { - $response = gPsr\parse_response($this->buffer); + $response = Utils::parseResponse($this->buffer); $bodyChunk = (string) $response->getBody(); } catch (\InvalidArgumentException $exception) { $this->closeError($exception); diff --git a/src/Io/Transaction.php b/src/Io/Transaction.php index bfa42241..73868a9c 100644 --- a/src/Io/Transaction.php +++ b/src/Io/Transaction.php @@ -8,11 +8,11 @@ use React\EventLoop\LoopInterface; use React\Http\Message\Response; use React\Http\Message\ResponseException; +use React\Http\Psr7\Uri; use React\Promise\Deferred; use React\Promise\Promise; use React\Promise\PromiseInterface; use React\Stream\ReadableStreamInterface; -use RingCentral\Psr7\Uri; /** * @internal diff --git a/src/Message/Request.php b/src/Message/Request.php index cf59641e..4ca36c65 100644 --- a/src/Message/Request.php +++ b/src/Message/Request.php @@ -7,11 +7,11 @@ use Psr\Http\Message\UriInterface; use React\Http\Io\BufferedBody; use React\Http\Io\ReadableBodyStream; +use React\Http\Psr7\Request as BaseRequest; use React\Stream\ReadableStreamInterface; -use RingCentral\Psr7\Request as BaseRequest; /** - * Respresents an outgoing HTTP request message. + * Represents an outgoing HTTP request message. * * This class implements the * [PSR-7 `RequestInterface`](https://www.php-fig.org/psr/psr-7/#32-psrhttpmessagerequestinterface) @@ -28,7 +28,7 @@ * * @see RequestInterface */ -final class Request extends BaseRequest implements RequestInterface +final class Request extends BaseRequest { /** * @param string $method HTTP method for the request. diff --git a/src/Message/Response.php b/src/Message/Response.php index edd6245b..e84beedb 100644 --- a/src/Message/Response.php +++ b/src/Message/Response.php @@ -6,8 +6,8 @@ use Psr\Http\Message\StreamInterface; use React\Http\Io\BufferedBody; use React\Http\Io\HttpBodyStream; +use React\Http\Psr7\Response as Psr7Response; use React\Stream\ReadableStreamInterface; -use RingCentral\Psr7\Response as Psr7Response; /** * Represents an outgoing server response message. diff --git a/src/Message/ServerRequest.php b/src/Message/ServerRequest.php index 25532cf4..a5137db6 100644 --- a/src/Message/ServerRequest.php +++ b/src/Message/ServerRequest.php @@ -7,11 +7,11 @@ use Psr\Http\Message\UriInterface; use React\Http\Io\BufferedBody; use React\Http\Io\HttpBodyStream; +use React\Http\Psr7\Request as BaseRequest; use React\Stream\ReadableStreamInterface; -use RingCentral\Psr7\Request as BaseRequest; /** - * Respresents an incoming server request message. + * Represents an incoming server request message. * * This class implements the * [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface) diff --git a/src/Psr7/BufferStream.php b/src/Psr7/BufferStream.php new file mode 100644 index 00000000..bcb5bfe5 --- /dev/null +++ b/src/Psr7/BufferStream.php @@ -0,0 +1,142 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + + return null; + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function rewind() + { + $this->seek(0); + } + + public function seek($offset, $whence = SEEK_SET) + { + throw new \RuntimeException('Cannot seek a BufferStream'); + } + + public function eof() + { + return $this->buffer === ''; + } + + public function tell() + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return 0; + } + + return strlen($string); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key === 'hwm') { + return $this->hwm; + } + + return $key ? null : array(); + } +} diff --git a/src/Psr7/MessageTrait.php b/src/Psr7/MessageTrait.php new file mode 100644 index 00000000..a2abb01b --- /dev/null +++ b/src/Psr7/MessageTrait.php @@ -0,0 +1,200 @@ + array of values */ + protected $headers = array(); + + /** @var array Map of lowercase header name => original name at registration */ + protected $headerNames = array(); + + public function getProtocolVersion() + { + return $this->protocol; + } + + public function withProtocolVersion($version) + { + if ($this->protocol === $version) { + return $this; + } + + $new = clone $this; + $new->protocol = $version; + + return $new; + } + + public function getHeaders() + { + return $this->headers; + } + + public function hasHeader($header) + { + return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]); + } + + public function getHeader($header) + { + $header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$header])) { + return array(); + } + + $header = $this->headerNames[$header]; + + return $this->headers[$header]; + } + + public function getHeaderLine($header) + { + return \implode(', ', $this->getHeader($header)); + } + + public function withHeader($header, $value) + { + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + + return $new; + } + + public function withAddedHeader($header, $value) + { + if (!\is_string($header) || '' === $header) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + + $new = clone $this; + $new->setHeaders(array($header => $value)); + + return $new; + } + + public function withoutHeader($header) + { + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (!isset($this->headerNames[$normalized])) { + return $this; + } + + $header = $this->headerNames[$normalized]; + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + + return $new; + } + + public function getBody() + { + if (null === $this->stream) { + $this->stream = Utils::createStream(); + } + + return $this->stream; + } + + public function withBody(StreamInterface $body) + { + if ($body === $this->stream) { + return $this; + } + + $new = clone $this; + $new->stream = $body; + + return $new; + } + + protected function setHeaders(array $headers) + { + foreach ($headers as $header => $value) { + if (\is_int($header)) { + // If a header name was set to a numeric string, PHP will cast the key to an int. + // We must cast it back to a string in order to comply with validation. + $header = (string) $header; + } + $value = $this->validateAndTrimHeader($header, $value); + $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + + /** + * Make sure the header complies with RFC 7230. + * + * Header names must be a non-empty string consisting of token characters. + * + * Header values must be strings consisting of visible characters with all optional + * leading and trailing whitespace stripped. This method will always strip such + * optional whitespace. Note that the method does not allow folding whitespace within + * the values as this was deprecated for almost all instances by the RFC. + * + * header-field = field-name ":" OWS field-value OWS + * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" + * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) + * OWS = *( SP / HTAB ) + * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) + * + * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 + */ + private function validateAndTrimHeader($header, $values) + { + if (!\is_string($header) || 1 !== \preg_match("@^[!#$%&'*+.^_`|~0-9A-Za-z-]+$@", $header)) { + throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string.'); + } + + if (!\is_array($values)) { + // This is simple, just one value. + if ((!\is_numeric($values) && !\is_string($values)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $values)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + } + + return array(\trim((string) $values, " \t")); + } + + if (empty($values)) { + throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given.'); + } + + // Assert Non empty array + $returnValues = array(); + foreach ($values as $v) { + if ((!\is_numeric($v) && !\is_string($v)) || 1 !== \preg_match("@^[ \t\x21-\x7E\x80-\xFF]*$@", (string) $v)) { + throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings.'); + } + + $returnValues[] = \trim((string) $v, " \t"); + } + + return $returnValues; + } +} diff --git a/src/Psr7/Request.php b/src/Psr7/Request.php new file mode 100644 index 00000000..3b567521 --- /dev/null +++ b/src/Psr7/Request.php @@ -0,0 +1,41 @@ +method = $method; + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + + if (!$this->hasHeader('Host')) { + $this->updateHostFromUri(); + } + + // If we got no body, defer initialization of the stream until Request::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Utils::createStream($body); + } + } +} diff --git a/src/Psr7/RequestTrait.php b/src/Psr7/RequestTrait.php new file mode 100644 index 00000000..f13200aa --- /dev/null +++ b/src/Psr7/RequestTrait.php @@ -0,0 +1,107 @@ +requestTarget) { + return $this->requestTarget; + } + + if ('' === $target = $this->uri->getPath()) { + $target = '/'; + } + if ('' !== $this->uri->getQuery()) { + $target .= '?' . $this->uri->getQuery(); + } + + return $target; + } + + public function withRequestTarget($requestTarget) + { + if (\preg_match('#\s#', $requestTarget)) { + throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + } + + $new = clone $this; + $new->requestTarget = $requestTarget; + + return $new; + } + + public function getMethod() + { + return $this->method; + } + + public function withMethod($method) + { + if (!\is_string($method)) { + throw new \InvalidArgumentException('Method must be a string'); + } + + $new = clone $this; + $new->method = $method; + + return $new; + } + + public function getUri() + { + return $this->uri; + } + + public function withUri(UriInterface $uri, $preserveHost = false) + { + if ($uri === $this->uri) { + return $this; + } + + $new = clone $this; + $new->uri = $uri; + + if (!$preserveHost || !$this->hasHeader('Host')) { + $new->updateHostFromUri(); + } + + return $new; + } + + protected function updateHostFromUri() + { + if ('' === $host = $this->uri->getHost()) { + return; + } + + if (null !== ($port = $this->uri->getPort())) { + $host .= ':' . $port; + } + + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $this->headerNames['host'] = $header = 'Host'; + } + + // Ensure Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + $this->headers = array($header => array($host)) + $this->headers; + } +} diff --git a/src/Psr7/Response.php b/src/Psr7/Response.php new file mode 100644 index 00000000..a96975f3 --- /dev/null +++ b/src/Psr7/Response.php @@ -0,0 +1,83 @@ + 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required', + ); + + /** @var string */ + private $reasonPhrase = ''; + + /** @var int */ + private $statusCode; + + /** + * @param int $status Status code + * @param array $headers Response headers + * @param string|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct($status = 200, $headers = array(), $body = null, $version = '1.1', $reason = null) + { + // If we got no body, defer initialization of the stream until Response::getBody() + if ('' !== $body && null !== $body) { + $this->stream = Utils::createStream($body); + } + + $this->statusCode = $status; + $this->setHeaders($headers); + if (null === $reason && isset(self::$PHRASES[$this->statusCode])) { + $this->reasonPhrase = self::$PHRASES[$status]; + } else { + $this->reasonPhrase = isset($reason) ? $reason : ''; + } + + $this->protocol = $version; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function withStatus($code, $reasonPhrase = '') + { + if (!\is_numeric($code)) { + throw new \InvalidArgumentException('Status code has to be an integer'); + } + + $code = (int) $code; + if ($code < 100 || $code > 599) { + throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code)); + } + + $new = clone $this; + $new->statusCode = $code; + if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::$PHRASES[$new->statusCode])) { + $reasonPhrase = self::$PHRASES[$new->statusCode]; + } + $new->reasonPhrase = $reasonPhrase; + + return $new; + } +} diff --git a/src/Psr7/Uri.php b/src/Psr7/Uri.php new file mode 100644 index 00000000..f5041b46 --- /dev/null +++ b/src/Psr7/Uri.php @@ -0,0 +1,397 @@ + 80, 'https' => 443); + + const CHAR_UNRESERVED = 'a-zA-Z0-9_\-.~'; + + const CHAR_SUB_DELIMS = '!\$&\'()*+,;='; + + /** @var string Uri scheme. */ + private $scheme = ''; + + /** @var string Uri user info. */ + private $userInfo = ''; + + /** @var string Uri host. */ + private $host = ''; + + /** @var int|null Uri port. */ + private $port; + + /** @var string Uri path. */ + private $path = ''; + + /** @var string Uri query string. */ + private $query = ''; + + /** @var string Uri fragment. */ + private $fragment = ''; + + public function __construct($uri = '') + { + if ('' !== $uri) { + if (false === $parts = \parse_url($uri)) { + throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri)); + } + + // Apply parse_url parts to a URI. + $this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->userInfo = isset($parts['user']) ? $parts['user'] : ''; + $this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; + $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; + $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $parts['pass']; + } + } + } + + public function __toString() + { + return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); + } + + public function getScheme() + { + return $this->scheme; + } + + public function getAuthority() + { + if ('' === $this->host) { + return ''; + } + + $authority = $this->host; + if ('' !== $this->userInfo) { + $authority = $this->userInfo . '@' . $authority; + } + + if (null !== $this->port) { + $authority .= ':' . $this->port; + } + + return $authority; + } + + public function getUserInfo() + { + return $this->userInfo; + } + + public function getHost() + { + return $this->host; + } + + public function getPort() + { + return $this->port; + } + + public function getPath() + { + return $this->path; + } + + public function getQuery() + { + return $this->query; + } + + public function getFragment() + { + return $this->fragment; + } + + public function withScheme($scheme) + { + if (!\is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + + if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->scheme = $scheme; + $new->port = $new->filterPort($new->port); + + return $new; + } + + public function withUserInfo($user, $password = null) + { + $info = $user; + if (null !== $password && '' !== $password) { + $info .= ':' . $password; + } + + if ($this->userInfo === $info) { + return $this; + } + + $new = clone $this; + $new->userInfo = $info; + + return $new; + } + + public function withHost($host) + { + if (!\is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + + if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { + return $this; + } + + $new = clone $this; + $new->host = $host; + + return $new; + } + + public function withPort($port) + { + if ($this->port === $port = $this->filterPort($port)) { + return $this; + } + + $new = clone $this; + $new->port = $port; + + return $new; + } + + public function withPath($path) + { + if ($this->path === $path = $this->filterPath($path)) { + return $this; + } + + $new = clone $this; + $new->path = $path; + + return $new; + } + + public function withQuery($query) + { + if ($this->query === $query = $this->filterQueryAndFragment($query)) { + return $this; + } + + $new = clone $this; + $new->query = $query; + + return $new; + } + + public function withFragment($fragment) + { + if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { + return $this; + } + + $new = clone $this; + $new->fragment = $fragment; + + return $new; + } + + /** + * Converts the relative URI into a new URI that is resolved against the base URI. + * + * @link http://tools.ietf.org/html/rfc3986#section-5.2 + */ + public static function resolve(UriInterface $base, $rel) + { + if ((string) $rel === '') { + // we can simply return the same base URI instance for this same-document reference + return $base; + } + + if (!($rel instanceof UriInterface)) { + $rel = new self($rel); + } + + if ($rel->getScheme() !== '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + + if ($rel->getAuthority() !== '') { + $targetAuthority = $rel->getAuthority(); + $targetPath = self::removeDotSegments($rel->getPath()); + $targetQuery = $rel->getQuery(); + } else { + $targetAuthority = $base->getAuthority(); + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() !== '' ? $rel->getQuery() : $base->getQuery(); + } else { + $path = $rel->getPath(); + if ($path[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($targetAuthority !== '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = strrpos($base->getPath(), '/'); + if ($lastSlashPos === false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + } + + return new self(self::createUriString( + $base->getScheme(), + $targetAuthority, + $targetPath, + $targetQuery, + $rel->getFragment() + )); + } + + /** + * Removes dot segments from a path and returns the new path. + * + * @link http://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + public static function removeDotSegments($path) + { + if ($path === '' || $path === '/') { + return $path; + } + + $results = array(); + $segments = explode('/', $path); + foreach ($segments as $segment) { + if ($segment === '..') { + array_pop($results); + } elseif ($segment !== '.') { + $results[] = $segment; + } + } + + $newPath = implode('/', $results); + + if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) { + // Re-add the leading slash if necessary for cases like "/.." + $newPath = '/' . $newPath; + } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) { + // Add the trailing slash if necessary + // If newPath is not empty, then $segment must be set and is the last segment from the foreach + $newPath .= '/'; + } + + return $newPath; + } + + /** + * Create a URI string from its various parts. + */ + private static function createUriString($scheme, $authority, $path, $query, $fragment) + { + $uri = ''; + if ('' !== $scheme) { + $uri .= $scheme . ':'; + } + + if ('' !== $authority) { + $uri .= '//' . $authority; + } + + if ('' !== $path) { + if ('/' !== $path[0]) { + if ('' !== $authority) { + // If the path is rootless and an authority is present, the path MUST be prefixed by "/" + $path = '/' . $path; + } + } elseif (isset($path[1]) && '/' === $path[1]) { + if ('' === $authority) { + // If the path is starting with more than one "/" and no authority is present, the + // starting slashes MUST be reduced to one. + $path = '/' . \ltrim($path, '/'); + } + } + + $uri .= $path; + } + + if ('' !== $query) { + $uri .= '?' . $query; + } + + if ('' !== $fragment) { + $uri .= '#' . $fragment; + } + + return $uri; + } + + /** + * Is a given port non-standard for the current scheme? + */ + private static function isNonStandardPort($scheme, $port) + { + return !isset(static::$SCHEMES[$scheme]) || $port !== static::$SCHEMES[$scheme]; + } + + private function filterPort($port) + { + if (null === $port) { + return null; + } + + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); + } + + return self::isNonStandardPort($this->scheme, $port) ? $port : null; + } + + private function filterPath($path) + { + if (!\is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', array(__CLASS__, 'rawurlencodeMatchZero'), $path); + } + + private function filterQueryAndFragment($str) + { + if (!\is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + + return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/?]++|%(?![A-Fa-f0-9]{2}))/', array(__CLASS__, 'rawurlencodeMatchZero'), $str); + } + + private static function rawurlencodeMatchZero(array $match) + { + return \rawurlencode($match[0]); + } +} diff --git a/src/Psr7/Utils.php b/src/Psr7/Utils.php new file mode 100644 index 00000000..9f1518d6 --- /dev/null +++ b/src/Psr7/Utils.php @@ -0,0 +1,123 @@ +@,;:\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; + const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; + + /** + * Creates a new PSR-7 stream. + * + * @param string|StreamInterface $body + */ + public static function createStream($body = '') + { + if ($body instanceof StreamInterface) { + return $body; + } + + return new BufferedBody($body); + } + + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + * @return ResponseInterface + */ + public static function parseResponse($message) + { + $data = self::parseMessage($message); + // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space + // between status-code and reason-phrase is required. But browsers accept + // responses without space and reason as well. + if (!preg_match('/^HTTP\/.* \d{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = explode(' ', $data['start-line'], 3); + + $httpPart = explode('/', $parts[0]); + + return new Response( + (int) $parts[1], + $data['headers'], + $data['body'], + $httpPart[1], + isset($parts[2]) ? $parts[2] : null + ); + } + + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage($message) + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + + $message = ltrim($message, "\r\n"); + + $messageParts = preg_split("/\r?\n\r?\n/", $message, 2); + + if ($messageParts === false || count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + + list($rawHeaders, $body) = $messageParts; + $rawHeaders .= "\r\n"; // Put back the delimiter we split previously + $headerParts = preg_split("/\r?\n/", $rawHeaders, 2); + + if ($headerParts === false || count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + + list($startLine, $rawHeaders) = $headerParts; + + if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = preg_replace(self::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + + /** @var array[] $headerLines */ + $count = preg_match_all(self::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER); + + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4 + if (preg_match(self::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + + throw new \InvalidArgumentException('Invalid header syntax'); + } + + $headers = array(); + + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + + return array( + 'start-line' => $startLine, + 'headers' => $headers, + 'body' => $body, + ); + } +} diff --git a/src/Psr7/functions.php b/src/Psr7/functions.php new file mode 100644 index 00000000..1bfeb971 --- /dev/null +++ b/src/Psr7/functions.php @@ -0,0 +1,42 @@ +getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + + foreach ($message->getHeaders() as $name => $values) { + if (strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + } + + return "{$msg}\r\n\r\n" . $message->getBody(); +} diff --git a/tests/BrowserTest.php b/tests/BrowserTest.php index ad61cf9b..a702e6b3 100644 --- a/tests/BrowserTest.php +++ b/tests/BrowserTest.php @@ -5,7 +5,6 @@ use Psr\Http\Message\RequestInterface; use React\Http\Browser; use React\Promise\Promise; -use RingCentral\Psr7\Uri; class BrowserTest extends TestCase { diff --git a/tests/Io/MiddlewareRunnerTest.php b/tests/Io/MiddlewareRunnerTest.php index ac836f03..8a628365 100644 --- a/tests/Io/MiddlewareRunnerTest.php +++ b/tests/Io/MiddlewareRunnerTest.php @@ -7,11 +7,11 @@ use Psr\Http\Message\ServerRequestInterface; use React\Http\Io\MiddlewareRunner; use React\Http\Message\ServerRequest; +use React\Http\Psr7\Response; use React\Promise; use React\Promise\PromiseInterface; use React\Tests\Http\Middleware\ProcessStack; use React\Tests\Http\TestCase; -use RingCentral\Psr7\Response; final class MiddlewareRunnerTest extends TestCase { diff --git a/tests/Io/StreamingServerTest.php b/tests/Io/StreamingServerTest.php index a2700b86..141b2ae6 100644 --- a/tests/Io/StreamingServerTest.php +++ b/tests/Io/StreamingServerTest.php @@ -117,7 +117,7 @@ public function testRequestEvent() $serverParams = $requestAssertion->getServerParams(); $this->assertSame(1, $i); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -150,7 +150,7 @@ public function testRequestEventWithSingleRequestHandlerArray() $serverParams = $requestAssertion->getServerParams(); $this->assertSame(1, $i); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -173,7 +173,7 @@ public function testRequestGetWithHostAndCustomPort() $data = "GET / HTTP/1.1\r\nHost: example.com:8080\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -195,7 +195,7 @@ public function testRequestGetWithHostAndHttpsPort() $data = "GET / HTTP/1.1\r\nHost: example.com:443\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -217,7 +217,7 @@ public function testRequestGetWithHostAndDefaultPortWillBeIgnored() $data = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -239,7 +239,7 @@ public function testRequestGetHttp10WithoutHostWillBeIgnored() $data = "GET / HTTP/1.0\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/', $requestAssertion->getRequestTarget()); $this->assertSame('/', $requestAssertion->getUri()->getPath()); @@ -274,7 +274,7 @@ public function testRequestOptionsAsterisk() $data = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('OPTIONS', $requestAssertion->getMethod()); $this->assertSame('*', $requestAssertion->getRequestTarget()); $this->assertSame('', $requestAssertion->getUri()->getPath()); @@ -307,7 +307,7 @@ public function testRequestConnectAuthorityForm() $data = "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com:443\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('CONNECT', $requestAssertion->getMethod()); $this->assertSame('example.com:443', $requestAssertion->getRequestTarget()); $this->assertSame('', $requestAssertion->getUri()->getPath()); @@ -329,7 +329,7 @@ public function testRequestConnectWithoutHostWillBePassesAsIs() $data = "CONNECT example.com:443 HTTP/1.1\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('CONNECT', $requestAssertion->getMethod()); $this->assertSame('example.com:443', $requestAssertion->getRequestTarget()); $this->assertSame('', $requestAssertion->getUri()->getPath()); @@ -351,7 +351,7 @@ public function testRequestConnectAuthorityFormWithDefaultPortWillBePassedAsIs() $data = "CONNECT example.com:80 HTTP/1.1\r\nHost: example.com:80\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('CONNECT', $requestAssertion->getMethod()); $this->assertSame('example.com:80', $requestAssertion->getRequestTarget()); $this->assertSame('', $requestAssertion->getUri()->getPath()); @@ -373,7 +373,7 @@ public function testRequestConnectAuthorityFormNonMatchingHostWillBePassedAsIs() $data = "CONNECT example.com:80 HTTP/1.1\r\nHost: other.example.org\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('CONNECT', $requestAssertion->getMethod()); $this->assertSame('example.com:80', $requestAssertion->getRequestTarget()); $this->assertSame('', $requestAssertion->getUri()->getPath()); @@ -425,7 +425,7 @@ public function testRequestWithoutHostEventUsesSocketAddress() $data = "GET /test HTTP/1.0\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('/test', $requestAssertion->getRequestTarget()); $this->assertEquals('http://127.0.0.1/test', $requestAssertion->getUri()); @@ -446,7 +446,7 @@ public function testRequestAbsoluteEvent() $data = "GET http://example.com/test HTTP/1.1\r\nHost: example.com\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('http://example.com/test', $requestAssertion->getRequestTarget()); $this->assertEquals('http://example.com/test', $requestAssertion->getUri()); @@ -468,7 +468,7 @@ public function testRequestAbsoluteNonMatchingHostWillBePassedAsIs() $data = "GET http://example.com/test HTTP/1.1\r\nHost: other.example.org\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('GET', $requestAssertion->getMethod()); $this->assertSame('http://example.com/test', $requestAssertion->getRequestTarget()); $this->assertEquals('http://example.com/test', $requestAssertion->getUri()); @@ -502,7 +502,7 @@ public function testRequestOptionsAsteriskEvent() $data = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('OPTIONS', $requestAssertion->getMethod()); $this->assertSame('*', $requestAssertion->getRequestTarget()); $this->assertEquals('http://example.com', $requestAssertion->getUri()); @@ -524,7 +524,7 @@ public function testRequestOptionsAbsoluteEvent() $data = "OPTIONS http://example.com HTTP/1.1\r\nHost: example.com\r\n\r\n"; $this->connection->emit('data', array($data)); - $this->assertInstanceOf('RingCentral\Psr7\Request', $requestAssertion); + $this->assertInstanceOf('React\Http\Psr7\Request', $requestAssertion); $this->assertSame('OPTIONS', $requestAssertion->getMethod()); $this->assertSame('http://example.com', $requestAssertion->getRequestTarget()); $this->assertEquals('http://example.com', $requestAssertion->getUri()); diff --git a/tests/Message/ResponseExceptionTest.php b/tests/Message/ResponseExceptionTest.php index 33eeea9e..f548f616 100644 --- a/tests/Message/ResponseExceptionTest.php +++ b/tests/Message/ResponseExceptionTest.php @@ -2,9 +2,9 @@ namespace React\Tests\Http\Message; -use React\Http\Message\ResponseException; use PHPUnit\Framework\TestCase; -use RingCentral\Psr7\Response; +use React\Http\Message\ResponseException; +use React\Http\Psr7\Response; class ResponseExceptionTest extends TestCase { diff --git a/tests/Middleware/RequestBodyBufferMiddlewareTest.php b/tests/Middleware/RequestBodyBufferMiddlewareTest.php index fd818a8c..7f293ab5 100644 --- a/tests/Middleware/RequestBodyBufferMiddlewareTest.php +++ b/tests/Middleware/RequestBodyBufferMiddlewareTest.php @@ -8,9 +8,9 @@ use React\Http\Message\Response; use React\Http\Message\ServerRequest; use React\Http\Middleware\RequestBodyBufferMiddleware; +use React\Http\Psr7\BufferStream; use React\Stream\ThroughStream; use React\Tests\Http\TestCase; -use RingCentral\Psr7\BufferStream; final class RequestBodyBufferMiddlewareTest extends TestCase {