Skip to content

Commit 72a87db

Browse files
authored
Merge pull request #20 from clue-labs/unix
Support communication over Unix domain sockets (UDS)
2 parents c3af12a + 7249458 commit 72a87db

File tree

4 files changed

+84
-3
lines changed

4 files changed

+84
-3
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ built on top of [ReactPHP](https://reactphp.org).
1414
* [DNS resolution](#dns-resolution)
1515
* [Authentication](#authentication)
1616
* [Advanced secure proxy connections](#advanced-secure-proxy-connections)
17+
* [Advanced Unix domain sockets](#advanced-unix-domain-sockets)
1718
* [Install](#install)
1819
* [Tests](#tests)
1920
* [License](#license)
@@ -287,6 +288,42 @@ $proxy = new ProxyConnector('https://127.0.0.1:443', $connector);
287288
$proxy->connect('tcp://smtp.googlemail.com:587');
288289
```
289290

291+
#### Advanced Unix domain sockets
292+
293+
HTTP CONNECT proxy servers support forwarding TCP/IP based connections and
294+
higher level protocols.
295+
In some advanced cases, it may be useful to let your HTTP CONNECT proxy server
296+
listen on a Unix domain socket (UDS) path instead of a IP:port combination.
297+
For example, this allows you to rely on file system permissions instead of
298+
having to rely on explicit [authentication](#authentication).
299+
300+
You can simply use the `http+unix://` URI scheme like this:
301+
302+
```php
303+
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $connector);
304+
305+
$proxy->connect('tcp://google.com:80')->then(function (ConnectionInterface $stream) {
306+
// connected…
307+
});
308+
```
309+
310+
Similarly, you can also combine this with [authentication](#authentication)
311+
like this:
312+
313+
```php
314+
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $connector);
315+
```
316+
317+
> Note that Unix domain sockets (UDS) are considered advanced usage and PHP only
318+
has limited support for this.
319+
In particular, enabling [secure TLS](#secure-tls-connections) may not be
320+
supported.
321+
322+
> Note that the HTTP CONNECT protocol does not support the notion of UDS paths.
323+
The above works reasonably well because UDS is only used for the connection between
324+
client and proxy server and the path will not actually passed over the protocol.
325+
This implies that this does not support connecting to UDS destination paths.
326+
290327
## Install
291328

292329
The recommended way to install this library is [through Composer](https://getcomposer.org).

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
},
1919
"require": {
2020
"php": ">=5.3",
21-
"react/socket": "^1.0 || ^0.8 || ^0.7.1",
2221
"react/promise": " ^2.1 || ^1.2.1",
22+
"react/socket": "^1.0 || ^0.8.4",
2323
"ringcentral/psr7": "^1.2"
2424
},
2525
"require-dev": {

src/ProxyConnector.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22

33
namespace Clue\React\HttpProxy;
44

5-
use React\Socket\ConnectorInterface;
65
use Exception;
76
use InvalidArgumentException;
87
use RuntimeException;
98
use RingCentral\Psr7;
109
use React\Promise;
1110
use React\Promise\Deferred;
1211
use React\Socket\ConnectionInterface;
12+
use React\Socket\ConnectorInterface;
13+
use React\Socket\FixedUriConnector;
1314

1415
/**
1516
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
@@ -57,13 +58,25 @@ class ProxyConnector implements ConnectorInterface
5758
*/
5859
public function __construct($proxyUrl, ConnectorInterface $connector)
5960
{
61+
// support `http+unix://` scheme for Unix domain socket (UDS) paths
62+
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
63+
// rewrite URI to parse authentication from dummy host
64+
$proxyUrl = 'http://' . $match[1] . 'localhost';
65+
66+
// connector uses Unix transport scheme and explicit path given
67+
$connector = new FixedUriConnector(
68+
'unix://' . $match[2],
69+
$connector
70+
);
71+
}
72+
6073
if (strpos($proxyUrl, '://') === false) {
6174
$proxyUrl = 'http://' . $proxyUrl;
6275
}
6376

6477
$parts = parse_url($proxyUrl);
6578
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
66-
throw new InvalidArgumentException('Invalid proxy URL');
79+
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
6780
}
6881

6982
// apply default port and TCP/TLS transport for given scheme

tests/ProxyConnectorTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ public function testInvalidProxyScheme()
3131
new ProxyConnector('ftp://example.com', $this->connector);
3232
}
3333

34+
/**
35+
* @expectedException InvalidArgumentException
36+
*/
37+
public function testInvalidHttpsUnixScheme()
38+
{
39+
new ProxyConnector('https+unix:///tmp/proxy.sock', $this->connector);
40+
}
41+
3442
public function testCreatesConnectionToHttpPort()
3543
{
3644
$promise = new Promise(function () { });
@@ -71,6 +79,16 @@ public function testCreatesConnectionToHttpsPort()
7179
$proxy->connect('google.com:80');
7280
}
7381

82+
public function testCreatesConnectionToUnixPath()
83+
{
84+
$promise = new Promise(function () { });
85+
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
86+
87+
$proxy = new ProxyConnector('http+unix:///tmp/proxy.sock', $this->connector);
88+
89+
$proxy->connect('google.com:80');
90+
}
91+
7492
public function testCancelPromiseWillCancelPendingConnection()
7593
{
7694
$promise = new Promise(function () { }, $this->expectCallableOnce());
@@ -140,6 +158,19 @@ public function testWillProxyAuthorizationHeaderIfProxyUriContainsAuthentication
140158
$proxy->connect('google.com:80');
141159
}
142160

161+
public function testWillProxyAuthorizationHeaderIfUnixProxyUriContainsAuthentication()
162+
{
163+
$stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('close', 'write'))->getMock();
164+
$stream->expects($this->once())->method('write')->with("CONNECT google.com:80 HTTP/1.1\r\nHost: google.com:80\r\nProxy-Authorization: Basic dXNlcjpwYXNz\r\n\r\n");
165+
166+
$promise = \React\Promise\resolve($stream);
167+
$this->connector->expects($this->once())->method('connect')->with('unix:///tmp/proxy.sock')->willReturn($promise);
168+
169+
$proxy = new ProxyConnector('http+unix://user:pass@/tmp/proxy.sock', $this->connector);
170+
171+
$proxy->connect('google.com:80');
172+
}
173+
143174
public function testRejectsInvalidUri()
144175
{
145176
$this->connector->expects($this->never())->method('connect');

0 commit comments

Comments
 (0)