Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork17
Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP.
License
clue/reactphp-http-proxy
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTPCONNECT proxy server, built on top ofReactPHP.
HTTP CONNECT proxy servers (also commonly known as "HTTPS proxy" or "SSL proxy")are commonly used to tunnel HTTPS traffic through an intermediary ("proxy"), toconceal the origin address (anonymity) or to circumvent address blocking(geoblocking). While many (public) HTTP CONNECT proxy servers often limit thisto HTTPS port443 only, this can technically be used to tunnel anyTCP/IP-based protocol (HTTP, SMTP, IMAP etc.).This library provides a simple API to create these tunneled connections for you.Because it implements ReactPHP's standardConnectorInterface,it can simply be used in place of a normal connector.This makes it fairly simple to add HTTP CONNECT proxy support to pretty much anyexisting higher-level protocol implementation.
- Async execution of connections -Send any number of HTTP CONNECT requests in parallel and process theirresponses as soon as results come in.The Promise-based design provides asane interface to working with out oforder responses and possible connection errors.
- Standard interfaces -Allows easy integration with existing higher-level components by implementingReactPHP's standard
ConnectorInterface. - Lightweight, SOLID design -Provides a thin abstraction that isjust good enoughand does not get in your way.Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
- Good test coverage -Comes with an automated tests suite and is regularly tested against actual proxy servers in the wild.
Table of contents
We invest a lot of time developing, maintaining and updating our awesomeopen-source projects. You can help us sustain this high-quality of our work bybecoming a sponsor on GitHub. Sponsors getnumerous benefits in return, see oursponsoring pagefor details.
Let's take these projects to the next level together! 🚀
The following example code demonstrates how this library can be used to send asecure HTTPS request to google.com through a local HTTP proxy server:
<?phprequire__DIR__ .'/vendor/autoload.php';$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false));$browser =newReact\Http\Browser($connector);$browser->get('https://google.com/')->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders(), (string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See also theexamples.
TheProxyConnector is responsible for creating plain TCP/IP connections toany destination by using an intermediary HTTP CONNECT proxy.
[you] -> [proxy] -> [destination]Its constructor simply accepts an HTTP proxy URL with the proxy server address:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');
The proxy URL may or may not contain a scheme and port definition. The defaultport will be80 for HTTP (or443 for HTTPS), but many common HTTP proxyservers use custom ports (often the alternative HTTP port8080).
If you need custom connector settings (DNS resolution, TLS parameters, timeouts,proxy servers etc.), you can explicitly pass a custom instance of theConnectorInterface:
$connector =newReact\Socket\Connector(array('dns' =>'127.0.0.1','tcp' =>array('bindto' =>'192.168.10.1:0' ),'tls' =>array('verify_peer' =>false,'verify_peer_name' =>false )));$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080',$connector);
This is the main class in this package.Because it implements ReactPHP's standardConnectorInterface,it can simply be used in place of a normal connector.Accordingly, it provides only a single public method, theconnect() method.Theconnect(string $uri): PromiseInterface<ConnectionInterface, Exception>method can be used to establish a streaming connection.It returns aPromise which eitherfulfills with aConnectionInterfaceon success or rejects with anException on error.
This makes it fairly simple to add HTTP CONNECT proxy support to pretty much anyhigher-level component:
- $acme = new AcmeApi($connector);+ $proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080', $connector);+ $acme = new AcmeApi($proxy);
HTTP CONNECT proxies are most frequently used to issue HTTPS requests to your destination.However, this is actually performed on a higher protocol layer and thisconnector is actually inherently a general-purpose plain TCP/IP connector.As documented above, you can simply invoke itsconnect() method to establisha streaming plain TCP/IP connection and use any higher level protocol like so:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$proxy->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface$connection) {$connection->write("EHLO local\r\n");$connection->on('data',function ($chunk)use ($connection) {echo$chunk; });},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
You can either use theProxyConnector directly or you may want to wrap this connectorin ReactPHP'sConnector:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false));$connector->connect('tcp://smtp.googlemail.com:587')->then(function (React\Socket\ConnectionInterface$connection) {$connection->write("EHLO local\r\n");$connection->on('data',function ($chunk)use ($connection) {echo$chunk; });},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Note that HTTP CONNECT proxies often restrict which ports one may connect to.Many (public) proxy servers do in fact limit this to HTTPS (443) only.
This class can also be used if you want to establish a secure TLS connection(formerly known as SSL) between you and your destination, such as when usingsecure HTTPS to your destination site. You can simply wrap this connector inReactPHP'sConnector:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false));$connector->connect('tls://smtp.googlemail.com:465')->then(function (React\Socket\ConnectionInterface$connection) {$connection->write("EHLO local\r\n");$connection->on('data',function ($chunk)use ($connection) {echo$chunk; });},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Note how secure TLS connections are in fact entirely handled outside ofthis HTTP CONNECT client implementation.
This library also allows you to send HTTP requests through an HTTP CONNECT proxy server.
In order to send HTTP requests, you first have to add a dependency forReactPHP's async HTTP client.This allows you to send both plain HTTP and TLS-encrypted HTTPS requests like this:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false));$browser =newReact\Http\Browser($connector);$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface$response) {var_dump($response->getHeaders(), (string)$response->getBody());},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See alsoReactPHP's HTTP clientand any of theexamples for more details.
By default, theProxyConnector does not implement any timeouts for establishing remoteconnections.Your underlying operating system may impose limits on pending and/or idle TCP/IPconnections, anywhere in a range of a few minutes to several hours.
Many use cases require more control over the timeout and likely values muchsmaller, usually in the range of a few seconds only.
You can use ReactPHP'sConnectorto decorate any givenConnectorInterface instance.It provides the sameconnect() method, but will automatically reject theunderlying connection attempt if it takes too long:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false,'timeout' =>3.0));$connector->connect('tcp://google.com:80')->then(function ($connection) {// connection succeeded within 3.0 seconds},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
See also any of theexamples.
Note how the connection timeout is in fact entirely handled outside of thisHTTP CONNECT client implementation.
By default, theProxyConnector does not perform any DNS resolution at all and simplyforwards any hostname you're trying to connect to the remote proxy server.The remote proxy server is thus responsible for looking up any hostnames via DNS(this default mode is thus calledremote DNS resolution).
As an alternative, you can also send the destination IP to the remote proxyserver.In this mode you either have to stick to using IPs only (which is ofen unfeasable)or perform any DNS lookups locally and only transmit the resolved destination IPs(this mode is thus calledlocal DNS resolution).
The defaultremote DNS resolution is useful if your localProxyConnector either cannot resolve target hostnames because it has no direct access to the internet orif it should not resolve target hostnames because its outgoing DNS traffic mightbe intercepted.
As noted above, theProxyConnector defaults to using remote DNS resolution.However, wrapping theProxyConnector in ReactPHP'sConnector actuallyperforms local DNS resolution unless explicitly defined otherwise.Given that remote DNS resolution is assumed to be the preferred mode, allother examples explicitly disable DNS resolution like this:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>false));
If you want to explicitly uselocal DNS resolution, you can use the following code:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080');// set up Connector which uses Google's public DNS (8.8.8.8)$connector =newReact\Socket\Connector(array('tcp' =>$proxy,'dns' =>'8.8.8.8'));
Note how local DNS resolution is in fact entirely handled outside of thisHTTP CONNECT client implementation.
If your HTTP proxy server requires authentication, you may pass the username andpassword as part of the HTTP proxy URL like this:
$proxy =newClue\React\HttpProxy\ProxyConnector('alice:password@127.0.0.1:8080');
Note that both the username and password must be percent-encoded if they containspecial characters:
$user ='he:llo';$pass ='p@ss';$url =rawurlencode($user) .':' .rawurlencode($pass) .'@127.0.0.1:8080';$proxy =newClue\React\HttpProxy\ProxyConnector($url);
The authentication details will be used for basic authentication and will betransferred in the
Proxy-AuthorizationHTTP request header for eachconnection attempt.If the authentication details are missing or not accepted by the remote HTTPproxy server, it is expected to reject each connection attempt with a407(Proxy Authentication Required) response status code and an exceptionerror code ofSOCKET_EACCES(13).
TheProxyConnector constructor accepts an optional array of custom requestheaders to send in theCONNECT request. This can be useful if you're using acustom proxy setup or authentication scheme if the proxy server does not supportbasicauthentication as documented above. This is rarely usedin practice, but may be useful for some more advanced use cases. In this case,you may simply pass an assoc array of additional request headers like this:
$proxy =newClue\React\HttpProxy\ProxyConnector('127.0.0.1:8080',null,array('Proxy-Authorization' =>'Bearer abc123','User-Agent' =>'ReactPHP' ));
Note that communication between the client and the proxy is usually via anunencrypted, plain TCP/IP HTTP connection. Note that this is the most commonsetup, because you can still establish a TLS connection between you and thedestination host as above.
If you want to connect to a (rather rare) HTTPS proxy, you may want use thehttps:// scheme (HTTPS default port 443) to create a secure connection to the proxy:
$proxy =newClue\React\HttpProxy\ProxyConnector('https://127.0.0.1:443');$proxy->connect('tcp://smtp.googlemail.com:587');
HTTP CONNECT proxy servers support forwarding TCP/IP based connections andhigher level protocols.In some advanced cases, it may be useful to let your HTTP CONNECT proxy serverlisten on a Unix domain socket (UDS) path instead of a IP:port combination.For example, this allows you to rely on file system permissions instead ofhaving to rely on explicitauthentication.
You can simply use thehttp+unix:// URI scheme like this:
$proxy =newClue\React\HttpProxy\ProxyConnector('http+unix:///tmp/proxy.sock');$proxy->connect('tcp://google.com:80')->then(function (React\Socket\ConnectionInterface$connection) {// connected…},function (Exception$e) {echo'Error:' .$e->getMessage() .PHP_EOL;});
Similarly, you can also combine this withauthenticationlike this:
$proxy =newClue\React\HttpProxy\ProxyConnector('http+unix://alice:password@/tmp/proxy.sock');
Note that Unix domain sockets (UDS) are considered advanced usage and PHP onlyhas limited support for this.In particular, enablingsecure TLS may not besupported.
Note that the HTTP CONNECT protocol does not support the notion of UDS paths.The above works reasonably well because UDS is only used for the connection betweenclient and proxy server and the path will not actually passed over the protocol.This implies that this does not support connecting to UDS destination paths.
The recommended way to install this library isthrough Composer.New to Composer?
This project followsSemVer.This will install the latest supported version:
composer require clue/http-proxy-react:^1.9
See also theCHANGELOG for details about version upgrades.
This project aims to run on any platform and thus does not require any PHPextensions and supports running on legacy PHP 5.3 through current PHP 8+ andHHVM.It'shighly recommended to use the latest supported PHP version for this project.
To run the test suite, you first need to clone this repo and then install alldependenciesthrough Composer:
composer install
To run the test suite, go to the project root and run:
vendor/bin/phpunit
The test suite contains tests that rely on a working internet connection,alternatively you can also run it like this:
vendor/bin/phpunit --exclude-group internet
This project is released under the permissiveMIT license.
Did you know that I offer custom development services and issuing invoices forsponsorships of releases and for contributions? Contact me (@clue) for details.
- If you want to learn more about how the
ConnectorInterfaceand its usual implementations look like, refer to the documentation of the underlyingreact/socket component. - If you want to learn more about processing streams of data, refer to thedocumentation of the underlyingreact/stream component.
- As an alternative to an HTTP CONNECT proxy, you may also want to look intousing a SOCKS (SOCKS4/SOCKS5) proxy instead.You may want to useclue/reactphp-sockswhich also provides an implementation of the same
ConnectorInterfaceso that supporting either proxy protocol should be fairly trivial. - As an alternative to an HTTP CONNECT proxy, you may also want to look intousing an SSH proxy (SSH tunnel) instead.You may want to useclue/reactphp-ssh-proxywhich also provides an implementation of the same
ConnectorInterfaceso that supporting either proxy protocol should be fairly trivial. - If you're dealing with public proxies, you'll likely have to work with mixedquality and unreliable proxies. You may want to look into usingclue/reactphp-connection-manager-extrawhich allows retrying unreliable ones, implying connection timeouts,concurrently working with multiple connectors and more.
- If you're looking for an end-user HTTP CONNECT proxy server daemon, you maywant to useLeProxy.
About
Async HTTP proxy connector, tunnel any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP.
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.