Add Client class

Move Request::request() to Client::request()
This commit is contained in:
PJ Dietz 2014-07-24 20:41:32 -04:00
parent ad1e5a1782
commit 1a21b2b7d0
13 changed files with 275 additions and 161 deletions

5
.idea/encodings.xml Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

5
.idea/misc.xml Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" />
</project>

9
.idea/modules.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wellrested.iml" filepath="$PROJECT_DIR$/.idea/wellrested.iml" />
</modules>
</component>
</project>

7
.idea/vcs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

9
.idea/wellrested.iml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -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 ```php
// Prepare a request. // Prepare a request.
@ -189,8 +193,9 @@ $rqst->setUri('http://my.api.local/resources/');
$rqst->setMethod('POST'); $rqst->setMethod('POST');
$rqst->setBody(json_encode($newResource)); $rqst->setBody(json_encode($newResource));
// Make the request. // Use a Client to get a Response.
$resp = $rqst->request(); $client = new Client();
$resp = $client->request($rqst);
// Read the response. // Read the response.
if ($resp->getStatusCode() === 201) { 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 and License
--------------------- ---------------------
Copyright © 2014 by PJ Dietz Copyright © 2014 by PJ Dietz

View File

@ -0,0 +1,122 @@
<?php
/**
* pjdietz\WellRESTed\Client
*
* @author PJ Dietz <pj@pjdietz.com>
* @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;
}
}

View File

@ -22,6 +22,20 @@ interface RequestInterface
*/ */
public function getMethod(); 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. * Return path component of the request URI.
* *
@ -29,6 +43,13 @@ interface RequestInterface
*/ */
public function getPath(); public function getPath();
/**
* Return the HTTP port
*
* @return int
*/
public function getPort();
/** /**
* Return an associative array of query paramters. * Return an associative array of query paramters.
* *
@ -47,6 +68,13 @@ interface RequestInterface
*/ */
public function getHeader($headerName); public function getHeader($headerName);
/**
* Return an associative array of headers.
*
* @return array
*/
public function getHeaders();
/** /**
* Return the body of the request. * Return the body of the request.
* *

View File

@ -72,20 +72,6 @@ abstract class Message
return $this->headers; 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. * Return the value of a given header, or false if it does not exist.
* *

View File

@ -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. * Return the default port for the currently set scheme.
* *

82
test/ClientTest.php Normal file
View File

@ -0,0 +1,82 @@
<?php
use pjdietz\WellRESTed\Client;
use pjdietz\WellRESTed\Request;
use pjdietz\WellRESTed\Test;
class ClientTest extends \PHPUnit_Framework_TestCase
{
public function testFake()
{
$this->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
]],
];
}
}

View File

@ -45,15 +45,6 @@ class RequestBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(3, count($this->request->getHeaders())); $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 * @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
]],
];
}
} }

View File

@ -173,6 +173,4 @@ class ResponseBuilderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($body, $captured); $this->assertEquals($body, $captured);
} }
} }