diff --git a/src/Httpful/Request.php b/src/Httpful/Request.php index bf40b44..96e6f18 100644 --- a/src/Httpful/Request.php +++ b/src/Httpful/Request.php @@ -27,6 +27,7 @@ class Request public $uri, $method = Http::GET, $headers = array(), + $raw_headers = '', $strict_ssl = false, $content_type, $expected_type, @@ -685,15 +686,49 @@ public function _curlPrep() curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $headers = array("Content-Type: {$this->content_type}"); - - $headers[] = !empty($this->expected_type) ? - "Accept: {$this->expected_type}, text/plain" : - "Accept: */*"; + $headers = array(); + if (!isset($this->headers['User-Agent'])) { + $user_agent = 'User-Agent: HttpFul/1.0 (cURL/'; + $curl = \curl_version(); + if (isset($curl['version'])) + $user_agent .= $curl['version']; + else + $user_agent .= '?.?.?'; + $user_agent .= ' PHP/'.PHP_VERSION.' ('.PHP_OS.')'; + if (isset($_SERVER['SERVER_SOFTWARE'])) + $user_agent .= ' '.\preg_replace('~PHP/[\d\.]+~U', '', $_SERVER['SERVER_SOFTWARE']); + else { + if (isset($_SERVER['TERM_PROGRAM'])) + $user_agent .= " {$_SERVER['TERM_PROGRAM']}"; + if (isset($_SERVER['TERM_PROGRAM_VERSION'])) + $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}"; + } + if (isset($_SERVER['HTTP_USER_AGENT'])) + $user_agent .= " {$_SERVER['HTTP_USER_AGENT']}"; + $user_agent .= ')'; + $headers[] = $user_agent; + } + $headers[] = "Content-Type: {$this->content_type}"; + + // http://pretty-rfc.herokuapp.com/RFC2616#header.accept + $accept = "Accept: */*; q=0.5, text/plain; q=0.8,\r\n\t" . + 'text/html;level=3; q=0.9'; + if (!empty($this->expected_type)) + $accept .= ", {$this->expected_type}"; + $headers[] = $accept; foreach ($this->headers as $header => $value) { $headers[] = "$header: $value"; } + + $url = \parse_url($this->uri); + $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : ''); + $this->raw_headers = "{$this->method} $path HTTP/1.1\r\n"; + $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : ''); + $this->raw_headers .= "Host: $host\r\n"; + $this->raw_headers .= \implode("\r\n", $headers); + $this->raw_headers .= "\r\n"; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); if (isset($this->payload)) { diff --git a/src/Httpful/Response.php b/src/Httpful/Response.php index 5778e76..1075405 100644 --- a/src/Httpful/Response.php +++ b/src/Httpful/Response.php @@ -13,6 +13,7 @@ class Response public $body, $raw_body, $headers, + $raw_headers, $request, $code = 0, $content_type, @@ -42,11 +43,21 @@ public function __construct($body, $headers, Request $request) } /** - * @return bool Did we receive a 400 or 500? + * Status Code Definitions + * + * Informational 1xx + * Successful 2xx + * Redirection 3xx + * Client Error 4xx + * Server Error 5xx + * + * http://pretty-rfc.herokuapp.com/RFC2616#status.codes + * + * @return bool Did we receive a 4xx or 5xx? */ public function hasErrors() { - return $this->code < 100 || $this->code >= 400; + return $this->code >= 400; } /** @@ -100,7 +111,8 @@ public function _parse($body) */ public function _parseHeaders($headers) { - $headers = preg_split("/(\r|\n)+/", $headers); + $headers = preg_split("/(\r|\n)+/", $headers, -1, \PREG_SPLIT_NO_EMPTY); + $parse_headers = array(); for ($i = 1; $i < count($headers); $i++) { list($key, $raw_value) = explode(':', $headers[$i], 2); $parse_headers[trim($key)] = trim($raw_value); diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index 03d7ecb..524edca 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -28,13 +28,13 @@ class HttpfulTest extends \PHPUnit_Framework_TestCase "HTTP/1.1 200 OK Content-Type: application/json Connection: keep-alive -Transfer-Encoding: chunked"; +Transfer-Encoding: chunked\r\n"; const SAMPLE_JSON_RESPONSE = '{"key":"value","object":{"key":"value"},"array":[1,2,3,4]}'; const SAMPLE_CSV_HEADER = "HTTP/1.1 200 OK Content-Type: text/csv Connection: keep-alive -Transfer-Encoding: chunked"; +Transfer-Encoding: chunked\r\n"; const SAMPLE_CSV_RESPONSE = "Key1,Key2 Value1,Value2 @@ -44,12 +44,12 @@ class HttpfulTest extends \PHPUnit_Framework_TestCase "HTTP/1.1 200 OK Content-Type: application/xml Connection: keep-alive -Transfer-Encoding: chunked"; +Transfer-Encoding: chunked\r\n"; const SAMPLE_VENDOR_HEADER = "HTTP/1.1 200 OK Content-Type: application/vnd.nategood.message+xml Connection: keep-alive -Transfer-Encoding: chunked"; +Transfer-Encoding: chunked\r\n"; const SAMPLE_VENDOR_TYPE = "application/vnd.nategood.message+xml"; function testInit() @@ -169,7 +169,35 @@ function testIni() Request::resetIni(); } - function testAuthSetup() + function testAccept() + { + $r = Request::get('http://example.com/') + ->expectsType(Mime::JSON); + + $this->assertEquals(Mime::JSON, $r->expected_type); + $r->_curlPrep(); + $this->assertContains('application/json', $r->raw_headers); + } + function testUserAgent() + { + $r = Request::get('http://example.com/') + ->withUserAgent('ACME/1.2.3'); + + $this->assertArrayHasKey('User-Agent', $r->headers); + $r->_curlPrep(); + $this->assertContains('User-Agent: ACME/1.2.3', $r->raw_headers); + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); + + $r = Request::get('http://example.com/') + ->withUserAgent(''); + + $this->assertArrayHasKey('User-Agent', $r->headers); + $r->_curlPrep(); + $this->assertContains('User-Agent:', $r->raw_headers); + $this->assertNotContains('User-Agent: HttpFul/1.0', $r->raw_headers); + } + + function testAuthSetup() { $username = 'nathan'; $password = 'opensesame'; @@ -228,7 +256,7 @@ function testParsingContentTypeCharset() // $response = new Response(SAMPLE_JSON_RESPONSE, "", $req); // // Check default content type of iso-8859-1 $response = new Response(self::SAMPLE_JSON_RESPONSE, "HTTP/1.1 200 OK -Content-Type: text/plain; charset=utf-8", $req); +Content-Type: text/plain; charset=utf-8\r\n", $req); $this->assertInternalType('array', $response->headers); $this->assertEquals($response->headers['Content-Type'], 'text/plain; charset=utf-8'); $this->assertEquals($response->content_type, 'text/plain'); @@ -263,6 +291,53 @@ function testParseHeaders() $this->assertEquals('application/json', $response->headers['Content-Type']); } + function testRawHeaders() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertContains('Content-Type: application/json', $response->raw_headers); + } + + function testHasErrors() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response('', "HTTP/1.1 100 Continue\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 200 OK\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 300 Multiple Choices\r\n", $req); + $this->assertFalse($response->hasErrors()); + $response = new Response('', "HTTP/1.1 400 Bad Request\r\n", $req); + $this->assertTrue($response->hasErrors()); + $response = new Response('', "HTTP/1.1 500 Internal Server Error\r\n", $req); + $this->assertTrue($response->hasErrors()); + } + + function test_parseCode() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $code = $response->_parseCode("HTTP/1.1 406 Not Acceptable\r\n"); + $this->assertEquals(406, $code); + } + + function testToString() + { + $req = Request::init()->sendsAndExpects(Mime::JSON); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $this->assertEquals(self::SAMPLE_JSON_RESPONSE, (string)$response); + } + + function test_parseHeaders() + { + $req = Request::init(); + $response = new Response(self::SAMPLE_JSON_RESPONSE, self::SAMPLE_JSON_HEADER, $req); + $parse_headers = $response->_parseHeaders(self::SAMPLE_JSON_HEADER); + $this->assertEquals(3, count($parse_headers)); + $this->assertEquals('application/json', $parse_headers['Content-Type']); + $this->assertArrayHasKey('Connection', $parse_headers); + } + function testDetectContentType() { $req = Request::init(); @@ -298,7 +373,7 @@ function testMissingContentType() $response = new Response('Nathan', "HTTP/1.1 200 OK Connection: keep-alive -Transfer-Encoding: chunked", $request); +Transfer-Encoding: chunked\r\n", $request); $this->assertEquals("", $response->content_type); } @@ -342,4 +417,5 @@ class DemoMimeHandler extends \Httpful\Handlers\MimeHandlerAdapter { public function parse($body) { return 'custom parse'; } -} \ No newline at end of file +} + diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 1df78ad..8f62e80 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -1,7 +1,9 @@ - - - Httpful - - - \ No newline at end of file + + . + + + + + +