From 1a21b2b7d05f007ffa8c7f3574326d3e35ff0041 Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Thu, 24 Jul 2014 20:41:32 -0400 Subject: [PATCH] Add Client class Move Request::request() to Client::request() --- .idea/encodings.xml | 5 + .idea/misc.xml | 5 + .idea/modules.xml | 9 ++ .idea/vcs.xml | 7 + .idea/wellrested.iml | 9 ++ README.md | 17 ++- src/pjdietz/WellRESTed/Client.php | 122 ++++++++++++++++++ .../Interfaces/RequestInterface.php | 28 ++++ src/pjdietz/WellRESTed/Message.php | 14 -- src/pjdietz/WellRESTed/Request.php | 85 ------------ test/ClientTest.php | 82 ++++++++++++ test/RequestTest.php | 51 -------- test/ResponseTest.php | 2 - 13 files changed, 275 insertions(+), 161 deletions(-) create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/wellrested.iml create mode 100644 src/pjdietz/WellRESTed/Client.php create mode 100644 test/ClientTest.php diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..e206d70 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1162f43 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bc416df --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c80f219 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/wellrested.iml b/.idea/wellrested.iml new file mode 100644 index 0000000..6b8184f --- /dev/null +++ b/.idea/wellrested.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/README.md b/README.md index a44341f..63998aa 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,11 @@ if ($rqst->getMethod() === 'PUT') { } ``` -The Request class can also make a request to another server and provide the response as a Response object. (This feature requires [PHP cURL](http://php.net/manual/en/book.curl.php).) +### HTTP Client + +You can also use the `Client` class to make a request using cURL. + +(This feature requires [PHP cURL](http://php.net/manual/en/book.curl.php).) ```php // Prepare a request. @@ -189,8 +193,9 @@ $rqst->setUri('http://my.api.local/resources/'); $rqst->setMethod('POST'); $rqst->setBody(json_encode($newResource)); -// Make the request. -$resp = $rqst->request(); +// Use a Client to get a Response. +$client = new Client(); +$resp = $client->request($rqst); // Read the response. if ($resp->getStatusCode() === 201) { @@ -200,12 +205,6 @@ if ($resp->getStatusCode() === 201) { ``` -More Examples ---------------- - -For more examples, see the project [pjdietz/wellrested-samples](https://github.com/pjdietz/wellrested-samples). **Not yet updated for version 2.0** - - Copyright and License --------------------- Copyright © 2014 by PJ Dietz diff --git a/src/pjdietz/WellRESTed/Client.php b/src/pjdietz/WellRESTed/Client.php new file mode 100644 index 0000000..670d9e6 --- /dev/null +++ b/src/pjdietz/WellRESTed/Client.php @@ -0,0 +1,122 @@ + + * @copyright Copyright 2014 by PJ Dietz + * @license MIT + */ + +namespace pjdietz\WellRESTed; + +use pjdietz\WellRESTed\Exceptions\CurlException; +use pjdietz\WellRESTed\Interfaces\RequestInterface; +use pjdietz\WellRESTed\Interfaces\ResponseInterface; + +/** + * Class for making HTTP requests using cURL. + */ +class Client +{ + /** @var array cURL options */ + private $curlOpts; + + public function __construct(array $curlOpts = null) { + if (is_array($curlOpts)) { + $this->curlOpts = $curlOpts; + } else { + $this->curlOpts = array(); + } + } + + /** + * Make an HTTP request and return a Response. + * + * @param RequestInterface $rqst + * @param array $curlOpts Option array of cURL options + * @throws \pjdietz\WellRESTed\Exceptions\CurlException + * @return ResponseInterface + */ + public function request(RequestInterface $rqst, $curlOpts = null) + { + $ch = curl_init(); + + $headers = array(); + foreach ($rqst->getHeaders() as $field => $value) { + $lines[] = sprintf('%s: %s', $field, $value); + } + + $options = $this->curlOpts; + $options[CURLOPT_URL] = $rqst->getUri(); + $options[CURLOPT_PORT] = $rqst->getPort(); + $options[CURLOPT_HEADER] = 1; + $options[CURLOPT_RETURNTRANSFER] = 1; + $options[CURLOPT_HTTPHEADER] = $headers; + + // Set the method. Include the body, if needed. + switch ($rqst->getMethod()) { + case 'GET': + $options[CURLOPT_HTTPGET] = 1; + break; + case 'POST': + $options[CURLOPT_POST] = 1; + $options[CURLOPT_POSTFIELDS] = $rqst->getBody(); + break; + case 'PUT': + $options[CURLOPT_CUSTOMREQUEST] = 'PUT'; + $options[CURLOPT_POSTFIELDS] = $rqst->getBody(); + break; + default: + $options[CURLOPT_CUSTOMREQUEST] = $rqst->getMethod(); + $options[CURLOPT_POSTFIELDS] = $rqst->getBody(); + break; + } + + // Override cURL options with the user options passed in. + if ($curlOpts) { + foreach ($curlOpts as $optKey => $optValue) { + $options[$optKey] = $optValue; + } + } + + // Set the cURL options. + curl_setopt_array($ch, $options); + + // Make the cURL request. + $result = curl_exec($ch); + + // Throw an exception in the event of a cURL error. + if ($result === false) { + $error = curl_error($ch); + $errno = curl_errno($ch); + curl_close($ch); + throw new CurlException($error, $errno); + } + + // Make a reponse to populate and return with data obtained via cURL. + $resp = new Response(); + + $resp->setStatusCode(curl_getinfo($ch, CURLINFO_HTTP_CODE)); + + // Split the result into headers and body. + $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); + $headers = substr($result, 0, $headerSize); + $body = substr($result, $headerSize); + + // Set the body. Do not auto-add the Content-length header. + $resp->setBody($body, false); + + // Iterate over the headers line by line and add each one. + foreach (explode("\r\n", $headers) as $header) { + if (strpos($header, ':')) { + list ($headerName, $headerValue) = explode(':', $header, 2); + $resp->setHeader($headerName, ltrim($headerValue)); + } + } + + curl_close($ch); + + return $resp; + } +} diff --git a/src/pjdietz/WellRESTed/Interfaces/RequestInterface.php b/src/pjdietz/WellRESTed/Interfaces/RequestInterface.php index 1770306..e697232 100644 --- a/src/pjdietz/WellRESTed/Interfaces/RequestInterface.php +++ b/src/pjdietz/WellRESTed/Interfaces/RequestInterface.php @@ -22,6 +22,20 @@ interface RequestInterface */ public function getMethod(); + /** + * Return the full URI for the request. + * + * @return string + */ + public function getUri(); + + /** + * Return the hostname portion of the URI + * + * @return string + */ + public function getHostname(); + /** * Return path component of the request URI. * @@ -29,6 +43,13 @@ interface RequestInterface */ public function getPath(); + /** + * Return the HTTP port + * + * @return int + */ + public function getPort(); + /** * Return an associative array of query paramters. * @@ -47,6 +68,13 @@ interface RequestInterface */ public function getHeader($headerName); + /** + * Return an associative array of headers. + * + * @return array + */ + public function getHeaders(); + /** * Return the body of the request. * diff --git a/src/pjdietz/WellRESTed/Message.php b/src/pjdietz/WellRESTed/Message.php index 8596cc0..5f66f5a 100644 --- a/src/pjdietz/WellRESTed/Message.php +++ b/src/pjdietz/WellRESTed/Message.php @@ -72,20 +72,6 @@ abstract class Message return $this->headers; } - /** - * Return an array containing one string for each header as "field: value" - * - * @return string - */ - public function getHeaderLines() - { - $lines = array(); - foreach ($this->headers as $field => $value) { - $lines[] = sprintf('%s: %s', $field, $value); - } - return $lines; - } - /** * Return the value of a given header, or false if it does not exist. * diff --git a/src/pjdietz/WellRESTed/Request.php b/src/pjdietz/WellRESTed/Request.php index f57b4b0..6ef078e 100644 --- a/src/pjdietz/WellRESTed/Request.php +++ b/src/pjdietz/WellRESTed/Request.php @@ -336,91 +336,6 @@ class Request extends Message implements RequestInterface // ------------------------------------------------------------------------- - /** - * Make a cURL request out of the instance and return a Response. - * - * @param array|null $curlOpts Associative array of options to set using curl_setopt_array before making the request. - * @throws Exceptions\CurlException - * @return Response - */ - public function request($curlOpts = null) - { - $ch = curl_init(); - - $options = array( - CURLOPT_URL => $this->getUri(), - CURLOPT_PORT => $this->port, - CURLOPT_HEADER => 1, - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_HTTPHEADER => $this->getHeaderLines() - ); - - // Set the method. Include the body, if needed. - switch ($this->method) { - case 'GET': - $options[CURLOPT_HTTPGET] = 1; - break; - case 'POST': - $options[CURLOPT_POST] = 1; - $options[CURLOPT_POSTFIELDS] = $this->body; - break; - case 'PUT': - $options[CURLOPT_CUSTOMREQUEST] = 'PUT'; - $options[CURLOPT_POSTFIELDS] = $this->body; - break; - default: - $options[CURLOPT_CUSTOMREQUEST] = $this->method; - $options[CURLOPT_POSTFIELDS] = $this->body; - break; - } - - // Override cURL options with the user options passed in. - if ($curlOpts) { - foreach ($curlOpts as $optKey => $optValue) { - $options[$optKey] = $optValue; - } - } - - // Set the cURL options. - curl_setopt_array($ch, $options); - - // Make the cURL request. - $result = curl_exec($ch); - - // Throw an exception in the event of a cURL error. - if ($result === false) { - $error = curl_error($ch); - $errno = curl_errno($ch); - curl_close($ch); - throw new CurlException($error, $errno); - } - - // Make a reponse to populate and return with data obtained via cURL. - $resp = new Response(); - - $resp->setStatusCode(curl_getinfo($ch, CURLINFO_HTTP_CODE)); - - // Split the result into headers and body. - $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); - $headers = substr($result, 0, $headerSize); - $body = substr($result, $headerSize); - - // Set the body. Do not auto-add the Content-length header. - $resp->setBody($body, false); - - // Iterate over the headers line by line and add each one. - foreach (explode("\r\n", $headers) as $header) { - if (strpos($header, ':')) { - list ($headerName, $headerValue) = explode(':', $header, 2); - $resp->setHeader($headerName, ltrim($headerValue)); - } - } - - curl_close($ch); - - return $resp; - } - /** * Return the default port for the currently set scheme. * diff --git a/test/ClientTest.php b/test/ClientTest.php new file mode 100644 index 0000000..241f073 --- /dev/null +++ b/test/ClientTest.php @@ -0,0 +1,82 @@ +assertTrue(true); + } + + /** + * @dataProvider curlProvider + */ + public function testCurl($method, $uri, $opts, $code) + { + $rqst = $this->getMockBuilder('pjdietz\WellRESTed\Request')->getMock(); + $rqst->expects($this->any()) + ->method("getUri") + ->will($this->returnValue($uri)); + $rqst->expects($this->any()) + ->method("getMethod") + ->will($this->returnValue($method)); + $rqst->expects($this->any()) + ->method("getPort") + ->will($this->returnValue(80)); + $rqst->expects($this->any()) + ->method("getHeaders") + ->will($this->returnValue(array( + "Cache-control" => "max-age=0" + ))); + + $client = new Client(array(CURLOPT_HTTPHEADER => array("Cache-control" => "max-age=0"))); + $resp = $client->request($rqst, $opts); + $this->assertEquals($code, $resp->getStatusCode()); + } + + public function curlProvider() + { + return [ + ["GET", "http://icanhasip.com", [ + [CURLOPT_MAXREDIRS => 2] + ], 200], + ["POST", "http://icanhasip.com", [], 200], + ["PUT", "http://icanhasip.com", [], 405], + ["DELETE", "http://icanhasip.com", [], 405] + ]; + } + + /** + * @dataProvider curlErrorProvider + * @expectedException \pjdietz\WellRESTed\Exceptions\CurlException + */ + public function testErrorCurl($uri, $opts) + { + $rqst = $this->getMockBuilder('pjdietz\WellRESTed\Request')->getMock(); + $rqst->expects($this->any()) + ->method("getUri") + ->will($this->returnValue($uri)); + $rqst->expects($this->any()) + ->method("getHeaders") + ->will($this->returnValue(array( + "Cache-control" => "max-age=0" + ))); + + $rqst = new Request($uri); + $client = new Client(); + $client->request($rqst, $opts); + } + + public function curlErrorProvider() + { + return [ + ["http://localhost:9991", [ + CURLOPT_FAILONERROR, true, + CURLOPT_TIMEOUT_MS, 10 + ]], + ]; + } +} diff --git a/test/RequestTest.php b/test/RequestTest.php index cd66245..3f3fd16 100644 --- a/test/RequestTest.php +++ b/test/RequestTest.php @@ -45,15 +45,6 @@ class RequestBuilderTest extends \PHPUnit_Framework_TestCase $this->assertEquals(3, count($this->request->getHeaders())); } - /** - * @dataProvider headerProvider - */ - public function testHeaderLines($name, $value, $testName) - { - $line = "$name: $value"; - $this->assertTrue(in_array($line, $this->request->getHeaderLines())); - } - /** * @dataProvider headerProvider */ @@ -498,46 +489,4 @@ class RequestBuilderTest extends \PHPUnit_Framework_TestCase ]; } - /** - * @dataProvider curlProvider - */ - public function testCurl($method, $uri, $opts, $code) - { - $rqst = new Request($uri); - $rqst->setMethod($method); - $resp = $rqst->request($opts); - $this->assertEquals($code, $resp->getStatusCode()); - } - - public function curlProvider() - { - return [ - ["GET", "http://icanhasip.com", [ - [CURLOPT_MAXREDIRS => 2] - ], 200], - ["POST", "http://icanhasip.com", [], 200], - ["PUT", "http://icanhasip.com", [], 405], - ["DELETE", "http://icanhasip.com", [], 405] - ]; - } - - /** - * @dataProvider curlErrorProvider - * @expectedException \pjdietz\WellRESTed\Exceptions\CurlException - */ - public function testErrorCurl($uri, $opts) - { - $rqst = new Request($uri); - $resp = $rqst->request($opts); - } - - public function curlErrorProvider() - { - return [ - ["http://localhost:9991", [ - CURLOPT_FAILONERROR, true, - CURLOPT_TIMEOUT_MS, 10 - ]], - ]; - } } diff --git a/test/ResponseTest.php b/test/ResponseTest.php index 0e80a27..3d30368 100644 --- a/test/ResponseTest.php +++ b/test/ResponseTest.php @@ -173,6 +173,4 @@ class ResponseBuilderTest extends \PHPUnit_Framework_TestCase $this->assertEquals($body, $captured); } - - }