Skip to content

Refactor Request and ServerRequest classes to build on top of new PSR-7 implementation #519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading