Skip to content

Commit d7d7289

Browse files
committed
Dont add path to an url we already added a path to.
1 parent 19c28ed commit d7d7289

File tree

6 files changed

+141
-17
lines changed

6 files changed

+141
-17
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ matrix:
3030
- php: 7.1
3131
- php: 7.2
3232
env: COVERAGE=true TEST_COMMAND="composer test-ci" DEPENDENCIES="henrikbjorn/phpspec-code-coverage:^1.0"
33+
- php: 7.2
34+
env: TEST_COMMAND="./vendor/bin/phpunit" DEPENDENCIES="phpunit/phpunit:^7.5 nyholm/psr7:^1.0"
3335

3436
# Test LTS versions
3537
- php: 7.1

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
### Changed
66

7-
- [RetryPlugin] Renamed the configuration options for the exception retry callback from `decider` to `exception_decider`
7+
- RetryPlugin: Renamed the configuration options for the exception retry callback from `decider` to `exception_decider`
88
and `delay` to `exception_delay`. The old names still work but are deprecated.
9+
- AddPathPlugin: Do not add the prefix if the URL already has the same prefix.
10+
911

1012
## 1.8.2 - 2018-12-14
1113

phpunit.xml.dist

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="./vendor/autoload.php"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true">
8+
<php>
9+
<env name="SHELL_VERBOSITY" value="-1" />
10+
</php>
11+
12+
<testsuites>
13+
<testsuite name="HTTPlug unit tests">
14+
<directory suffix="Test.php">./tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
</phpunit>

spec/Plugin/AddPathPluginSpec.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ function it_adds_path(
3737
$host->getPath()->shouldBeCalled()->willReturn('/api');
3838

3939
$request->getUri()->shouldBeCalled()->willReturn($uri);
40-
$request->withUri($uri)->shouldBeCalled()->willReturn($request);
40+
$request->withUri($uri)->shouldBeCalledTimes(1)->willReturn($request);
4141

42-
$uri->withPath('/api/users')->shouldBeCalled()->willReturn($uri);
42+
$uri->withPath('/api/users')->shouldBeCalledTimes(1)->willReturn($uri);
4343
$uri->getPath()->shouldBeCalled()->willReturn('/users');
4444

4545
$this->beConstructedWith($host);

src/Plugin/AddPathPlugin.php

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ final class AddPathPlugin implements Plugin
1818
*/
1919
private $uri;
2020

21-
/**
22-
* Stores identifiers of the already altered requests.
23-
*
24-
* @var array
25-
*/
26-
private $alteredRequests = [];
27-
2821
/**
2922
* @param UriInterface $uri
3023
*/
@@ -42,18 +35,43 @@ public function __construct(UriInterface $uri)
4235
}
4336

4437
/**
38+
* Adds a prefix in the beginning of the URL's path.
39+
*
40+
* The prefix is not added if that prefix is already on the URL's path. This will fail on the edge
41+
* case of the prefix being repeated, for example if `https://example.com/api/api/foo` is a valid
42+
* URL on the server and the configured prefix is `/api`.
43+
*
44+
* We looked at other solutions, but they are all much more complicated, while still having edge
45+
* cases:
46+
* - Doing an spl_object_hash on `$first` will lead to collisions over time because over time the
47+
* hash can collide.
48+
* - Have the PluginClient provide a magic header to identify the request chain and only apply
49+
* this plugin once.
50+
*
51+
* There are 2 reasons for the AddPathPlugin to be executed twice on the same request:
52+
* - A plugin can restart the chain by calling `$first`, e.g. redirect
53+
* - A plugin can call `$next` more than once, e.g. retry
54+
*
55+
* Depending on the scenario, the path should or should not be added. E.g. `$first` could
56+
* be called after a redirect response from the server. The server likely already has the
57+
* correct path.
58+
*
59+
* No solution fits all use cases. This implementation will work fine for the common use cases.
60+
* If you have a specific situation where this is not the right thing, you can build a custom plugin
61+
* that does exactly what you need.
62+
*
4563
* {@inheritdoc}
4664
*/
4765
public function handleRequest(RequestInterface $request, callable $next, callable $first)
4866
{
49-
$identifier = spl_object_hash((object) $first);
67+
$prepend = $this->uri->getPath();
68+
$path = $request->getUri()->getPath();
5069

51-
if (!array_key_exists($identifier, $this->alteredRequests)) {
52-
$request = $request->withUri($request->getUri()
53-
->withPath($this->uri->getPath().$request->getUri()->getPath())
54-
);
55-
$this->alteredRequests[$identifier] = $identifier;
56-
}
70+
if (substr($path, 0, strlen($prepend)) !== $prepend) {
71+
$request = $request->withUri($request->getUri()
72+
->withPath($prepend.$path)
73+
);
74+
}
5775

5876
return $next($request);
5977
}

tests/Plugin/AddPathPluginTest.php

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
namespace tests\Http\Client\Common\Plugin;
4+
5+
use Http\Client\Common\Plugin;
6+
use Http\Client\Common\Plugin\AddPathPlugin;
7+
use Http\Client\Common\PluginClient;
8+
use Http\Client\HttpClient;
9+
use Nyholm\Psr7\Request;
10+
use Nyholm\Psr7\Response;
11+
use Nyholm\Psr7\Uri;
12+
use PHPUnit\Framework\TestCase;
13+
use Psr\Http\Message\RequestInterface;
14+
15+
class AddPathPluginTest extends TestCase
16+
{
17+
/**
18+
* @var Plugin
19+
*/
20+
private $plugin;
21+
22+
/**
23+
* @var callable empty
24+
*/
25+
private $first;
26+
27+
protected function setUp()
28+
{
29+
$this->first = function () {};
30+
$this->plugin = new AddPathPlugin(new Uri('/api'));
31+
}
32+
33+
public function testRewriteSameUrl()
34+
{
35+
$verify = function (RequestInterface $request) {
36+
$this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
37+
};
38+
39+
$request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/html']);
40+
$this->plugin->handleRequest($request, $verify, $this->first);
41+
42+
// Make a second call with the same $request object
43+
$this->plugin->handleRequest($request, $verify, $this->first);
44+
45+
// Make a new call with a new object but same URL
46+
$request = new Request('GET', 'https://example.com/foo', ['Content-Type'=>'text/plain']);
47+
$this->plugin->handleRequest($request, $verify, $this->first);
48+
}
49+
50+
public function testRewriteCallingThePluginTwice()
51+
{
52+
$request = new Request('GET', 'https://example.com/foo');
53+
$this->plugin->handleRequest($request, function (RequestInterface $request) {
54+
$this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
55+
56+
// Run the plugin again with the modified request
57+
$this->plugin->handleRequest($request, function (RequestInterface $request) {
58+
$this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
59+
}, $this->first);
60+
}, $this->first);
61+
}
62+
63+
public function testRewriteWithDifferentUrl()
64+
{
65+
$request = new Request('GET', 'https://example.com/foo');
66+
$this->plugin->handleRequest($request, function (RequestInterface $request) {
67+
$this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
68+
}, $this->first);
69+
70+
$request = new Request('GET', 'https://example.com/bar');
71+
$this->plugin->handleRequest($request, function (RequestInterface $request) {
72+
$this->assertEquals('https://example.com/api/bar', $request->getUri()->__toString());
73+
}, $this->first);
74+
}
75+
76+
public function testRewriteWhenPathIsIncluded()
77+
{
78+
$verify = function (RequestInterface $request) {
79+
$this->assertEquals('https://example.com/api/foo', $request->getUri()->__toString());
80+
};
81+
82+
$request = new Request('GET', 'https://example.com/api/foo');
83+
$this->plugin->handleRequest($request, $verify, $this->first);
84+
}
85+
}

0 commit comments

Comments
 (0)