Skip to content
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ The `getUri()` method can be used to get its [`Uri`](#uri) instance.

### Uri

An `Uri` represents an absolute URI (aka URL).

By definition of this library, an `Uri` instance is always absolute and can not contain any placeholders.
As such, any incomplete/relative URI will be rejected with an `InvalidArgumentException`.

Each [`Request`](#request) contains a (full) absolute request URI.

```
Expand All @@ -265,6 +270,8 @@ assert('/' == $uri->getPath());

See its [class outline](src/Message/Uri.php) for more details.

Internally, this class uses the excellent [ml/iri](https://github.com/lanthaler/IRI) library under the hood.

### ResponseException

The `ResponseException` is an `Exception` sub-class that will be used to reject
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"react/socket-client": "0.3.*|0.4.*",
"react/dns": "0.3.*|0.4.*",
"react/promise": "1.*|2.*",
"rize/uri-template": "~0.3.0"
"rize/uri-template": "~0.3.0",
"ml/iri": "~1.0"
}
}
31 changes: 19 additions & 12 deletions src/Io/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,8 @@ public function onResponse(Response $response, Request $request)
{
$this->progress('response', array($response, $request));

if ($this->followRedirects && ($response->getCode() >= 300 && $response->getCode() < 400 && $location = $response->getHeader('Location'))) {
// naïve approach..
$method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
$request = new Request($method, $location);

$this->progress('redirect', array($request));

if ($this->numRequests >= $this->maxRedirects) {
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
}

return $this->next($request);
if ($this->followRedirects && ($response->getCode() >= 300 && $response->getCode() < 400)) {
return $this->onResponseRedirect($response, $request);
}

// only status codes 200-399 are considered to be valid, reject otherwise
Expand All @@ -94,6 +84,23 @@ public function onError(Exception $error, Request $request)
throw $error;
}

private function onResponseRedirect(Response $response, Request $request)
{
$location = $request->getUri()->resolve($response->getHeader('Location'));

// naïve approach..
$method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
$request = new Request($method, $location);

$this->progress('redirect', array($request));

if ($this->numRequests >= $this->maxRedirects) {
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
}

return $this->next($request);
}

private function progress($name, array $args = array())
{
return;
Expand Down
91 changes: 50 additions & 41 deletions src/Message/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,87 @@
namespace Clue\React\Buzz\Message;

use InvalidArgumentException;
use ML\IRI\IRI;

/**
* An `Uri` represents an absolute URI (aka URL).
*
* By definition of this library, an `Uri` instance is always absolute and can not contain any placeholders.
*/
class Uri
{
private $scheme;
private $host;
private $port;
private $path;
private $query;
private $iri;

/**
* Instantiate new absolute URI instance
*
* By definition of this library, an `Uri` instance is always absolute and can not contain any placeholders.
* As such, any incomplete/relative URI will be rejected with an `InvalidArgumentException`.
*
* @param string|Uri|IRI $uri
* @throws InvalidArgumentException for incomplete/relative URIs
*/
public function __construct($uri)
{
$parts = parse_url($uri);
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['path'])) {
if (!$uri instanceof IRI) {
$uri = new IRI($uri);
}

if (!$uri->isAbsolute() || $uri->getHost() === '' || $uri->getPath() === '') {
throw new InvalidArgumentException('Not a valid absolute URI');
}

if (strpos($uri, '{') !== false) {
throw new \InvalidArgumentException('Contains placeholders');
throw new InvalidArgumentException('Contains placeholders');
}

$this->scheme = $parts['scheme'];
$this->host = $parts['host'];
$this->port = isset($parts['port']) ? $parts['port'] : null;
$this->path = $parts['path'];
$this->query = isset($parts['query']) ? $parts['query'] : '';
$this->iri = $uri;
}

public function __toString()
{
$url = $this->scheme . '://' . $this->host;
if ($this->port !== null) {
$url .= ':' . $this->port;
}
$url .= $this->path;
if ($this->query !== '') {
$url .= '?' . $this->query;
}

return $url;
return (string)$this->iri;
}

public function getScheme()
{
return $this->scheme;
return $this->iri->getScheme();
}

public function getHost()
{
return $this->host;
return $this->iri->getHost();
}

public function getPort()
{
return $this->port;
return $this->iri->getPort();
}

public function getPath()
{
return $this->path;
return $this->iri->getPath();
}

public function getQuery()
{
return $this->query;
return $this->iri->getQuery();
}

/**
* Resolve a (relative) URI reference against this URI
*
* @param string|Uri $uri relative or absolute URI
* @return Uri absolute URI
* @link http://tools.ietf.org/html/rfc3986#section-5.2
* @uses IRI::resolve()
*/
public function resolve($uri)
{
if ($uri instanceof self) {
$uri = (string)$uri;
}
return new self($this->iri->resolve($uri));
}

/**
Expand Down Expand Up @@ -95,25 +112,17 @@ public function expandBase($uri)
return $uri;
}

$new = clone $this;

$pos = strpos($uri, '?');
if ($pos !== false) {
$new->query = substr($uri, $pos + 1);
$uri = substr($uri, 0, $pos);
}
$base = (string)$this;

if ($uri !== '' && substr($new->path, -1) !== '/') {
$new->path .= '/';
if ($uri !== '' && substr($base, -1) !== '/' && substr($uri, 0, 1) !== '?') {
$base .= '/';
}

if (isset($uri[0]) && $uri[0] === '/') {
$uri = substr($uri, 1);
}

$new->path .= $uri;

return (string)$new;
return $base . $uri;
}

/**
Expand All @@ -127,7 +136,7 @@ public function expandBase($uri)
*/
public function assertBaseOf(Uri $new)
{
if ($new->scheme !== $this->scheme || $new->host !== $this->host || $new->port !== $this->port || strpos($new->path, $this->path) !== 0) {
if (strpos((string)$new, (string)$this) !== 0) {
throw new \UnexpectedValueException('Invalid base, "' . $new . '" does not appear to be below "' . $this . '"');
}

Expand Down
9 changes: 8 additions & 1 deletion tests/FunctionalBrowserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ public function testSimpleRequest()
$this->loop->run();
}

public function testRedirectRequest()
public function testRedirectRequestRelative()
{
$this->expectPromiseResolve($this->browser->get($this->base . 'redirect-to?url=get'));

$this->loop->run();
}

public function testRedirectRequestAbsolute()
{
$this->expectPromiseResolve($this->browser->get($this->base . 'redirect-to?url=' . urlencode($this->base . 'get')));

Expand Down
25 changes: 25 additions & 0 deletions tests/Message/UriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,29 @@ public function testAssertNotBase($other)

$base->assertBaseOf(new Uri($other));
}

public function testResolveRelative()
{
$base = new Uri('http://example.com/base/');

$this->assertEquals('http://example.com/base/', $base->resolve(''));
$this->assertEquals('http://example.com/', $base->resolve('/'));

$this->assertEquals('http://example.com/base/a', $base->resolve('a'));
$this->assertEquals('http://example.com/a', $base->resolve('../a'));
}

public function testResolveAbsolute()
{
$base = new Uri('http://example.org/');

$this->assertEquals('http://www.example.com/', $base->resolve('http://www.example.com/'));
}

public function testResolveUri()
{
$base = new Uri('http://example.org/');

$this->assertEquals('http://www.example.com/', $base->resolve(new Uri('http://www.example.com/')));
}
}