From 5893e904b942f8474c281ae7743d7a71a9313039 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 11 Aug 2013 00:39:58 +1000 Subject: [PATCH 1/7] Add initial session support This works, but still needs tests. --- docs/usage-advanced.md | 30 +++++ library/Requests/Session.php | 218 +++++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100755 library/Requests/Session.php diff --git a/docs/usage-advanced.md b/docs/usage-advanced.md index 60bd8c5da..5ccda246e 100755 --- a/docs/usage-advanced.md +++ b/docs/usage-advanced.md @@ -1,6 +1,36 @@ Advanced Usage ============== +Session Handling +---------------- +Making multiple requests to the same site with similar options can be a pain, +since you end up repeating yourself. The Session object can be used to set +default parameters for these. + +Let's simulate communicating with GitHub. + +```php +$session = new Requests_Session('https://api.github.com/'); +$session->headers['X-ContactAuthor'] = 'rmccue'; +$session->useragent = 'My-Awesome-App'; + +$response = $session->get('/zen'); +``` + +You can use the `url`, `headers`, `data` and `options` properties of the Session +object to set the defaults for this session, and the constructor also takes +parameters in the same order as `Requests::request()`. Accessing any other +properties will set the corresponding key in the options array; that is: + +```php +// Setting the property... +$session->useragent = 'My-Awesome-App'; + +// ...is the same as setting the option +$session->options['useragent'] = 'My-Awesome-App'; +``` + + Secure Requests with SSL ------------------------ By default, HTTPS requests will use the most secure options available: diff --git a/library/Requests/Session.php b/library/Requests/Session.php new file mode 100755 index 000000000..22bffa462 --- /dev/null +++ b/library/Requests/Session.php @@ -0,0 +1,218 @@ +useragent = 'X';` + * + * @var array + */ + public $options = array(); + + /** + * Create a new session + * + * @param string|null $url Base URL for requests + * @param array $headers Headers + * @param array $data [description] + * @param array $options [description] + */ + public function __construct($url = null, $headers = array(), $data = array(), $options = array()) { + $this->url = $url; + $this->headers = $headers; + $this->data = $data; + $this->options = $options; + } + + /** + * Get a property's value + * + * @param string $key Property key + * @return mixed|null Property value, null if none found + */ + public function __get($key) { + if (isset($this->options[$key])) + return $this->options[$key]; + + return null; + } + + /** + * Set a property's value + * + * @param string $key Property key + * @param mixed $value Property value + */ + public function __set($key, $value) { + $this->options[$key] = $value; + } + + /**#@+ + * @see request() + * @param string $url + * @param array $headers + * @param array $options + * @return Requests_Response + */ + /** + * Send a GET request + */ + public function get($url, $headers = array(), $options = array()) { + return $this->request($url, $headers, null, Requests::GET, $options); + } + + /** + * Send a HEAD request + */ + public function head($url, $headers = array(), $options = array()) { + return $this->request($url, $headers, null, Requests::HEAD, $options); + } + + /** + * Send a DELETE request + */ + public function delete($url, $headers = array(), $options = array()) { + return $this->request($url, $headers, null, Requests::DELETE, $options); + } + + /**#@+ + * @see request() + * @param string $url + * @param array $headers + * @param array $data + * @param array $options + * @return Requests_Response + */ + /** + * Send a POST request + */ + public function post($url, $headers = array(), $data = array(), $options = array()) { + return $this->request($url, $headers, $data, Requests::POST, $options); + } + + /** + * Send a PUT request + */ + public function put($url, $headers = array(), $data = array(), $options = array()) { + return $this->request($url, $headers, $data, Requests::PUT, $options); + } + + /** + * Send a PATCH request + * + * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the + * specification recommends that should send an ETag + * + * @link http://tools.ietf.org/html/rfc5789 + */ + public function patch($url, $headers, $data = array(), $options = array()) { + return $this->request($url, $headers, $data, Requests::PATCH, $options); + } + /**#@-*/ + + /** + * Main interface for HTTP requests + * + * This method initiates a request and sends it via a transport before + * parsing. + * + * @see Requests::request() + * + * @throws Requests_Exception On invalid URLs (`nonhttp`) + * + * @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 (use Requests constants) + * @param array $options Options for the request (see {@see Requests::request}) + * @return Requests_Response + */ + public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) { + $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); + + return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); + } + + /** + * Send multiple HTTP requests simultaneously + * + * @see Requests::request_multiple() + * + * @param array $requests Requests data (see {@see Requests::request_multiple}) + * @param array $options Global and default options (see {@see Requests::request}) + * @return array Responses (either Requests_Response or a Requests_Exception object) + */ + public function request_multiple($requests, $options = array()) { + foreach ($requests as $key => $request) { + $requests[$key] = $this->merge_request($request, false); + } + + $options = array_merge($this->options, $options); + + // Disallow forcing the type, as that's a per request setting + unset($options['type']); + + return Requests::request_multiple($requests, $options); + } + + /** + * Merge a request's data with the default data + * + * @param array $request Request data (same form as {@see request_multiple}) + * @param boolean $merge_options Should we merge options as well? + * @return array Request data + */ + protected function merge_request($request, $merge_options = true) { + if ($this->url !== null) { + $request['url'] = Requests_IRI::absolutize($this->url, $request['url']); + $request['url'] = $request['url']->uri; + } + $request['headers'] = array_merge($this->headers, $request['headers']); + + if (is_array($request['data']) && is_array($this->data)) { + $request['data'] = array_merge($this->data, $request['data']); + } + + if ($merge_options !== false) { + $request['options'] = array_merge($this->options, $request['options']); + + // Disallow forcing the type, as that's a per request setting + unset($request['options']['type']); + } + return $request; + } +} \ No newline at end of file From ad03301625475ae358fe72a9ef596790f67b4b54 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Sun, 11 Aug 2013 00:40:42 +1000 Subject: [PATCH 2/7] Add session usage example --- examples/session.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 examples/session.php diff --git a/examples/session.php b/examples/session.php new file mode 100755 index 000000000..ff44d240a --- /dev/null +++ b/examples/session.php @@ -0,0 +1,24 @@ +headers['Accept'] = 'application/json'; +$session->useragent = 'Awesomesauce'; + +// Now let's make a request! +$request = $session->get('/get'); + +// Check what we received +var_dump($request); + +// Let's check our user agent! +$request = $session->get('/user-agent'); + +// And check again +var_dump($request); From de59272b5c99dcd33446823e3082f9e4636e2421 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 1 Oct 2013 00:03:33 +1000 Subject: [PATCH 3/7] Add cookie support into the session object --- library/Requests/Session.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/Requests/Session.php b/library/Requests/Session.php index 22bffa462..bd87ccf6d 100755 --- a/library/Requests/Session.php +++ b/library/Requests/Session.php @@ -56,6 +56,10 @@ public function __construct($url = null, $headers = array(), $data = array(), $o $this->headers = $headers; $this->data = $data; $this->options = $options; + + if (empty($this->options['cookies'])) { + $this->options['cookies'] = new Requests_Cookie_Jar(); + } } /** From f265e2551eea0290da291229fad3188815ca48f3 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 1 Oct 2013 00:25:12 +1000 Subject: [PATCH 4/7] Add isset/unset to the session object --- library/Requests/Session.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/library/Requests/Session.php b/library/Requests/Session.php index bd87ccf6d..8d24593d2 100755 --- a/library/Requests/Session.php +++ b/library/Requests/Session.php @@ -85,6 +85,24 @@ public function __set($key, $value) { $this->options[$key] = $value; } + /** + * Remove a property's value + * + * @param string $key Property key + */ + public function __isset($key) { + return isset($this->options[$key]); + } + + /** + * Remove a property's value + * + * @param string $key Property key + */ + public function __unset($key) { + $this->options[$key] = null; + } + /**#@+ * @see request() * @param string $url From b93972e13957f1ba9b165cb9705a7bef8016571f Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 1 Oct 2013 00:52:17 +1000 Subject: [PATCH 5/7] Update session phpdoc --- library/Requests/Session.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/library/Requests/Session.php b/library/Requests/Session.php index 8d24593d2..f03995132 100755 --- a/library/Requests/Session.php +++ b/library/Requests/Session.php @@ -9,6 +9,11 @@ /** * Session handler for persistent requests and default parameters * + * Allows various options to be set as default values, and merges both the + * options and URL properties together. A base URL can be set for all requests, + * with all subrequests resolved from this. Base options can be set (including + * a shared cookie jar), then overridden for individual requests. + * * @package Requests * @subpackage Session Handler */ @@ -29,6 +34,10 @@ class Requests_Session { /** * Base data for requests + * + * If both the base data and the per-request data are arrays, the data will + * be merged before sending the request. + * * @var array */ public $data = array(); @@ -36,6 +45,9 @@ class Requests_Session { /** * Base options for requests * + * The base options are merged with the per-request data for each request. + * The only default option is a shared cookie jar between requests. + * * Values here can also be set directly via properties on the Session * object, e.g. `$session->useragent = 'X';` * @@ -47,9 +59,9 @@ class Requests_Session { * Create a new session * * @param string|null $url Base URL for requests - * @param array $headers Headers - * @param array $data [description] - * @param array $options [description] + * @param array $headers Default headers for requests + * @param array $data Default data for requests + * @param array $options Default options for requests */ public function __construct($url = null, $headers = array(), $data = array(), $options = array()) { $this->url = $url; From ca7133474aedeb2f6884de71bd27f6e5ab8edafa Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 1 Oct 2013 01:10:18 +1000 Subject: [PATCH 6/7] Add basic tests for session handling --- tests/Session.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 tests/Session.php diff --git a/tests/Session.php b/tests/Session.php new file mode 100755 index 000000000..fc19f1e15 --- /dev/null +++ b/tests/Session.php @@ -0,0 +1,41 @@ +get('/get'); + $this->assertTrue($response->success); + $this->assertEquals('http://httpbin.org/get', $response->url); + + $data = json_decode($response->body, true); + $this->assertNotNull($data); + $this->assertArrayHasKey('url', $data); + $this->assertEquals('http://httpbin.org/get', $data['url']); + } + + public function testSharedCookies() { + $session = new Requests_Session('http://httpbin.org/'); + + $options = array( + 'follow_redirects' => false + ); + $response = $session->get('/cookies/set?requests-testcookie=testvalue', array(), $options); + $this->assertEquals(302, $response->status_code); + + // Check the cookies + $response = $session->get('/cookies'); + $this->assertTrue($response->success); + + // Check the response + $data = json_decode($response->body, true); + $this->assertNotNull($data); + $this->assertArrayHasKey('cookies', $data); + + $cookies = array( + 'requests-testcookie' => 'testvalue' + ); + $this->assertEquals($cookies, $data['cookies']); + } +} From ba1f66bbd4c76302b3a890f6feb9601555816547 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 1 Oct 2013 01:21:17 +1000 Subject: [PATCH 7/7] Add expanded session testing --- tests/Session.php | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/Session.php b/tests/Session.php index fc19f1e15..676c609da 100755 --- a/tests/Session.php +++ b/tests/Session.php @@ -1,6 +1,51 @@ 'testing', + 'X-TestHeader2' => 'requests-test' + ); + $data = array( + 'testdata' => 'value1', + 'test2' => 'value2', + 'test3' => array( + 'foo' => 'bar', + 'abc' => 'xyz' + ) + ); + $options = array( + 'testoption' => 'test', + 'foo' => 'bar' + ); + + $session = new Requests_Session('http://example.com/', $headers, $data, $options); + $this->assertEquals('http://example.com/', $session->url); + $this->assertEquals($headers, $session->headers); + $this->assertEquals($data, $session->data); + $this->assertEquals($options['testoption'], $session->options['testoption']); + + // Test via property access + $this->assertEquals($options['testoption'], $session->testoption); + + // Test setting new property + $session->newoption = 'foobar'; + $options['newoption'] = 'foobar'; + $this->assertEquals($options['newoption'], $session->options['newoption']); + + // Test unsetting property + unset($session->newoption); + $this->assertFalse(isset($session->newoption)); + + // Update property + $session->testoption = 'foobar'; + $options['testoption'] = 'foobar'; + $this->assertEquals($options['testoption'], $session->testoption); + + // Test getting invalid property + $this->assertNull($session->invalidoption); + } + public function testURLResolution() { $session = new Requests_Session('http://httpbin.org/');