8000 [HttpClient] added support for BrowserKit · symfony/symfony@feebfee · GitHub
[go: up one dir, main page]

Skip to content

Commit feebfee

Browse files
committed
[HttpClient] added support for BrowserKit
1 parent 4574f85 commit feebfee

File tree

4 files changed

+151
-1
lines changed

4 files changed

+151
-1
lines changed

src/Symfony/Component/BrowserKit/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.3.0
55
-----
66

7+
* Added `HttpBrowser`, an implementation of a browser with the HttpClient component
78
* Renamed `Client` to `AbstractBrowser`
89
* Marked `Response` final.
910
* Deprecated `Response::buildHeader()`
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\BrowserKit;
13+
14+
use Psr\Log\LoggerInterface;
15+
use Psr\Log\NullLogger;
16+
use Symfony\Component\HttpClient\HttpClient;
17+
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
18+
use Symfony\Component\HttpKernel\HttpCache\Store;
19+
use Symfony\Component\HttpKernel\HttpKernelBrowser;
20+
use Symfony\Component\HttpKernel\RealHttpKernel;
21+
use Symfony\Component\Mime\Part\AbstractPart;
22+
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
23+
use Symfony\Component\Mime\Part\TextPart;
24+
use Symfony\Contracts\HttpClient\HttpClientInterface;
25+
26+
/**
27+
* An implementation of a browser using the HttpClient component
28+
* to make real HTTP requests.
29+
*
30+
* @author Fabien Potencier <fabien@symfony.com>
31+
*/
32+
final class HttpBrowser extends AbstractBrowser
33+
{
34+
private $client;
35+
private $store;
36+
private $logger;
37+
private $cache;
38+
private $httpKernelBrowser;
39+
40+
public function __construct(HttpClientInterface $client = null, Store $store = null, LoggerInterface $logger = null)
41+
{
42+
if (!class_exists(HttpClient::class)) {
43+
throw new \LogicException(sprintf('You cannot use "%s" as the HttpClient component is not installed. Try running "composer require symfony/http-client".', __CLASS__));
44+
}
45+
46+
$this->client = $client ?? HttpClient::create();
47+
$this->store = $store;
48+
$this->logger = $logger ?? new NullLogger();
49+
50+
parent::__construct();
51+
}
52+
53+
protected function doRequest($request)
54+
{
55+
if ($this->store) {
56+
return $this->doCachedHttpRequest($request);
57+
}
58+
59+
$this->logger->debug(sprintf('Request: %s %s', strtoupper($request->getMethod()), $request->getUri()));
60+
61+
$headers = $this->getHeaders($request);
62+
$body = '';
63+
if (null !== $part = $this->getBody($request)) {
64+
$headers = array_merge($headers, $part->getPreparedHeaders()->toArray());
65+
$body = $part->bodyToIterable();
66+
}
67+
$response = $this->client->request($request->getMethod(), $request->getUri(), [
68+
'headers' => $headers,
69+
'body' => $body,
70+
'max_redirects' => 0,
71+
]);
72+
73+
$this->logger->debug(sprintf('Response: %s %s', $response->getStatusCode(), $request->getUri()));
74+
75+
return new Response($response->getContent(false), $response->getStatusCode(), $response->getHeaders(false));
76+
}
77+
78+
private function doCachedHttpRequest(Request $request): Response
79+
{
80+
if (null === $this->cache) {
81+
if (!class_exists(RealHttpKernel::class)) {
82+
throw new \LogicException('You cannot use a cached HTTP browser as the HttpKernel component is not installed. Try running "composer require symfony/http-kernel".');
83+
}
84+
85+
$kernel = new RealHttpKernel($this->client, $this->logger);
86+
$this->cache = new HttpCache($kernel, $this->store, null, ['debug' => !$this->logger instanceof NullLogger]);
87+
$this->httpKernelBrowser = new HttpKernelBrowser($kernel);
88+
}
89+
90+
$response = $this->cache->handle($this->httpKernelBrowser->filterRequest($request));
91+
$this->logger->debug('Cache: '.$response->headers->get('X-Symfony-Cache'));
92+
93+
return $this->httpKernelBrowser->filterResponse($response);
94+
}
95+
96+
private function getBody(Request $request): ?AbstractPart
97+
{
98+
if (\in_array($request->getMethod(), ['GET', 'HEAD'])) {
99+
return null;
100+
}
101+
102+
if (!class_exists(AbstractPart::class)) {
103+
throw new \LogicException('You cannot pass non-empty bodies as the Mime component is not installed. Try running "composer require symfony/mime".');
104+
}
105+
106+
if (null !== $content = $request->getContent()) {
107+
return new TextPart($content, 'utf-8', 'plain', '8bit');
108+
}
109+
110+
$fields = $request->getParameters();
111+
foreach ($request->getFiles() as $name => $file) {
112+
if (!isset($file['tmp_name'])) {
113+
continue;
114+
}
115+
116+
$fields[$name] = DataPart::fromPath($file['tmp_name'], $file['name']);
117+
}
118+
119+
return new FormDataPart($fields);
120+
}
121+
122+
private function getHeaders(Request $request): array
123+
{
124+
$headers = [];
125+
foreach ($request->getServer() as $key => $value) {
126+
$key = strtolower(str_replace('_', '-', $key));
127+
$contentHeaders = ['content-length' => true, 'content-md5' => true, 'content-type' => true];
128+
if (0 === strpos($key, 'http-')) {
129+
$headers[substr($key, 5)] = $value;
130+
} elseif (isset($contentHeaders[$key])) {
131+
// CONTENT_* are not prefixed with HTTP_
132+
$headers[$key] = $value;
133+
}
134+
}
135+
$cookies = [];
136+
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
137+
$cookies[] = $name.'='.$value;
138+
}
139+
if ($cookies) {
140+
$headers['cookie'] = implode('; ', $cookies);
141+
}
142+
143+
return $headers;
144+
}
145+
}

src/Symfony/Component/BrowserKit/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ BrowserKit Component
44
The BrowserKit component simulates the behavior of a web browser, allowing you
55
to make requests, click on links and submit forms programmatically.
66

7+
The component comes with a concrete implementation that uses the HttpClient
8+
component to make real HTTP requests.
9+
710
Resources
811
---------
912

src/Symfony/Component/BrowserKit/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
},
2222
"require-dev": {
2323
"symfony/process": "~3.4|~4.0",
24-
"symfony/css-selector": "~3.4|~4.0"
24+
"symfony/css-selector": "~3.4|~4.0",
25+
"symfony/mime": "^4.3"
2526
},
2627
"suggest": {
2728
"symfony/process": ""

0 commit comments

Comments
 (0)
0