Home » Networking » PHP SoapClient Through SOCKS Proxy

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.

8 thoughts on “PHP SoapClient Through SOCKS Proxy

  1. Thanks for the article. Haven’t been able to get this working through a tunnel yet. But FYI you have this in the second line of callCurl:

    curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);

    Where $headers is never instantiated before hand, or passed in to the method as a param.

    It is however created in the list() command in the second to last line, but it’s value is based on the curl response itself.

  2. Aaron, good catch. I modified the code to reflect this. You need to pass headers specific to the SOAP requests. Some servers, though, will bind your session with an e.g. JSESSIONID, in which case you need to get it from list($headers, $content), store it somewhere and make sure you include it on subsequent requests

    Thanks

  3. You have
    curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);

    twice, it should probally only be within the if statement…

  4. Muchas Gracias fue de mucha ayuda.

    Se debe tener en cuenta que el wsdl debe estar local sino no funciona el constructor.

  5. Hi,

    Thanks for this great example! I’m noticing that even though we are passing a string containing a local WSDL file to the constructor, SOAPClient is trying to load the WSDL from the network bypassing the proxy after it reads the local wsdl. This of course fails where the server cannot contact the wsdl host directly. Any idea how to turn this off?

    Thanks,

    Chris

  6. Does this approach work in WSDL mode, where the constructor fetches the WSDL. In my testing, it gets the WSDL without calling __doRequest().

  7. I Cannot get this to work. the connection always goes out directly to the destination.

    The only way I can get the code to send the traffic to my local proxy is using proxy_host and proxy_port when calling ProxySoapClient

    $client = new ProxySoapClient($wsdl,array(‘location’=>$location,’login’=>$username,’password’=>$password,’proxy_host’=>”127.0.0.1″,’proxy_port’=>”1080″,’proxy_login’ => NULL,’proxy_password’ => NULL,));

    I am using SSH to create the proxy. I am assuming it must need to be SOCKS5. I think the native SoapClient proxy is http proxy. So this doesn’t work. But the new class and extension of SoapClient does not seem to actually make CURL use a proxy. I even removed the “if(ENVIRONMENT == “development”)” wrapper to force it to use it, but still. CURL tries to go out directly to the destination and not via proxy.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.