From 3c44e00fc9e21ab1374d3a28d92c3cd4e74e4b34 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:14:25 +0200 Subject: [PATCH 01/12] Additions to phpunit.xml Allow running phpunit in tests folder alone. Generate coverage report. --- tests/phpunit.xml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 + + . + + + + + + From e02bc70ea9ec4b2b79110a55a846b5527948df63 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:18:57 +0200 Subject: [PATCH 02/12] Fix mock headers to spec. Headers should end in CRLF make them so. --- tests/Httpful/HttpfulTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index 03d7ecb..66b2fb4 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() @@ -228,7 +228,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'); @@ -298,7 +298,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); } From 83caf198878c6691326b7d4e7cf30563683d487f Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:20:14 +0200 Subject: [PATCH 03/12] Add new line at end of unit test file. So that diff may stap complaining about missing new lines. --- tests/Httpful/HttpfulTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index 66b2fb4..ef47673 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -342,4 +342,5 @@ class DemoMimeHandler extends \Httpful\Handlers\MimeHandlerAdapter { public function parse($body) { return 'custom parse'; } -} \ No newline at end of file +} + From a23e8c301dbe501ccb934c0ff7e690a334bd81b4 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:22:37 +0200 Subject: [PATCH 04/12] Additianal tests for Request. Test changes made to Accept and User Agent headers. --- tests/Httpful/HttpfulTest.php | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index ef47673..2485df3 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -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'; From 1741fd8427f8015c391023fcca844fa0641cc6b7 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:23:55 +0200 Subject: [PATCH 05/12] Additional response tests. Test changes made to hasErrors , raw_headers, _parseHeaders. Additional tests against __toString and _parseCode in an attempt to get the coverage up. --- tests/Httpful/HttpfulTest.php | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index 2485df3..f8a629d 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -291,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(); From ed0b7e639cd77cc347bc103c47da812a25a96a7d Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:26:37 +0200 Subject: [PATCH 06/12] Add raw_headers property to class. Although not implicitly required added for completion. --- src/Httpful/Response.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Httpful/Response.php b/src/Httpful/Response.php index 5778e76..1443202 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, From 458643e18f645b2e054dedfd1518b884bd4a1d67 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:28:08 +0200 Subject: [PATCH 07/12] Fix hasErrors and aditional comments. Only status codes 4xx and 5xx should be considered errors. 1xx are informational and therefor not an error. --- src/Httpful/Response.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Httpful/Response.php b/src/Httpful/Response.php index 1443202..bce95a0 100644 --- a/src/Httpful/Response.php +++ b/src/Httpful/Response.php @@ -43,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; } /** From bddd0402103fa00c7f186c00fed56e6fb588df61 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:30:01 +0200 Subject: [PATCH 08/12] Fix parser to consider CRLF ended headers. As per 2616 headers should end in CRLF parser should handle it appropriately. Additional headers are optional and prser should consider not getting headers to parse. --- src/Httpful/Response.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Httpful/Response.php b/src/Httpful/Response.php index bce95a0..1075405 100644 --- a/src/Httpful/Response.php +++ b/src/Httpful/Response.php @@ -111,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); From 75417262e5718a5d86f2d5cebb095140f023d8d5 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:34:56 +0200 Subject: [PATCH 09/12] Added User-Agent request header. Compose an User-Agent header by collecting available information. This behaviour can be ommitted by adding a custom User-Agent request header. --- src/Httpful/Request.php | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Httpful/Request.php b/src/Httpful/Request.php index bf40b44..6cff1c6 100644 --- a/src/Httpful/Request.php +++ b/src/Httpful/Request.php @@ -685,11 +685,33 @@ 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}"; + foreach ($this->headers as $header => $value) { $headers[] = "$header: $value"; From b78e9e82cd9614fbe137c01bde9439c4e16ca323 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:38:05 +0200 Subject: [PATCH 10/12] Complete Acceptable test header. If you can coneg against this Accept: header you can mark your implementation passed. Contains all the legal LWS. Preferable mime types are listed in reverse order, expected mime-type is added to the end. The order the server should sort these in are as follows 1 expected/type 0.9 text/html;level=3 0.8 text/plain 0.5 *.* This will also prevent you from getting 406 Not Acceptable errors. --- src/Httpful/Request.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Httpful/Request.php b/src/Httpful/Request.php index 6cff1c6..2a166a5 100644 --- a/src/Httpful/Request.php +++ b/src/Httpful/Request.php @@ -685,9 +685,6 @@ public function _curlPrep() curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - $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/'; @@ -712,6 +709,12 @@ public function _curlPrep() } $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"; From c9092f8a69b8737a0ceb2c0c431b8eb6def75f73 Mon Sep 17 00:00:00 2001 From: nickl- Date: Sun, 17 Jun 2012 09:48:31 +0200 Subject: [PATCH 11/12] Compose request raw_header. Mainly done to be able to test against the composed header without a bajor refactor. This is now similar to the response and although there are other ways to get this information from cURL this now makes HttpFul complete being able to provide both request and response headers. Note the HTTP version is hard coded this might require additional work but should be efficient for the majority of use cases. --- src/Httpful/Request.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Httpful/Request.php b/src/Httpful/Request.php index 2a166a5..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, @@ -719,6 +720,15 @@ public function _curlPrep() 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)) { From 79b323c5f275e5ee28d2a4e6271c85af67156b5d Mon Sep 17 00:00:00 2001 From: nickl- Date: Mon, 18 Jun 2012 09:33:14 +0200 Subject: [PATCH 12/12] Fix whitespace --- tests/Httpful/HttpfulTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Httpful/HttpfulTest.php b/tests/Httpful/HttpfulTest.php index f8a629d..524edca 100644 --- a/tests/Httpful/HttpfulTest.php +++ b/tests/Httpful/HttpfulTest.php @@ -176,7 +176,7 @@ function testAccept() $this->assertEquals(Mime::JSON, $r->expected_type); $r->_curlPrep(); - $this->assertContains('application/json', $r->raw_headers); + $this->assertContains('application/json', $r->raw_headers); } function testUserAgent() { @@ -185,7 +185,7 @@ function testUserAgent() $this->assertArrayHasKey('User-Agent', $r->headers); $r->_curlPrep(); - $this->assertContains('User-Agent: ACME/1.2.3', $r->raw_headers); + $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/')