feature #30602 [BrowserKit] Add support for HttpClient (fabpot, THERAGE Kévin)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[BrowserKit] Add support for HttpClient

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | part of #30502
| License       | MIT
| Doc PR        | not yet

When combining the power of the new HttpClient component with the BrowserKit and Mime components, we can makes something really powerful... a full/better/awesome replacement for https://github.com/FriendsOfPHP/Goutte.

So, this PR is about integrating the HttpClient component with BrowserKit to give users a high-level interface to ease usages in the most common use cases.

Scraping websites can be done like this:

```php
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\HttpClient;

$client = HttpClient::create();
$browser = new HttpBrowser($client);

$browser->request('GET', 'https://example.com/');
$browser->clickLink('Log In');
$browser->submitForm('Sign In', ['username' => 'me', 'password' => 'pass']);
$browser->clickLink('Subscriptions')->filter('table tr:nth-child(2) td:nth-child(2)')->each(function ($node) {
    echo trim($node->text())."\n";
});
```

And voilà! Nice, isn't?

Want to add HTTP cache? Sure:

```php
use Symfony\Component\HttpKernel\HttpCache\Store;

$client = HttpClient::create();
$store = new Store(sys_get_temp_dir().'/http-cache-store');

$browser = new HttpBrowser($client, $store);

// ...
```

Want logging and debugging of HTTP Cache? Yep:

```php
use Psr\Log\AbstractLogger;

class EchoLogger extends AbstractLogger
{
    public function log($level, $message, array $context = [])
    {
        echo $message."\n";
    }
}

$browser = new HttpBrowser($client, $store, new EchoLogger());
```

The first time you run your code, you will get an output similar to:

```
Request: GET https://twig.symfony.com/
Response: 200 https://twig.symfony.com/
Cache: GET /: miss, store
Request: GET https://twig.symfony.com/doc/2.x/
Response: 200 https://twig.symfony.com/doc/2.x/
Cache: GET /doc/2.x/: miss, store
```

But then:

```
Cache: GET /: fresh
Cache: GET /doc/2.x/: fresh
```

Limit is the sky here as you get the full power of all the Symfony ecosystem.

Under the hood, these examples leverage HttpFoundation, HttpKernel (with HttpCache),
DomCrawler, BrowserKit, CssSelector, HttpClient, Mime, ...

Excited?

P.S. : Tests need to wait for the HttpClient Mock class to land into master.

Commits
-------

b5b2a2557c Add tests for HttpBrowser
dd55845706 [BrowserKit] added support for HttpClient
This commit is contained in:
Fabien Potencier 2019-03-23 16:07:52 +01:00
commit d73a53a61c
7 changed files with 306 additions and 71 deletions

View File

@ -4,6 +4,7 @@ CHANGELOG
4.3.0
-----
* Added `HttpBrowser`, an implementation of a browser with the HttpClient component
* Renamed `Client` to `AbstractBrowser`
* Marked `Response` final.
* Deprecated `Response::buildHeader()`

View File

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\BrowserKit;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Mime\Part\AbstractPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Component\Mime\Part\TextPart;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* An implementation of a browser using the HttpClient component
* to make real HTTP requests.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class HttpBrowser extends AbstractBrowser
{
private $client;
public function __construct(HttpClientInterface $client = null, History $history = null, CookieJar $cookieJar = null)
{
if (!class_exists(HttpClient::class)) {
throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
}
$this->client = $client ?? HttpClient::create();
parent::__construct([], $history, $cookieJar);
}
protected function doRequest($request)
{
$headers = $this->getHeaders($request);
$body = '';
if (null !== $part = $this->getBody($request)) {
$headers = array_merge($headers, $part->getPreparedHeaders()->toArray());
$body = $part->bodyToIterable();
}
$response = $this->client->request($request->getMethod(), $request->getUri(), [
'headers' => $headers,
'body' => $body,
'max_redirects' => 0,
]);
return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
}
private function getBody(Request $request): ?AbstractPart
{
if (\in_array($request->getMethod(), ['GET', 'HEAD'])) {
return null;
}
if (!class_exists(AbstractPart::class)) {
throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
}
if (null !== $content = $request->getContent()) {
return new TextPart($content, 'utf-8', 'plain', '8bit');
}
$fields = $request->getParameters();
foreach ($request->getFiles() as $name => $file) {
if (!isset($file['tmp_name'])) {
continue;
}
$fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
}
return new FormDataPart($fields);
}
private function getHeaders(Request $request): array
{
$headers = [];
foreach ($request->getServer() as $key => $value) {
$key = strtolower(str_replace('_', '-', $key));
$contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
if (0 === strpos($key, 'http-')) {
$headers[substr($key, 5)] = $value;
} elseif (isset($contentHeaders[$key])) {
// CONTENT_* are not prefixed with HTTP_
$headers[$key] = $value;
}
}
$cookies = [];
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
$cookies[] = $name.'='.$value;
}
if ($cookies) {
$headers['cookie'] = implode('; ', $cookies);
}
return $headers;
}
}

View File

@ -4,6 +4,9 @@ BrowserKit Component
The BrowserKit component simulates the behavior of a web browser, allowing you
to make requests, click on links and submit forms programmatically.
The component comes with a concrete implementation that uses the HttpClient
component to make real HTTP requests.
Resources
---------

View File

@ -75,21 +75,26 @@ EOF;
class AbstractBrowserTest extends TestCase
{
public function getBrowser(array $server = [], History $history = null, CookieJar $cookieJar = null)
{
return new TestClient($server, $history, $cookieJar);
}
public function testGetHistory()
{
$client = new TestClient([], $history = new History());
$client = $this->getBrowser([], $history = new History());
$this->assertSame($history, $client->getHistory(), '->getHistory() returns the History');
}
public function testGetCookieJar()
{
$client = new TestClient([], null, $cookieJar = new CookieJar());
$client = $this->getBrowser([], null, $cookieJar = new CookieJar());
$this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar');
}
public function testGetRequest()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://example.com/');
$this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request');
@ -101,13 +106,13 @@ class AbstractBrowserTest extends TestCase
*/
public function testGetRequestNull()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertNull($client->getRequest());
}
public function testXmlHttpRequest()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->xmlHttpRequest('GET', 'http://example.com/', [], [], [], null, true);
$this->assertEquals($client->getRequest()->getServer()['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest');
$this->assertFalse($client->getServerParameter('HTTP_X_REQUESTED_WITH', false));
@ -115,7 +120,7 @@ class AbstractBrowserTest extends TestCase
public function testGetRequestWithIpAsHttpHost()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'https://example.com/foo', [], [], ['HTTP_HOST' => '127.0.0.1']);
$this->assertEquals('https://example.com/foo', $client->getRequest()->getUri());
@ -125,7 +130,7 @@ class AbstractBrowserTest extends TestCase
public function testGetResponse()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('foo'));
$client->request('GET', 'http://example.com/');
@ -139,13 +144,13 @@ class AbstractBrowserTest extends TestCase
*/
public function testGetResponseNull()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertNull($client->getResponse());
}
public function testGetInternalResponse()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new SpecialResponse('foo'));
$client->request('GET', 'http://example.com/');
@ -160,7 +165,7 @@ class AbstractBrowserTest extends TestCase
*/
public function testGetInternalResponseNull()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertNull($client->getInternalResponse());
}
@ -168,14 +173,14 @@ class AbstractBrowserTest extends TestCase
{
$json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}';
$client = new TestClient();
$client = $this->getBrowser();
$client->request('POST', 'http://example.com/jsonrpc', [], [], [], $json);
$this->assertEquals($json, $client->getRequest()->getContent());
}
public function testGetCrawler()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('foo'));
$crawler = $client->request('GET', 'http://example.com/');
@ -188,18 +193,18 @@ class AbstractBrowserTest extends TestCase
*/
public function testGetCrawlerNull()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertNull($client->getCrawler());
}
public function testRequestHttpHeaders()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', '/');
$headers = $client->getRequest()->getServer();
$this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com');
$headers = $client->getRequest()->getServer();
$this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header');
@ -208,7 +213,7 @@ class AbstractBrowserTest extends TestCase
$headers = $client->getRequest()->getServer();
$this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com:8080');
$headers = $client->getRequest()->getServer();
$this->assertEquals('www.example.com:8080', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header with port');
@ -216,20 +221,20 @@ class AbstractBrowserTest extends TestCase
public function testRequestURIConversion()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', '/foo');
$this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com');
$this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/');
$client->request('GET', '/foo');
$this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', '#');
$this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #');
@ -238,32 +243,32 @@ class AbstractBrowserTest extends TestCase
$client->request('GET', '#foo');
$this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/');
$client->request('GET', 'bar');
$this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
$this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/');
$client->request('GET', 'http');
$this->assertEquals('http://www.example.com/foo/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', 'http/bar');
$this->assertEquals('http://www.example.com/http/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/');
$client->request('GET', 'http');
$this->assertEquals('http://www.example.com/http', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo');
$client->request('GET', '?');
$this->assertEquals('http://www.example.com/foo?', $client->getRequest()->getUri(), '->request() uses the previous request for ?');
@ -275,7 +280,7 @@ class AbstractBrowserTest extends TestCase
public function testRequestReferer()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
$server = $client->getRequest()->getServer();
@ -284,7 +289,7 @@ class AbstractBrowserTest extends TestCase
public function testRequestHistory()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->request('GET', 'bar');
@ -294,7 +299,7 @@ class AbstractBrowserTest extends TestCase
public function testRequestCookies()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>', 200, ['Set-Cookie' => 'foo=bar']));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals(['foo' => 'bar'], $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar');
@ -305,7 +310,7 @@ class AbstractBrowserTest extends TestCase
public function testRequestSecureCookies()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>', 200, ['Set-Cookie' => 'foo=bar; path=/; secure']));
$client->request('GET', 'https://www.example.com/foo/foobar');
@ -314,7 +319,7 @@ class AbstractBrowserTest extends TestCase
public function testClick()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
@ -325,7 +330,7 @@ class AbstractBrowserTest extends TestCase
public function testClickLink()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><a href="/foo">foo</a></html>'));
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->clickLink('foo');
@ -335,7 +340,7 @@ class AbstractBrowserTest extends TestCase
public function testClickLinkNotFound()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><a href="/foo">foobar</a></html>'));
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -349,7 +354,7 @@ class AbstractBrowserTest extends TestCase
public function testClickForm()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
@ -360,7 +365,7 @@ class AbstractBrowserTest extends TestCase
public function testSubmit()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
@ -371,7 +376,7 @@ class AbstractBrowserTest extends TestCase
public function testSubmitForm()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><form name="signup" action="/foo"><input type="text" name="username" value="the username" /><input type="password" name="password" value="the password" /><input type="submit" value="Register" /></form></html>'));
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -391,7 +396,7 @@ class AbstractBrowserTest extends TestCase
public function testSubmitFormNotFound()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -408,7 +413,7 @@ class AbstractBrowserTest extends TestCase
public function testSubmitPreserveAuth()
{
$client = new TestClient(['PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar']);
$client = $this->getBrowser(['PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar']);
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
@ -431,7 +436,7 @@ class AbstractBrowserTest extends TestCase
public function testSubmitPassthrewHeaders()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('<html><form action="/foo"><input type="submit" /></form></html>'));
$crawler = $client->request('GET', 'http://www.example.com/foo/foobar');
$headers = ['Accept-Language' => 'de'];
@ -445,7 +450,7 @@ class AbstractBrowserTest extends TestCase
public function testFollowRedirect()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->followRedirects(false);
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -462,19 +467,19 @@ class AbstractBrowserTest extends TestCase
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, ['Location' => 'http://www.example.com/redirected']));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() automatically follows redirects if followRedirects is true');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 201, ['Location' => 'http://www.example.com/redirected']));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->followRedirect() does not follow redirect if HTTP Code is not 30x');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 201, ['Location' => 'http://www.example.com/redirected']));
$client->followRedirects(false);
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -489,12 +494,12 @@ class AbstractBrowserTest extends TestCase
public function testFollowRelativeRedirect()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, ['Location' => '/redirected']));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, ['Location' => '/redirected:1234']));
$client->request('GET', 'http://www.example.com/foo/foobar');
$this->assertEquals('http://www.example.com/redirected:1234', $client->getRequest()->getUri(), '->followRedirect() follows relative urls');
@ -502,7 +507,7 @@ class AbstractBrowserTest extends TestCase
public function testFollowRedirectWithMaxRedirects()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->setMaxRedirects(1);
$client->setNextResponse(new Response('', 302, ['Location' => 'http://www.example.com/redirected']));
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -525,13 +530,13 @@ class AbstractBrowserTest extends TestCase
$this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, ['Location' => '//www.example.org/']));
$client->request('GET', 'https://www.example.com/');
$this->assertEquals('https://www.example.org/', $client->getRequest()->getUri(), '->followRedirect() follows protocol-relative URLs');
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, ['Location' => 'http://www.example.com/redirected']));
$client->request('POST', 'http://www.example.com/foo/foobar', ['name' => 'bar']);
@ -541,7 +546,7 @@ class AbstractBrowserTest extends TestCase
public function testFollowRedirectWithCookies()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->followRedirects(false);
$client->setNextResponse(new Response('', 302, [
'Location' => 'http://www.example.com/redirected',
@ -562,7 +567,7 @@ class AbstractBrowserTest extends TestCase
'HTTPS' => false,
];
$client = new TestClient();
$client = $this->getBrowser();
$client->followRedirects(false);
$client->setNextResponse(new Response('', 302, [
'Location' => 'http://www.example.com/redirected',
@ -589,7 +594,7 @@ class AbstractBrowserTest extends TestCase
'HTTP_REFERER' => 'http://www.example.com:8080/',
];
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 302, [
'Location' => 'http://www.example.com:8080/redirected',
]));
@ -600,7 +605,7 @@ class AbstractBrowserTest extends TestCase
public function testIsFollowingRedirects()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertTrue($client->isFollowingRedirects(), '->getFollowRedirects() returns default value');
$client->followRedirects(false);
$this->assertFalse($client->isFollowingRedirects(), '->getFollowRedirects() returns assigned value');
@ -608,7 +613,7 @@ class AbstractBrowserTest extends TestCase
public function testGetMaxRedirects()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertEquals(-1, $client->getMaxRedirects(), '->getMaxRedirects() returns default value');
$client->setMaxRedirects(3);
$this->assertEquals(3, $client->getMaxRedirects(), '->getMaxRedirects() returns assigned value');
@ -621,7 +626,7 @@ class AbstractBrowserTest extends TestCase
$server = ['X_TEST_FOO' => 'bazbar'];
$content = 'foobarbaz';
$client = new TestClient();
$client = $this->getBrowser();
$client->setNextResponse(new Response('', 307, ['Location' => 'http://www.example.com/redirected']));
$client->request('POST', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content);
@ -641,7 +646,7 @@ class AbstractBrowserTest extends TestCase
$server = ['X_TEST_FOO' => 'bazbar'];
$content = 'foobarbaz';
$client = new TestClient();
$client = $this->getBrowser();
foreach ([301, 302, 303] as $code) {
$client->setNextResponse(new Response('', $code, ['Location' => 'http://www.example.com/redirected']));
@ -661,7 +666,7 @@ class AbstractBrowserTest extends TestCase
*/
public function testFollowMetaRefresh(string $content, string $expectedEndingUrl, bool $followMetaRefresh = true)
{
$client = new TestClient();
$client = $this->getBrowser();
$client->followMetaRefresh($followMetaRefresh);
$client->setNextResponse(new Response($content));
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -691,7 +696,7 @@ class AbstractBrowserTest extends TestCase
public function testBack()
{
$client = new TestClient();
$client = $this->getBrowser();
$parameters = ['foo' => 'bar'];
$files = ['myfile.foo' => 'baz'];
@ -711,7 +716,7 @@ class AbstractBrowserTest extends TestCase
public function testForward()
{
$client = new TestClient();
$client = $this->getBrowser();
$parameters = ['foo' => 'bar'];
$files = ['myfile.foo' => 'baz'];
@ -732,7 +737,7 @@ class AbstractBrowserTest extends TestCase
public function testBackAndFrowardWithRedirects()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo');
$client->setNextResponse(new Response('', 301, ['Location' => 'http://www.example.com/redirected']));
@ -751,7 +756,7 @@ class AbstractBrowserTest extends TestCase
public function testReload()
{
$client = new TestClient();
$client = $this->getBrowser();
$parameters = ['foo' => 'bar'];
$files = ['myfile.foo' => 'baz'];
@ -770,7 +775,7 @@ class AbstractBrowserTest extends TestCase
public function testRestart()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'http://www.example.com/foo/foobar');
$client->restart();
@ -780,7 +785,7 @@ class AbstractBrowserTest extends TestCase
public function testInsulatedRequests()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->insulate();
$client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar')");
$client->request('GET', 'http://www.example.com/foo/foobar');
@ -799,7 +804,7 @@ class AbstractBrowserTest extends TestCase
public function testGetServerParameter()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
$this->assertEquals('testvalue', $client->getServerParameter('testkey', 'testvalue'));
@ -807,7 +812,7 @@ class AbstractBrowserTest extends TestCase
public function testSetServerParameter()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
@ -821,7 +826,7 @@ class AbstractBrowserTest extends TestCase
public function testSetServerParameterInRequest()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertEquals('', $client->getServerParameter('HTTP_HOST'));
$this->assertEquals('Symfony BrowserKit', $client->getServerParameter('HTTP_USER_AGENT'));
@ -855,7 +860,7 @@ class AbstractBrowserTest extends TestCase
public function testRequestWithRelativeUri()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', '/', [], [], [
'HTTP_HOST' => 'testhost',
@ -872,7 +877,7 @@ class AbstractBrowserTest extends TestCase
public function testInternalRequest()
{
$client = new TestClient();
$client = $this->getBrowser();
$client->request('GET', 'https://www.example.com/https/www.example.com', [], [], [
'HTTP_HOST' => 'testhost',
@ -890,7 +895,7 @@ class AbstractBrowserTest extends TestCase
*/
public function testInternalRequestNull()
{
$client = new TestClient();
$client = $this->getBrowser();
$this->assertNull($client->getInternalRequest());
}

View File

@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\BrowserKit\Tests;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\BrowserKit\Response;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
class SpecialHttpResponse extends Response
{
}
class TestHttpClient extends HttpBrowser
{
protected $nextResponse = null;
protected $nextScript = null;
public function __construct(array $server = [], History $history = null, CookieJar $cookieJar = null)
{
$client = new MockHttpClient(function (string $method, string $url, array $options) {
if (null === $this->nextResponse) {
return new MockResponse();
}
return new MockResponse($this->nextResponse->getContent(), [
'http_code' => $this->nextResponse->getStatusCode(),
'raw_headers' => $this->nextResponse->getHeaders(),
]);
});
parent::__construct($client);
$this->setServerParameters($server);
$this->history = $history ?? new History();
$this->cookieJar = $cookieJar ?? new CookieJar();
}
public function setNextResponse(Response $response)
{
$this->nextResponse = $response;
}
public function setNextScript($script)
{
$this->nextScript = $script;
}
protected function filterResponse($response)
{
if ($response instanceof SpecialHttpResponse) {
return new Response($response->getContent(), $response->getStatusCode(), $response->getHeaders());
}
return $response;
}
protected function doRequest($request)
{
$response = parent::doRequest($request);
if (null === $this->nextResponse) {
return $response;
}
$class = \get_class($this->nextResponse);
$response = new $class($response->getContent(), $response->getStatusCode(), $response->getHeaders());
$this->nextResponse = null;
return $response;
}
protected function getScript($request)
{
$r = new \ReflectionClass('Symfony\Component\BrowserKit\Response');
$path = $r->getFileName();
return <<<EOF
<?php
require_once('$path');
echo serialize($this->nextScript);
EOF;
}
}
class HttpBrowserTest extends AbstractBrowserTest
{
public function getBrowser(array $server = [], History $history = null, CookieJar $cookieJar = null)
{
return new TestHttpClient($server, $history, $cookieJar);
}
public function testGetInternalResponse()
{
$client = $this->getBrowser();
$client->setNextResponse(new SpecialHttpResponse('foo'));
$client->request('GET', 'http://example.com/');
$this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse());
$this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialHttpResponse', $client->getInternalResponse());
$this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialHttpResponse', $client->getResponse());
}
}

View File

@ -20,8 +20,10 @@
"symfony/dom-crawler": "~3.4|~4.0"
},
"require-dev": {
"symfony/process": "~3.4|~4.0",
"symfony/css-selector": "~3.4|~4.0"
"symfony/css-selector": "~3.4|~4.0",
"symfony/http-client": "^4.3",
"symfony/mime": "^4.3",
"symfony/process": "~3.4|~4.0"
},
"suggest": {
"symfony/process": ""

View File

@ -56,7 +56,7 @@ class MockResponse implements ResponseInterface
}
}
$info['raw_headers'] = $rawHeaders;
$this->info['raw_headers'] = $rawHeaders;
}
/**