Skip to content

Commit 1d7790e

Browse files
committed
Add new Uri class for new PSR-7 implementation
1 parent 0638dcd commit 1d7790e

File tree

8 files changed

+881
-5
lines changed

8 files changed

+881
-5
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ multiple concurrent HTTP requests without blocking.
7979
* [xml()](#xml)
8080
* [Request](#request-1)
8181
* [ServerRequest](#serverrequest)
82+
* [Uri](#uri)
8283
* [ResponseException](#responseexception)
8384
* [React\Http\Middleware](#reacthttpmiddleware)
8485
* [StreamingRequestMiddleware](#streamingrequestmiddleware)
@@ -2664,6 +2665,18 @@ application reacts to certain HTTP requests.
26642665
> Internally, this implementation builds on top of a base class which is
26652666
considered an implementation detail that may change in the future.
26662667

2668+
#### Uri
2669+
2670+
The `React\Http\Message\Uri` class can be used to
2671+
respresent a URI (or URL).
2672+
2673+
This class implements the
2674+
[PSR-7 `UriInterface`](https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface).
2675+
2676+
This is mostly used internally to represent the URI of each HTTP request
2677+
message for our HTTP client and server implementations. Likewise, you may
2678+
also use this class with other HTTP implementations and for tests.
2679+
26672680
#### ResponseException
26682681

26692682
The `React\Http\Message\ResponseException` is an `Exception` sub-class that will be used to reject

src/Io/AbstractRequest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Psr\Http\Message\RequestInterface;
66
use Psr\Http\Message\StreamInterface;
77
use Psr\Http\Message\UriInterface;
8-
use RingCentral\Psr7\Uri;
8+
use React\Http\Message\Uri;
99

1010
/**
1111
* [Internal] Abstract HTTP request base class (PSR-7)

src/Message/Uri.php

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
<?php
2+
3+
namespace React\Http\Message;
4+
5+
use Psr\Http\Message\UriInterface;
6+
7+
/**
8+
* Respresents a URI (or URL).
9+
*
10+
* This class implements the
11+
* [PSR-7 `UriInterface`](https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface).
12+
*
13+
* This is mostly used internally to represent the URI of each HTTP request
14+
* message for our HTTP client and server implementations. Likewise, you may
15+
* also use this class with other HTTP implementations and for tests.
16+
*
17+
* @see UriInterface
18+
*/
19+
final class Uri implements UriInterface
20+
{
21+
/** @var string */
22+
private $scheme = '';
23+
24+
/** @var string */
25+
private $userInfo = '';
26+
27+
/** @var string */
28+
private $host = '';
29+
30+
/** @var ?int */
31+
private $port = null;
32+
33+
/** @var string */
34+
private $path = '';
35+
36+
/** @var string */
37+
private $query = '';
38+
39+
/** @var string */
40+
private $fragment = '';
41+
42+
/**
43+
* @param string $uri
44+
* @throws \InvalidArgumentException if given $uri is invalid
45+
*/
46+
public function __construct($uri)
47+
{
48+
$parts = \parse_url($uri);
49+
if ($parts === false || (isset($parts['scheme']) && !\preg_match('#^[a-z]+$#i', $parts['scheme'])) || (isset($parts['host']) && \preg_match('#[\s_%+]#', $parts['host']))) {
50+
throw new \InvalidArgumentException('Invalid URI given');
51+
}
52+
53+
if (isset($parts['scheme'])) {
54+
$this->scheme = \strtolower($parts['scheme']);
55+
}
56+
57+
if (isset($parts['user']) || isset($parts['pass'])) {
58+
$this->userInfo = $this->encode(isset($parts['user']) ? $parts['user'] : '', \PHP_URL_USER) . (isset($parts['pass']) ? ':' . $this->encode($parts['pass'], \PHP_URL_PASS) : '');
59+
}
60+
61+
if (isset($parts['host'])) {
62+
$this->host = \strtolower($parts['host']);
63+
}
64+
65+
if (isset($parts['port']) && !(($parts['port'] === 80 && $this->scheme === 'http') || ($parts['port'] === 443 && $this->scheme === 'https'))) {
66+
$this->port = $parts['port'];
67+
}
68+
69+
if (isset($parts['path'])) {
70+
$this->path = $this->encode($parts['path'], \PHP_URL_PATH);
71+
}
72+
73+
if (isset($parts['query'])) {
74+
$this->query = $this->encode($parts['query'], \PHP_URL_QUERY);
75+
}
76+
77+
if (isset($parts['fragment'])) {
78+
$this->fragment = $this->encode($parts['fragment'], \PHP_URL_FRAGMENT);
79+
}
80+
}
81+
82+
public function getScheme()
83+
{
84+
return $this->scheme;
85+
}
86+
87+
public function getAuthority()
88+
{
89+
if ($this->host === '') {
90+
return '';
91+
}
92+
93+
return ($this->userInfo !== '' ? $this->userInfo . '@' : '') . $this->host . ($this->port !== null ? ':' . $this->port : '');
94+
}
95+
96+
public function getUserInfo()
97+
{
98+
return $this->userInfo;
99+
}
100+
101+
public function getHost()
102+
{
103+
return $this->host;
104+
}
105+
106+
public function getPort()
107+
{
108+
return $this->port;
109+
}
110+
111+
public function getPath()
112+
{
113+
return $this->path;
114+
}
115+
116+
public function getQuery()
117+
{
118+
return $this->query;
119+
}
120+
121+
public function getFragment()
122+
{
123+
return $this->fragment;
124+
}
125+
126+
public function withScheme($scheme)
127+
{
128+
$scheme = \strtolower($scheme);
129+
if ($scheme === $this->scheme) {
130+
return $this;
131+
}
132+
133+
if (!\preg_match('#^[a-z]*$#', $scheme)) {
134+
throw new \InvalidArgumentException('Invalid URI scheme given');
135+
}
136+
137+
$new = clone $this;
138+
$new->scheme = $scheme;
139+
140+
if (($this->port === 80 && $scheme === 'http') || ($this->port === 443 && $scheme === 'https')) {
141+
$new->port = null;
142+
}
143+
144+
return $new;
145+
}
146+
147+
public function withUserInfo($user, $password = null)
148+
{
149+
$userInfo = $this->encode($user, \PHP_URL_USER) . ($password !== null ? ':' . $this->encode($password, \PHP_URL_PASS) : '');
150+
if ($userInfo === $this->userInfo) {
151+
return $this;
152+
}
153+
154+
$new = clone $this;
155+
$new->userInfo = $userInfo;
156+
157+
return $new;
158+
}
159+
160+
public function withHost($host)
161+
{
162+
$host = \strtolower($host);
163+
if ($host === $this->host) {
164+
return $this;
165+
}
166+
167+
if (\preg_match('#[\s_%+]#', $host) || ($host !== '' && \parse_url('http://' . $host, \PHP_URL_HOST) !== $host)) {
168+
throw new \InvalidArgumentException('Invalid URI host given');
169+
}
170+
171+
$new = clone $this;
172+
$new->host = $host;
173+
174+
return $new;
175+
}
176+
177+
public function withPort($port)
178+
{
179+
$port = $port === null ? null : (int) $port;
180+
if (($port === 80 && $this->scheme === 'http') || ($port === 443 && $this->scheme === 'https')) {
181+
$port = null;
182+
}
183+
184+
if ($port === $this->port) {
185+
return $this;
186+
}
187+
188+
if ($port !== null && ($port < 1 || $port > 0xffff)) {
189+
throw new \InvalidArgumentException('Invalid URI port given');
190+
}
191+
192+
$new = clone $this;
193+
$new->port = $port;
194+
195+
return $new;
196+
}
197+
198+
public function withPath($path)
199+
{
200+
$path = $this->encode($path, \PHP_URL_PATH);
201+
if ($path === $this->path) {
202+
return $this;
203+
}
204+
205+
$new = clone $this;
206+
$new->path = $path;
207+
208+
return $new;
209+
}
210+
211+
public function withQuery($query)
212+
{
213+
$query = $this->encode($query, \PHP_URL_QUERY);
214+
if ($query === $this->query) {
215+
return $this;
216+
}
217+
218+
$new = clone $this;
219+
$new->query = $query;
220+
221+
return $new;
222+
}
223+
224+
public function withFragment($fragment)
225+
{
226+
$fragment = $this->encode($fragment, \PHP_URL_FRAGMENT);
227+
if ($fragment === $this->fragment) {
228+
return $this;
229+
}
230+
231+
$new = clone $this;
232+
$new->fragment = $fragment;
233+
234+
return $new;
235+
}
236+
237+
public function __toString()
238+
{
239+
$uri = '';
240+
if ($this->scheme !== '') {
241+
$uri .= $this->scheme . ':';
242+
}
243+
244+
$authority = $this->getAuthority();
245+
if ($authority !== '') {
246+
$uri .= '//' . $authority;
247+
}
248+
249+
if ($authority !== '' && isset($this->path[0]) && $this->path[0] !== '/') {
250+
$uri .= '/' . $this->path;
251+
} elseif ($authority === '' && isset($this->path[0]) && $this->path[0] === '/') {
252+
$uri .= '/' . \ltrim($this->path, '/');
253+
} else {
254+
$uri .= $this->path;
255+
}
256+
257+
if ($this->query !== '') {
258+
$uri .= '?' . $this->query;
259+
}
260+
261+
if ($this->fragment !== '') {
262+
$uri .= '#' . $this->fragment;
263+
}
264+
265+
return $uri;
266+
}
267+
268+
/**
269+
* @param string $part
270+
* @param int $component
271+
* @return string
272+
*/
273+
private function encode($part, $component)
274+
{
275+
return \preg_replace_callback(
276+
'/(?:[^a-z0-9_\-\.~!\$&\'\(\)\*\+,;=' . ($component === \PHP_URL_PATH ? ':@\/' : ($component === \PHP_URL_QUERY || $component === \PHP_URL_FRAGMENT ? ':@\/\?' : '')) . '%]++|%(?![a-f0-9]{2}))/i',
277+
function (array $match) {
278+
return \rawurlencode($match[0]);
279+
},
280+
$part
281+
);
282+
}
283+
}

tests/BrowserTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Psr\Http\Message\RequestInterface;
66
use React\Http\Browser;
77
use React\Promise\Promise;
8-
use RingCentral\Psr7\Uri;
98

109
class BrowserTest extends TestCase
1110
{

tests/Io/AbstractRequestTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
use Psr\Http\Message\StreamInterface;
66
use Psr\Http\Message\UriInterface;
77
use React\Http\Io\AbstractRequest;
8+
use React\Http\Message\Uri;
89
use React\Tests\Http\TestCase;
9-
use RingCentral\Psr7\Uri;
1010

1111
class RequestMock extends AbstractRequest
1212
{

tests/Io/ClientConnectionManagerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace React\Tests\Http\Io;
44

5-
use RingCentral\Psr7\Uri;
65
use React\Http\Io\ClientConnectionManager;
6+
use React\Http\Message\Uri;
77
use React\Promise\Promise;
88
use React\Promise\PromiseInterface;
99
use React\Tests\Http\TestCase;

tests/Io/ClientRequestStreamTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
namespace React\Tests\Http\Io;
44

55
use Psr\Http\Message\ResponseInterface;
6-
use RingCentral\Psr7\Uri;
76
use React\Http\Io\ClientRequestStream;
87
use React\Http\Message\Request;
8+
use React\Http\Message\Uri;
99
use React\Promise\Deferred;
1010
use React\Promise\Promise;
1111
use React\Stream\DuplexResourceStream;

0 commit comments

Comments
 (0)