Skip to content

Commit 8dc21a3

Browse files
valgaclue
authored andcommitted
Allow to supply custom HTTP headers
1 parent bac60c2 commit 8dc21a3

File tree

3 files changed

+81
-5
lines changed

3 files changed

+81
-5
lines changed

examples/03-custom-proxy-headers.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
// A simple example which requests https://google.com/ through an HTTP CONNECT proxy.
4+
// The proxy can be given as first argument and defaults to localhost:8080 otherwise.
5+
6+
use Clue\React\HttpProxy\ProxyConnector;
7+
use React\Socket\Connector;
8+
use React\Socket\ConnectionInterface;
9+
use RingCentral\Psr7;
10+
11+
require __DIR__ . '/../vendor/autoload.php';
12+
13+
$url = isset($argv[1]) ? $argv[1] : '127.0.0.1:8080';
14+
15+
$loop = React\EventLoop\Factory::create();
16+
17+
$proxy = new ProxyConnector($url, new Connector($loop), array(
18+
'X-Custom-Header-1' => 'Value-1',
19+
'X-Custom-Header-2' => 'Value-2',
20+
));
21+
$connector = new Connector($loop, array(
22+
'tcp' => $proxy,
23+
'timeout' => 3.0,
24+
'dns' => false,
25+
));
26+
27+
$connector->connect('tls://google.com:443')->then(function (ConnectionInterface $stream) {
28+
$stream->write("GET / HTTP/1.1\r\nHost: google.com\r\nConnection: close\r\n\r\n");
29+
$stream->on('data', function ($chunk) {
30+
echo $chunk;
31+
});
32+
}, 'printf');
33+
34+
$loop->run();

src/ProxyConnector.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class ProxyConnector implements ConnectorInterface
4444
private $connector;
4545
private $proxyUri;
4646
private $proxyAuth = '';
47+
/** @var array */
48+
private $proxyHeaders;
4749

4850
/**
4951
* Instantiate a new ProxyConnector which uses the given $proxyUrl
@@ -54,9 +56,10 @@ class ProxyConnector implements ConnectorInterface
5456
* @param ConnectorInterface $connector In its most simple form, the given
5557
* connector will be a \React\Socket\Connector if you want to connect to
5658
* a given IP address.
59+
* @param array $httpHeaders Custom HTTP headers to be sent to the proxy.
5760
* @throws InvalidArgumentException if the proxy URL is invalid
5861
*/
59-
public function __construct($proxyUrl, ConnectorInterface $connector)
62+
public function __construct($proxyUrl, ConnectorInterface $connector, array $httpHeaders = array())
6063
{
6164
// support `http+unix://` scheme for Unix domain socket (UDS) paths
6265
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
@@ -90,10 +93,12 @@ public function __construct($proxyUrl, ConnectorInterface $connector)
9093

9194
// prepare Proxy-Authorization header if URI contains username/password
9295
if (isset($parts['user']) || isset($parts['pass'])) {
93-
$this->proxyAuth = 'Proxy-Authorization: Basic ' . base64_encode(
96+
$this->proxyAuth = 'Basic ' . base64_encode(
9497
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
95-
) . "\r\n";
98+
);
9699
}
100+
101+
$this->proxyHeaders = $httpHeaders;
97102
}
98103

99104
public function connect($uri)
@@ -152,7 +157,8 @@ public function connect($uri)
152157
});
153158

154159
$auth = $this->proxyAuth;
155-
$connecting->then(function (ConnectionInterface $stream) use ($target, $auth, $deferred) {
160+
$headers = $this->proxyHeaders;
161+
$connecting->then(function (ConnectionInterface $stream) use ($target, $auth, $headers, $deferred) {
156162
// keep buffering data until headers are complete
157163
$buffer = '';
158164
$stream->on('data', $fn = function ($chunk) use (&$buffer, $deferred, $stream, &$fn) {
@@ -212,7 +218,13 @@ public function connect($uri)
212218
$deferred->reject(new RuntimeException('Connection to proxy lost while waiting for response (ECONNRESET)', defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104));
213219
});
214220

215-
$stream->write("CONNECT " . $target . " HTTP/1.1\r\nHost: " . $target . "\r\n" . $auth . "\r\n");
221+
$headers['Host'] = $target;
222+
if ($auth !== '') {
223+
$headers['Proxy-Authorization'] = $auth;
224+
}
225+
$request = new Psr7\Request('CONNECT', $target, $headers);
226+
$request = $request->withRequestTarget($target);
227+
$stream->write(Psr7\str($request));
216228
}, function (Exception $e) use ($deferred) {
217229
$deferred->reject($e = new RuntimeException(
218230
'Unable to connect to proxy (ECONNREFUSED)',

tests/ProxyConnectorTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,36 @@ public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentica
215215
$proxy->connect('google.com:80');
216216
}
217217

218+
public function testWillSendCustomHttpHeadersToProxy()
219+
{
220+
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
221+
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nX-Custom-Header: X-Custom-Value\r\nHost: google.com:80\r\n\r\n");
222+
223+
$promise = \React\Promise\resolve($stream);
224+
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
225+
226+
$proxy = new ProxyConnector('proxy.example.com', $this->connector, array(
227+
'X-Custom-Header' => 'X-Custom-Value',
228+
));
229+
230+
$proxy->connect('google.com:80');
231+
}
232+
233+
public function testWillOverrideProxyAuthorizationHeaderWithCredentialsFromUri()
234+
{
235+
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
236+
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\nHost: google.com:80\r\n\r\n");
237+
238+
$promise = \React\Promise\resolve($stream);
239+
$this->connector->expects($this->once())->method('connect')->willReturn($promise);
240+
241+
$proxy = new ProxyConnector('user:[email protected]', $this->connector, array(
242+
'Proxy-Authorization' => 'foobar',
243+
));
244+
245+
$proxy->connect('google.com:80');
246+
}
247+
218248
public function testRejectsInvalidUri()
219249
{
220250
$this->connector->expects($this->never())->method('connect');

0 commit comments

Comments
 (0)