Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to send multiple requests at once #23

Merged
merged 35 commits into from
Oct 25, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
99836dd
Add multiple request support
rmccue Jul 3, 2012
fb5e1c2
Only fire multiple.request.complete once parsed
rmccue Jul 3, 2012
66235e5
Add request_multiple to the Transport interface
rmccue Jul 3, 2012
bd4816f
Add multiple request support to Transport_fsockopen
rmccue Jul 3, 2012
4b0199e
Add more hooks for cURL multiple requests
rmccue Jul 3, 2012
fe5e7ed
Remove redundant parsing for single cURL requests
rmccue Jul 3, 2012
ddcff9a
Change cURL hooks around a bit
rmccue Jul 3, 2012
a2037d6
Avoid double-checking for cURL errors
rmccue Jul 3, 2012
80efd61
Stick to a single shared key for all cURL data
rmccue Jul 3, 2012
277ee66
Fix spelling error in fsockopen
rmccue Jul 3, 2012
f39a480
Only hook the internal parser in once per handler
rmccue Jul 3, 2012
f925013
Pass the ID into the multiple request callback
rmccue Jul 3, 2012
d4825a0
Ensure multi defaults match normal defaults
rmccue Jul 3, 2012
aca20ac
Document the parameters to `Requests::request_multiple()`
rmccue Jul 3, 2012
d7e68b3
Allow easy hooking into multiple.request.complete
rmccue Jul 3, 2012
f8b795d
Add example for multiple requesting
rmccue Jul 3, 2012
533a4f1
Merge branch 'master' into multiple
rmccue Jul 3, 2012
cbdd442
Add request_multiple to MockTransport
rmccue Jul 3, 2012
bacf847
Add fake request_multiple for RawTransport too
rmccue Jul 3, 2012
2bea203
Merge branch 'master' into multiple
rmccue Jul 3, 2012
f81b9f1
Ensure streaming files works properly
rmccue Jul 3, 2012
df5c648
Don't return cURL result by reference
rmccue Jul 3, 2012
9a4e351
Move defaulting code to a common method
rmccue Jul 3, 2012
b04ac1b
Add documentation for new methods
rmccue Jul 3, 2012
ab535ff
Remove redundant code
rmccue Jul 3, 2012
6d14220
Avoid using double-quoted strings for concatenation
rmccue Jul 4, 2012
b4b6b67
Ensure the type is always set correctly
rmccue Jul 4, 2012
a112b3f
Add multirequest tests
rmccue Jul 4, 2012
3886af0
Ensure fsockopen uses the correct hooking system
rmccue Jul 5, 2012
bbcded4
Ensure we set the correct HTTP type in tests
rmccue Jul 6, 2012
570d675
Test the per-request complete callback
rmccue Oct 25, 2012
2b4ce1d
Test failures in multiple requests too
rmccue Oct 25, 2012
f8d3aa6
Rewrite the multirequest loop
rmccue Oct 25, 2012
937c748
Ensure we default the HTTP type correctly
rmccue Oct 25, 2012
23c2087
Test multiple requests with output to a file
rmccue Oct 25, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions examples/multiple.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

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

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

// Setup what we want to request
$requests = array(
array(
'url' => 'http://httpbin.org/get',
'headers' => array('Accept' => 'application/javascript'),
),
'post' => array(
'url' => 'http://httpbin.org/post',
'data' => array('mydata' => 'something'),
),
'delayed' => array(
'url' => 'http://httpbin.org/delay/10',
'options' => array(
'timeout' => 20,
),
),
);

// Setup a callback
function my_callback(&$request, $id) {
var_dump($id, $request);
}

// Tell Requests to use the callback
$options = array(
'complete' => 'my_callback',
);

// Send the request!
$responses = Requests::request_multiple($requests, $options);

// Note: the response from the above call will be an associative array matching
// $requests with the response data, however we've already handled it in
// my_callback() anyway!
//
// If you don't believe me, uncomment this:
# var_dump($responses);
201 changes: 180 additions & 21 deletions library/Requests.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,57 +287,197 @@ public static function request($url, $headers = array(), $data = array(), $type
if (!preg_match('/^http(s)?:\/\//i', $url)) {
throw new Requests_Exception('Only HTTP requests are handled.', 'nonhttp', $url);
}
if (empty($options['type'])) {
$options['type'] = $type;
}
$options = array_merge(self::get_default_options(), $options);

self::set_defaults($url, $headers, $data, $type, $options);

$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));

if (!empty($options['transport'])) {
$transport = $options['transport'];

if (is_string($options['transport'])) {
$transport = new $transport();
}
}
else {
$transport = self::get_transport();
}
$response = $transport->request($url, $headers, $data, $options);

$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));

return self::parse_response($response, $url, $headers, $data, $options);
}

/**
* Send multiple HTTP requests simultaneously
*
* The `$requests` parameter takes an associative or indexed array of
* request fields. The key of each request can be used to match up the
* request with the returned data, or with the request passed into your
* `multiple.request.complete` callback.
*
* The request fields value is an associative array with the following keys:
*
* - `url`: Request URL Same as the `$url` parameter to
* {@see Requests::request}
* (string, required)
* - `headers`: Associative array of header fields. Same as the `$headers`
* parameter to {@see Requests::request}
* (array, default: `array()`)
* - `data`: Associative array of data fields or a string. Same as the
* `$data` parameter to {@see Requests::request}
* (array|string, default: `array()`)
* - `type`: HTTP request type (use Requests constants). Same as the `$type`
* parameter to {@see Requests::request}
* (string, default: `Requests::GET`)
* - `data`: Associative array of options. Same as the `$options` parameter
* to {@see Requests::request}
* (array, default: see {@see Requests::request})
*
* If the `$options` parameter is specified, individual requests will
* inherit options from it. This can be used to use a single hooking system,
* or set all the types to `Requests::POST`, for example.
*
* In addition, the `$options` parameter takes the following global options:
*
* - `complete`: A callback for when a request is complete. Takes two
* parameters, a Requests_Response/Requests_Exception reference, and the
* ID from the request array (Note: this can also be overriden on a
* per-request basis, although that's a little silly)
* (callback)
*
* @param array $requests Requests data (see description for more information)
* @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either Requests_Response or a Requests_Exception object)
*/
public static function request_multiple($requests, $options = array()) {
$options = array_merge(self::get_default_options(true), $options);

if (!empty($options['hooks'])) {
$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($options['complete'])) {
$options['hooks']->register('multiple.request.complete', $options['complete']);
}
}

foreach ($requests as $id => &$request) {
if (!isset($request['headers'])) {
$request['headers'] = array();
}
if (!isset($request['data'])) {
$request['data'] = array();
}
if (!isset($request['type'])) {
$request['type'] = self::GET;
}
if (!isset($request['options'])) {
$request['options'] = $options;
$request['options']['type'] = $request['type'];
}
else {
if (empty($request['options']['type'])) {
$request['options']['type'] = $request['type'];
}
$request['options'] = array_merge($options, $request['options']);
}

self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);

// Ensure we only hook in once
if ($request['options']['hooks'] !== $options['hooks']) {
$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($request['options']['complete'])) {
$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
}
}
}
unset($request);

if (!empty($options['transport'])) {
$transport = $options['transport'];

if (is_string($options['transport'])) {
$transport = new $transport();
}
}
else {
$transport = self::get_transport();
}
$responses = $transport->request_multiple($requests, $options);

foreach ($responses as $id => &$response) {
// If our hook got messed with somehow, ensure we end up with the
// correct response
if (is_string($response)) {
$request = $requests[$id];
$response = self::parse_multiple($response, $request);
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
}
}

return $responses;
}

/**
* Get the default options
*
* @see Requests::request() for values returned by this method
* @param boolean $multirequest Is this a multirequest?
* @return array Default option values
*/
protected static function get_default_options($multirequest = false) {
$defaults = array(
'timeout' => 10,
'useragent' => 'php-requests/' . self::VERSION,
'redirected' => 0,
'redirects' => 10,
'follow_redirects' => true,
'blocking' => true,
'type' => $type,
'type' => self::GET,
'filename' => false,
'auth' => false,
'idn' => true,
'hooks' => null,
'transport' => null,
);
$options = array_merge($defaults, $options);
if ($multirequest !== false) {
$defaults['complete'] = null;
}
return $defaults;
}

/**
* Set the default values
*
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type
* @param array $options Options for the request
* @return array $options
*/
protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
if (empty($options['hooks'])) {
$options['hooks'] = new Requests_Hooks();
}

// Special case for simple basic auth
if (is_array($options['auth'])) {
$options['auth'] = new Requests_Auth_Basic($options['auth']);
}
if ($options['auth'] !== false) {
$options['auth']->register($options['hooks']);
}

$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));

if ($options['idn'] !== false) {
$iri = new Requests_IRI($url);
$iri->host = Requests_IDNAEncoder::encode($iri->ihost);
$url = $iri->uri;
}

if (!empty($options['transport'])) {
$transport = $options['transport'];

if (is_string($options['transport'])) {
$transport = new $transport();
}
}
else {
$transport = self::get_transport();
}
$response = $transport->request($url, $headers, $data, $options);

$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));

return self::parse_response($response, $url, $headers, $data, $options);
}

/**
Expand Down Expand Up @@ -435,6 +575,25 @@ protected static function parse_response($headers, $url, $req_headers, $req_data
return $return;
}

/**
* Callback for `transport.internal.parse_response`
*
* Internal use only. Converts a raw HTTP response to a Requests_Response
* while still executing a multiple request.
*
* @param string $headers Full response text including headers and body
* @param array $request Request data as passed into {@see Requests::request_multiple()}
* @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
*/
public static function parse_multiple(&$response, $request) {
try {
$response = self::parse_response($response, $request['url'], $request['headers'], $request['data'], $request['options']);
}
catch (Requests_Exception $e) {
$response = $e;
}
}

/**
* Decoded a chunked body as per RFC 2616
*
Expand Down
9 changes: 9 additions & 0 deletions library/Requests/Transport.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ interface Requests_Transport {
*/
public function request($url, $headers = array(), $data = array(), $options = array());

/**
* Send multiple requests simultaneously
*
* @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
* @param array $options Global options, see {@see Requests::response()} for documentation
* @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
*/
public function request_multiple($requests, $options);

/**
* Self-test whether the transport can be used
* @return bool
Expand Down
Loading