Skip to content

Commit

Permalink
Merge pull request #519 from clue-labs/psr7-request
Browse files Browse the repository at this point in the history
Refactor `Request` and `ServerRequest` classes to build on top of new PSR-7 implementation
  • Loading branch information
WyriHaximus committed Mar 17, 2024
2 parents 4c23ea4 + 0638dcd commit 5873b89
Show file tree
Hide file tree
Showing 6 changed files with 636 additions and 39 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2642,8 +2642,7 @@ This is mostly used internally to represent each outgoing HTTP request
message for the HTTP client implementation. Likewise, you can also use this
class with other HTTP client implementations and for tests.

> Internally, this implementation builds on top of an existing outgoing
request message and only adds support for streaming. This base class is
> Internally, this implementation builds on top of a base class which is
considered an implementation detail that may change in the future.

#### ServerRequest
Expand All @@ -2662,8 +2661,7 @@ This is mostly used internally to represent each incoming request message.
Likewise, you can also use this class in test cases to test how your web
application reacts to certain HTTP requests.

> Internally, this implementation builds on top of an existing outgoing
request message and only adds required server methods. This base class is
> Internally, this implementation builds on top of a base class which is
considered an implementation detail that may change in the future.

#### ResponseException
Expand Down
156 changes: 156 additions & 0 deletions src/Io/AbstractRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

namespace React\Http\Io;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use RingCentral\Psr7\Uri;

/**
* [Internal] Abstract HTTP request base class (PSR-7)
*
* @internal
* @see RequestInterface
*/
abstract class AbstractRequest extends AbstractMessage implements RequestInterface
{
/** @var ?string */
private $requestTarget;

/** @var string */
private $method;

/** @var UriInterface */
private $uri;

/**
* @param string $method
* @param string|UriInterface $uri
* @param array<string,string|string[]> $headers
* @param StreamInterface $body
* @param string unknown $protocolVersion
*/
protected function __construct(
$method,
$uri,
array $headers,
StreamInterface $body,
$protocolVersion
) {
if (\is_string($uri)) {
$uri = new Uri($uri);
} elseif (!$uri instanceof UriInterface) {
throw new \InvalidArgumentException(
'Argument #2 ($uri) expected string|Psr\Http\Message\UriInterface'
);
}

// assign default `Host` request header from URI unless already given explicitly
$host = $uri->getHost();
if ($host !== '') {
foreach ($headers as $name => $value) {
if (\strtolower($name) === 'host' && $value !== array()) {
$host = '';
break;
}
}
if ($host !== '') {
$port = $uri->getPort();
if ($port !== null && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
$host .= ':' . $port;
}

$headers = array('Host' => $host) + $headers;
}
}

parent::__construct($protocolVersion, $headers, $body);

$this->method = $method;
$this->uri = $uri;
}

public function getRequestTarget()
{
if ($this->requestTarget !== null) {
return $this->requestTarget;
}

$target = $this->uri->getPath();
if ($target === '') {
$target = '/';
}
if (($query = $this->uri->getQuery()) !== '') {
$target .= '?' . $query;
}

return $target;
}

public function withRequestTarget($requestTarget)
{
if ((string) $requestTarget === $this->requestTarget) {
return $this;
}

$request = clone $this;
$request->requestTarget = (string) $requestTarget;

return $request;
}

public function getMethod()
{
return $this->method;
}

public function withMethod($method)
{
if ((string) $method === $this->method) {
return $this;
}

$request = clone $this;
$request->method = (string) $method;

return $request;
}

public function getUri()
{
return $this->uri;
}

public function withUri(UriInterface $uri, $preserveHost = false)
{
if ($uri === $this->uri) {
return $this;
}

$request = clone $this;
$request->uri = $uri;

$host = $uri->getHost();
$port = $uri->getPort();
if ($port !== null && $host !== '' && (!($port === 80 && $uri->getScheme() === 'http') || !($port === 443 && $uri->getScheme() === 'https'))) {
$host .= ':' . $port;
}

// update `Host` request header if URI contains a new host and `$preserveHost` is false
if ($host !== '' && (!$preserveHost || $request->getHeaderLine('Host') === '')) {
// first remove all headers before assigning `Host` header to ensure it always comes first
foreach (\array_keys($request->getHeaders()) as $name) {
$request = $request->withoutHeader($name);
}

// add `Host` header first, then all other original headers
$request = $request->withHeader('Host', $host);
foreach ($this->withoutHeader('Host')->getHeaders() as $name => $value) {
$request = $request->withHeader($name, $value);
}
}

return $request;
}
}
7 changes: 3 additions & 4 deletions src/Message/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use React\Http\Io\AbstractRequest;
use React\Http\Io\BufferedBody;
use React\Http\Io\ReadableBodyStream;
use React\Stream\ReadableStreamInterface;
use RingCentral\Psr7\Request as BaseRequest;

/**
* Respresents an outgoing HTTP request message.
Expand All @@ -22,13 +22,12 @@
* message for the HTTP client implementation. Likewise, you can also use this
* class with other HTTP client implementations and for tests.
*
* > Internally, this implementation builds on top of an existing outgoing
* request message and only adds support for streaming. This base class is
* > Internally, this implementation builds on top of a base class which is
* considered an implementation detail that may change in the future.
*
* @see RequestInterface
*/
final class Request extends BaseRequest implements RequestInterface
final class Request extends AbstractRequest implements RequestInterface
{
/**
* @param string $method HTTP method for the request.
Expand Down
25 changes: 10 additions & 15 deletions src/Message/ServerRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;
use React\Http\Io\AbstractRequest;
use React\Http\Io\BufferedBody;
use React\Http\Io\HttpBodyStream;
use React\Stream\ReadableStreamInterface;
use RingCentral\Psr7\Request as BaseRequest;

/**
* Respresents an incoming server request message.
Expand All @@ -24,13 +24,12 @@
* Likewise, you can also use this class in test cases to test how your web
* application reacts to certain HTTP requests.
*
* > Internally, this implementation builds on top of an existing outgoing
* request message and only adds required server methods. This base class is
* > Internally, this implementation builds on top of a base class which is
* considered an implementation detail that may change in the future.
*
* @see ServerRequestInterface
*/
final class ServerRequest extends BaseRequest implements ServerRequestInterface
final class ServerRequest extends AbstractRequest implements ServerRequestInterface
{
private $attributes = array();

Expand All @@ -57,26 +56,22 @@ public function __construct(
$version = '1.1',
$serverParams = array()
) {
$stream = null;
if (\is_string($body)) {
$body = new BufferedBody($body);
} elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) {
$stream = $body;
$body = null;
$temp = new self($method, '', $headers);
$size = (int) $temp->getHeaderLine('Content-Length');
if (\strtolower($temp->getHeaderLine('Transfer-Encoding')) === 'chunked') {
$size = null;
}
$body = new HttpBodyStream($body, $size);
} elseif (!$body instanceof StreamInterface) {
throw new \InvalidArgumentException('Invalid server request body given');
}

$this->serverParams = $serverParams;
parent::__construct($method, $url, $headers, $body, $version);

if ($stream !== null) {
$size = (int) $this->getHeaderLine('Content-Length');
if (\strtolower($this->getHeaderLine('Transfer-Encoding')) === 'chunked') {
$size = null;
}
$this->stream = new HttpBodyStream($stream, $size);
}
$this->serverParams = $serverParams;

$query = $this->getUri()->getQuery();
if ($query !== '') {
Expand Down

0 comments on commit 5873b89

Please sign in to comment.