<?php
namespace ZeusTest;
use PHPUnit_Framework_TestCase;
use Zend\Http\Request;
use Zend\Http\Response;
use Zeus\ServerService\Http\Message\Message;
use Zeus\ServerService\Shared\React\MessageComponentInterface;
use ZeusTest\Helpers\TestConnection;
class HttpAdapterTest extends PHPUnit_Framework_TestCase
{
protected function getTmpDir()
{
return __DIR__ . '/tmp/';
}
public function setUp()
{
parent::setUp();
ob_start();
}
public function tearDown()
{
ob_end_clean();
$files = glob($this->getTmpDir() . '*');
foreach ($files as $file) {
if (is_file($file)) {
unlink($file);
}
}
parent::tearDown();
}
public function testIfMessageHasBeenDispatched()
{
$message = $this->getHttpGetRequestString("/");
$dispatcherLaunched = false;
$this->getHttpAdapter(function() use (& $dispatcherLaunched) {$dispatcherLaunched = true;})->onMessage(new TestConnection(), $message);
$this->assertTrue($dispatcherLaunched, "Dispatcher should be called");
}
public function testIfHttp10ConnectionIsClosedAfterSingleRequest()
{
$message = $this->getHttpGetRequestString("/");
$testConnection = new TestConnection();
$this->getHttpAdapter(function() {})->onMessage($testConnection, $message);
$this->assertTrue($testConnection->isConnectionClosed(), "HTTP 1.0 connection should be closed after request");
}
public function testIfHttp10KeepAliveConnectionIsOpenAfterSingleRequest()
{
$message = $this->getHttpGetRequestString("/", ["Connection" => "keep-alive"]);
$testConnection = new TestConnection();
$this->getHttpAdapter(function() {})->onMessage($testConnection, $message);
$this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.0 keep-alive connection should be left open after request");
}
public function testIfHttp11ConnectionIsOpenAfterSingleRequest()
{
$message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
$testConnection = new TestConnection();
$this->getHttpAdapter(function() {})->onMessage($testConnection, $message);
$this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be left open after request");
}
public function testIfHttp11ConnectionIsClosedWithConnectionHeaderAfterSingleRequest()
{
$message = $this->getHttpGetRequestString("/", ["Connection" => "close", 'Host' => 'localhost'], "1.1");
$testConnection = new TestConnection();
$this->getHttpAdapter(function() {})->onMessage($testConnection, $message);
$this->assertTrue($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be closed when Connection: close header is present");
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Missing host header
* @expectedExceptionCode 400
*/
public function testIfHttp11HostHeaderIsMandatory()
{
$message = $this->getHttpGetRequestString("/", [], "1.1");
$testConnection = new TestConnection();
/** @var Response $response */
$response = null;
$requestHandler = function($_request, $_response) use (&$response) {$response = $_response; };
$httpAdapter = $this->getHttpAdapter($requestHandler);
$httpAdapter->onMessage($testConnection, $message);
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(400, $rawResponse->getStatusCode(), "HTTP/1.1 request with missing host header should generate 400 error message");
$testConnection = new TestConnection();
$message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
$httpAdapter->onMessage($testConnection, $message);
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(200, $rawResponse->getStatusCode(), "HTTP/1.1 request with valid host header should generate 200 OK message");
}
public function testIfPostDataIsCorrectlyInterpreted()
{
$postData = ["test1" => "test2", "test3" => "test4", "test4" => ["aaa" => "bbb"], "test5" => 12];
$message = $this->getHttpPostRequestString("/", [], $postData);
for($chunkSize = 1, $messageSize = strlen($message); $chunkSize < $messageSize; $chunkSize++) {
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$errorOccured = false;
$errorHandler = function($request, $response, $exception) use (& $errorOccured) {
$errorOccured = $exception;
};
$requestHandler = function ($_request, $_response) use (&$request, &$response) {
$request = $_request;
$response = $_response;
};
$httpAdapter = $this->getHttpAdapter($requestHandler, $errorHandler);
$chunks = str_split($message, $chunkSize);
foreach ($chunks as $index => $chunk) {
$httpAdapter->onMessage($testConnection, $chunk);
if ($errorOccured) {
$this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccured->getMessage());
}
}
$this->assertEquals(200, $response->getStatusCode(), "HTTP/1.1 request should generate 200 OK message");
$this->assertEquals("/", $request->getUriString());
foreach ($postData as $key => $value) {
$this->assertEquals($value, $request->getPost($key), "Request object should contain valid POST data for key $key");
}
}
}
public function testIfOptionsHeadAndTraceReturnEmptyBody()
{
foreach (["HEAD", "TRACE", "OPTIONS"] as $method) {
$testString = "$method test string";
$message = $this->getHttpCustomMethodRequestString($method, "/", []);
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$requestHandler = function($_request, $_response) use (&$request, &$response, $testString) {$request = $_request; $response = $_response; echo $testString; };
$httpAdapter = $this->getHttpAdapter($requestHandler, $requestHandler);
$httpAdapter->onMessage($testConnection, $message);
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(0, strlen($rawResponse->getBody()), "No content should be returned by $method response");
$this->assertEquals(strlen($testString), $response->getHeaders()->get('Content-Length')->getFieldValue(), "Incorrect Content-Length header returned by $method response");
}
}
public function testIfRequestBodyIsReadCorrectly()
{
$fileContent = ['Content of a.txt.', '<!DOCTYPE html><title>Content of a.html.</title>', 'a?b'];
$message = $this->getFileUploadRequest('POST', $fileContent);
for($chunkSize = 1, $messageSize = strlen($message); $chunkSize < $messageSize; $chunkSize++) {
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$fileList = [];
$tmpDir = $this->getTmpDir();
$errorOccured = false;
$errorHandler = function($request, $response, $exception) use (& $errorOccured) {
$errorOccured = $exception;
};
$requestHandler = function (Request $_request, Response $_response) use (&$request, &$response, & $fileList, $tmpDir) {
$request = $_request;
$response = $_response;
foreach ($request->getFiles() as $formName => $fileArray) {
foreach ($fileArray as $file) {
rename($file['tmp_name'], $tmpDir . $file['name']);
$fileList[$formName] = $file['name'];
}
}
};
$httpAdapter = $this->getHttpAdapter($requestHandler, $errorHandler);
$chunks = str_split($message, $chunkSize);
foreach ($chunks as $index => $chunk) {
$httpAdapter->onMessage($testConnection, $chunk);
if ($errorOccured) {
$this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccured->getMessage());
}
}
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(200, $rawResponse->getStatusCode(), "HTTP response should return 200 OK status, message received: " . $rawResponse->getContent());
$this->assertEquals(3, $request->getFiles()->count(), "HTTP request contains 3 files but Request object reported " . $request->getFiles()->count());
foreach ($fileContent as $index => $content) {
$name = "file" . ($index + 1);
$this->assertEquals($content, file_get_contents($this->getTmpDir() . $fileList[$name]), "Content of the uploaded file should match the original for file " . $fileList[$name]);
}
}
}
public function testRegularPostRequestWithBody()
{
$message = "POST / HTTP/1.0
Content-Length: 11
Hello_World";
for($chunkSize = 1, $messageSize = strlen($message); $chunkSize < $messageSize; $chunkSize++) {
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$fileList = [];
$tmpDir = $this->getTmpDir();
$errorOccured = false;
$errorHandler = function ($request, $response, $exception) use (& $errorOccured) {
$errorOccured = $exception;
};
$requestHandler = function (Request $_request, Response $_response) use (&$request, &$response, & $fileList, $tmpDir) {
$request = $_request;
$response = $_response;
return $request;
};
$httpAdapter = $this->getHttpAdapter($requestHandler, $errorHandler);
$chunks = str_split($message, $chunkSize);
foreach ($chunks as $index => $chunk) {
$httpAdapter->onMessage($testConnection, $chunk);
if ($errorOccured) {
$this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccured->getMessage());
}
}
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(200, $rawResponse->getStatusCode(), "HTTP response should return 200 OK status, message received: " . $rawResponse->getContent());
$this->assertEquals("Hello_World", $request->getContent(), "HTTP response should have returned 'Hello_World', received: " . $request->getContent());
}
}
public function testChunkedPostRequest()
{
$message = "POST / HTTP/1.0
Transfer-Encoding: chunked
6
Hello_
5
World
0
";
for($chunkSize = 1, $messageSize = strlen($message); $chunkSize < $messageSize; $chunkSize++) {
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$fileList = [];
$tmpDir = $this->getTmpDir();
$errorOccured = false;
$errorHandler = function ($request, $response, $exception) use (& $errorOccured) {
$errorOccured = $exception;
};
$requestHandler = function (Request $_request, Response $_response) use (&$request, &$response, & $fileList, $tmpDir) {
$request = $_request;
$response = $_response;
return $request;
};
$httpAdapter = $this->getHttpAdapter($requestHandler, $errorHandler);
$chunks = str_split($message, $chunkSize);
foreach ($chunks as $index => $chunk) {
$httpAdapter->onMessage($testConnection, $chunk);
if ($errorOccured) {
$this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccured->getMessage());
}
}
try {
$rawResponse = Response::fromString($testConnection->getSentData());
} catch (\Exception $e) {
$this->fail("Invalid response detected in chunk $chunkSize: " . json_encode($chunks));
$this->fail("Invalid response detected in chunk $chunkSize: " . $testConnection->getSentData());
}
$this->assertEquals(200, $rawResponse->getStatusCode(), "HTTP response should return 200 OK status, message received: " . $rawResponse->getContent());
$this->assertEquals("Hello_World", $request->getContent(), "HTTP response should have returned 'Hello_World', received: " . $request->getContent());
}
}
public function testIfRequestBodyIsNotAvailableInFileUploadMode()
{
$fileContent = ['Content of a.txt.', '<!DOCTYPE html><title>Content of a.html.</title>', 'a?b'];
$message = $this->getFileUploadRequest('POST', $fileContent);
$testConnection = new TestConnection();
/** @var Request $request */
$request = null;
/** @var Response $response */
$response = null;
$requestHandler = function($_request, $_response) use (&$request, &$response) {$request = $_request; $response = $_response; };
$httpAdapter = $this->getHttpAdapter($requestHandler, $requestHandler);
$httpAdapter->onMessage($testConnection, $message);
$rawResponse = Response::fromString($testConnection->getSentData());
$this->assertEquals(200, $rawResponse->getStatusCode(), "HTTP response should return 200 OK status, message received: " . $rawResponse->getContent());
$this->assertEquals(3, $request->getFiles()->count(), "HTTP request contains 3 files but Request object reported " . $request->getFiles()->count());
$this->assertEquals(0, strlen($request->getContent()), "No content should be present in request object in case of multipart data: " . $request->getContent());
}
protected function getBuffer()
{
$result = ob_get_clean();
ob_start();
return $result;
}
/**
* @param callback $dispatcher
* @param callback $errorHandler
* @return MessageComponentInterface
*/
protected function getHttpAdapter($dispatcher, $errorHandler = null)
{
$adapter = new Message($dispatcher, $errorHandler);
return $adapter;
}
protected function getHttpGetRequestString($uri, $headers = [], $protocolVersion = '1.0')
{
$request = "GET $uri HTTP/$protocolVersion\r\n";
foreach ($headers as $headerName => $headerValue) {
$request .= "$headerName: $headerValue\r\n";
}
$request .= "\r\n";
return $request;
}
protected function getHttpCustomMethodRequestString($method, $uri, $headers = [], $protocolVersion = '1.0')
{
$request = "$method $uri HTTP/$protocolVersion\r\n";
foreach ($headers as $headerName => $headerValue) {
$request .= "$headerName: $headerValue\r\n";
}
$request .= "\r\n";
return $request;
}
protected function getHttpPostRequestString($uri, $headers = [], $postData = [], $protocolVersion = '1.0')
{
$request = "POST $uri HTTP/$protocolVersion\r\n";
if (is_array($postData)) {
$postData = http_build_query($postData);
}
if (!isset($headers['Content-Length'])) {
$headers['Content-Length'] = strlen($postData);
}
if (!isset($headers['Content-Type'])) {
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
foreach ($headers as $headerName => $headerValue) {
$request .= "$headerName: $headerValue\r\n";
}
$request .= "\r\n$postData";
return $request;
}
protected function getFileUploadRequest($requestType, $fileContent)
{
$message =
$requestType . ' / HTTP/1.0
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 834
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text1"
text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text2"
a?b
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
' . $fileContent[0] . '
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
' . $fileContent[1] . '
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream
' . $fileContent[2] . '
-----------------------------735323031399963166993862150--';
return $message;
}
}
|