PHP SoapClient Through SOCKS Proxy

I often find myself in the following predicament: a service that I am using allows me to access it only through a registered IP address. For example, if you want to use Stamps.com to generate postage labels to automate shipping goods, they will ask you to provide a production and a testing IP address. They will only allow you to connect to their SOAP service if it originates from that particular IP address.

The problem is that I am behind a dynamic IP, so if I develop from my local machine I won’t be allowed to connect to the SOAP service at all since there is no point in sending them a request to change the whitelisted IP whenver my ISP decides to issue me another address. Also, developers come and go, and even if they have static addresses, it’s still a pain in the butt to continually request that new IP addresses get whitelisted and old ones removed.

To remedy this situation, we got our “preview” server whitelisted for development. Preview server is used to automatically pull all commits to our subversion repository and always keep the latest development version of the site available for testing. This server has a static IP address. Now, whenever I want to make requests to the SOAP server from my local machine, I proxy all requests through the “preview” server. Here is how to do it using SOCKS proxy:

Firstly, you need to create a ssh tunnel to the server which has the desired IP address. Assuming you are using Linux (or some other flavor of UNIX, like FreeBSD or Mac OS X), this command will allow all traffic going to 1080 to be proxied through whitelisted.server.com. Of course, you must have a ssh account available on the target server

ssh -p 22 -N -D 1080 whitelisted.server.com

-p 22 specifies the port number for SSH connection, while -D 1080 specifies the port that you will be using for proxying the traffic. You can use anything other than 1080, it’s up to you. You can also pass -f parameter so the command will execute in the background, but for that you have to have passwordless login enabled (e.g. use your RSA key to login). Once the tunnel is established (it will look like any other ssh connection), keep it open whenever you need to proxy the traffic.

Now, you need to tell PHP SoapClient to use this proxy.

Your regular code may look something like:

$client = new SoapClient("some.wsdl");
$client->SomeFunction($a, $b, $c);

We have to extend SoapClient and use the new class to be able to utilize the proxy:

class ProxySoapClient extends SoapClient 
{
	protected function callCurl($url, $data, $action)
	{
		$handle   = curl_init();
		curl_setopt($handle, CURLOPT_URL, $url);

		// If you need to handle headers like cookies, session id, etc. you will have
		// to set them here manually
		$headers = array("Content-Type: text/xml", 'SOAPAction: "' . $action . '"');	
		curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);

		curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
		curl_setopt($handle, CURLOPT_FRESH_CONNECT, true);
		curl_setopt($handle, CURLOPT_HEADER, true);

		if (ENVIRONMENT == 'development') {
			curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
			curl_setopt($handle, CURLOPT_PROXY, "localhost:1080"); // 1080 is your -D parameter
		}

		$response = curl_exec($handle);
        curl_close($handle);

		list($headers, $content) = explode("\r\n\r\n", $response, 2);

		// If you need headers for something, it's not too bad to 
		// keep them in e.g. $this->headers and then use them as needed

		return $content;
	}

	public function __doRequest($request, $location, $action, $version, $one_way = 0)
	{
		return $this->callCurl($location, $request, $action);
	}
}

Voila! We have overriden the method __doRequest to use curl extension to make the HTTP request through proxy and all your requests will now appear to come from the “preview” machine.