Skip to content

Commit

Permalink
Merge pull request #70 from rmccue/proxy
Browse files Browse the repository at this point in the history
Add full proxy support, major props @ozh
  • Loading branch information
rmccue committed Sep 24, 2013

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents d11e02b + f5e1aa3 commit edf5e52
Showing 7 changed files with 252 additions and 4 deletions.
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ here are prose; you might also want to check out the [API documentation][].
* [Authenticating your request][authentication]
* Advanced Usage
* [Custom authentication][authentication-custom]
* [Requests through proxy][proxy]
* [Hooking system][hooks]

[goals]: goals.md
@@ -23,4 +24,5 @@ here are prose; you might also want to check out the [API documentation][].
[usage-advanced]: usage-advanced.md
[authentication]: authentication.md
[authentication-custom]: authentication-custom.md
[hooks]: hooks.md
[hooks]: hooks.md
[proxy]: proxy.md
23 changes: 23 additions & 0 deletions docs/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Proxy Support
=============

You can easily make requests through HTTP proxies.

To make requests through an open proxy, specify the following options:

```php
$options = array(
'proxy' => '127.0.0.1:3128'
);
Requests::get('http://httpbin.org/ip', array(), $options);
```

If your proxy needs you to authenticate, the option will become an array like
the following:

```php
$options = array(
'proxy' => array( '127.0.0.1:3128', 'my_username', 'my_password' )
);
Requests::get('http://httpbin.org/ip', array(), $options);
```
18 changes: 18 additions & 0 deletions examples/proxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

// First, include Requests
include('../library/Requests.php');

// Next, make sure Requests can load internal classes
Requests::register_autoloader();

// Now let's make a request via a proxy.
$options = array(
'proxy' => '127.0.0.1:8080', // syntax: host:port, eg 12.13.14.14:8080 or someproxy.com:3128
// If you need to authenticate, use the following syntax:
// 'proxy' => array( '127.0.0.1:8080', 'username', 'password' ),
);
$request = Requests::get('http://httpbin.org/ip', array(), $options );

// See result
var_dump($request->body);
10 changes: 10 additions & 0 deletions library/Requests.php
Original file line number Diff line number Diff line change
@@ -267,6 +267,8 @@ public static function patch($url, $headers, $data = array(), $options = array()
* - `auth`: Authentication handler or array of user/password details to use
* for Basic authentication
* (Requests_Auth|array|boolean, default: false)
* - `proxy`: Proxy details to use for proxy by-passing and authentication
* (Requests_Proxy|array|boolean, default: false)
* - `idn`: Enable IDN parsing
* (boolean, default: true)
* - `transport`: Custom transport. Either a class name, or a
@@ -447,6 +449,7 @@ protected static function get_default_options($multirequest = false) {
'type' => self::GET,
'filename' => false,
'auth' => false,
'proxy' => false,
'idn' => true,
'hooks' => null,
'transport' => null,
@@ -485,6 +488,13 @@ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$optio
$options['auth']->register($options['hooks']);
}

if (!empty($options['proxy'])) {
$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
}
if ($options['proxy'] !== false) {
$options['proxy']->register($options['hooks']);
}

if ($options['idn'] !== false) {
$iri = new Requests_IRI($url);
$iri->host = Requests_IDNAEncoder::encode($iri->ihost);
35 changes: 35 additions & 0 deletions library/Requests/Proxy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/

/**
* Proxy connection interface
*
* Implement this interface to handle proxy settings and authentication
*
* Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider.
*
* @see Requests_Hooks
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
interface Requests_Proxy {
/**
* Register hooks as needed
*
* This method is called in {@see Requests::request} when the user has set
* an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need.
*
* @see Requests_Hooks::register
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks);
}
150 changes: 150 additions & 0 deletions library/Requests/Proxy/HTTP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php
/**
* HTTP Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/

/**
* HTTP Proxy connection interface
*
* Provides a handler for connection via an HTTP proxy
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
class Requests_Proxy_HTTP implements Requests_Proxy {
/**
* Proxy host and port
*
* Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
*
* @var string
*/
public $proxy;

/**
* Username
*
* @var string
*/
public $user;

/**
* Password
*
* @var string
*/
public $pass;

/**
* Do we need to authenticate? (ie username & password have been provided)
*
* @var boolean
*/
public $use_authentication;

/**
* Constructor
*
* @since 1.6
* @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|null $args Array of user and password. Must have exactly two elements
*/
public function __construct($args = null) {
if (is_string($args)) {
$this->proxy = $args;
}
elseif (is_array($args)) {
if (count($args) == 1) {
list($this->proxy) = $args;
}
elseif (count($args) == 3) {
list($this->proxy, $this->user, $this->pass) = $args;
$this->use_authentication = true;
}
else {
throw new Requests_Exception( 'Invalid number of arguments', 'proxyhttpbadargs');
}
}
}

/**
* Register the necessary callbacks
*
* @since 1.6
* @see curl_before_send
* @see fsockopen_remote_socket
* @see fsockopen_remote_host_path
* @see fsockopen_header
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks) {
$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));

$hooks->register('fsockopen.remote_socket', array(&$this, 'fsockopen_remote_socket'));
$hooks->register('fsockopen.remote_host_path', array(&$this, 'fsockopen_remote_host_path'));
if( $this->use_authentication ) {
$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
}
}

/**
* Set cURL parameters before the data is sent
*
* @since 1.6
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_setopt($handle, CURLOPT_PROXY, $this->proxy);

if ($this->use_authentication) {
curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string());
}
}

/**
* Alter remote socket information before opening socket connection
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_remote_socket( &$remote_socket ) {
$remote_socket = $this->proxy;
}

/**
* Alter remote path before getting stream data
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_remote_host_path( &$path, $url ) {
$path = $url;
}

/**
* Add extra headers to the request before sending
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_header( &$out ) {
$out .= "Proxy-Authorization: Basic " . base64_encode($this->get_auth_string()) . "\r\n";
}

/**
* Get the authentication string (user:pass)
*
* @since 1.6
* @return string
*/
public function get_auth_string() {
return $this->user . ':' . $this->pass;
}
}
16 changes: 13 additions & 3 deletions library/Requests/Transport/fsockopen.php
Original file line number Diff line number Diff line change
@@ -79,13 +79,20 @@ public function request($url, $headers = array(), $data = array(), $options = ar
$remote_socket = 'tcp://' . $host;
}

$proxy = isset( $options['proxy'] );
$proxy_auth = $proxy && isset( $options['proxy_username'] ) && isset( $options['proxy_password'] );

if (!isset($url_parts['port'])) {
$url_parts['port'] = 80;
}
$remote_socket .= ':' . $url_parts['port'];

set_error_handler(array($this, 'connect_error_handler'), E_WARNING | E_NOTICE);

$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));

$fp = stream_socket_client($remote_socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $context);

restore_error_handler();

if (!$fp) {
@@ -114,7 +121,10 @@ public function request($url, $headers = array(), $data = array(), $options = ar
else {
$path = '/';
}

$options['hooks']->dispatch( 'fsockopen.remote_host_path', array( &$path, $url ) );
$out = $options['type'] . " $path HTTP/1.0\r\n";

if (is_array($data)) {
$request_body = http_build_query($data, null, '&');
}
@@ -131,16 +141,16 @@ public function request($url, $headers = array(), $data = array(), $options = ar
case Requests::HEAD:
case Requests::GET:
case Requests::DELETE:
$get = self::format_get($url_parts, $data);
$out = $options['type'] . " $get HTTP/1.0\r\n";
$path = self::format_get($url_parts, $data);
$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path, $url));
$out = $options['type'] . " $path HTTP/1.0\r\n";
break;
}
$out .= "Host: {$url_parts['host']}";

if ($url_parts['port'] !== 80) {
$out .= ":{$url_parts['port']}";
}

$out .= "\r\n";

$out .= "User-Agent: {$options['useragent']}\r\n";

1 comment on commit edf5e52

@ozh
Copy link
Collaborator

@ozh ozh commented on edf5e52 Sep 24, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\o/ :cake:

Please sign in to comment.