PHP Classes
elePHPant
Icontem

File: test/Services/Http/HttpMessageTest.php

Recommend this page to a friend!
  Classes of Artur Graniszewski  >  ZEUS for PHP  >  test/Services/Http/HttpMessageTest.php  >  Download  
File: test/Services/Http/HttpMessageTest.php
Role: Unit test script
Content type: text/plain
Description: Unit test script
Class: ZEUS for PHP
Manage the execution of multiple parallel tasks
Author: By
Last change: Improvements to Socket Server, moved network layer to Kernel and used SocketStream in FIFO IPC
Date: 2 months ago
Size: 30,400 bytes
 

Contents

Class file image Download
<?php

namespace ZeusTest\Services\Http;

use PHPUnit_Framework_TestCase;
use Zend\Http\Header\ContentLength;
use Zend\Http\Header\TransferEncoding;
use Zend\Http\Request;
use Zend\Http\Response;
use Zeus\ServerService\Http\Message\Message;
use Zeus\Kernel\Networking\ConnectionInterface;
use Zeus\ServerService\Shared\Networking\HeartBeatMessageInterface;
use Zeus\ServerService\Shared\Networking\MessageComponentInterface;
use ZeusTest\Helpers\SocketTestConnection;

class HttpMessageTest extends PHPUnit_Framework_TestCase
{
    protected $fileHandle;

    protected function getTmpDir()
    {
        $tmpDir = __DIR__ . '/../../tmp/';

        if (!file_exists($tmpDir)) {
            mkdir($tmpDir);
        }
        return $tmpDir;
    }

    public function setUp()
    {
        parent::setUp();
    }

    public function tearDown()
    {
        if (is_resource($this->fileHandle)) {
            fclose($this->fileHandle);
        }

        $files = glob($this->getTmpDir() . '*');

        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }

        rmdir($this->getTmpDir());
        parent::tearDown();
    }

    public function testIfMessageHasBeenDispatched()
    {
        $testConnection = new SocketTestConnection(null);
        $message = $this->getHttpGetRequestString("/");
        $dispatcherLaunched = false;
        /** @var Message $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function() use (& $dispatcherLaunched) {$dispatcherLaunched = true;}, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertTrue($dispatcherLaunched, "Dispatcher should be called");

        $this->assertEquals(1, $httpAdapter->getNumberOfFinishedRequests());
    }

    public function testIfHttp10ConnectionIsClosedAfterSingleRequest()
    {
        $message = $this->getHttpGetRequestString("/");
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {}, null, $testConnection);
        $httpAdapter->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 SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function($request, Response\Stream $response) {
            $response->getHeaders()->addHeader(new ContentLength(2));
            echo "OK";
        }, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.0 keep-alive connection should be left open after request");
    }

    public function testIfHttp10KeepAliveConnectionIsClosedAfterSingleChunkedRequest()
    {
        $message = $this->getHttpGetRequestString("/", ["Connection" => "keep-alive"]);
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {
            echo "OK";
        }, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertTrue($testConnection->isConnectionClosed(), "HTTP 1.0 keep-alive connection should be closed on chunked response");
    }

    public function testIfHttp11ConnectionIsOpenAfterSingleRequest()
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {}, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be left open after request");
    }

    public function testExceptionHandlingKeepsConnectionOpen()
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {throw new \Exception("TEST EXCEPTION");}, null, $testConnection);
        try {
            $httpAdapter->onMessage($testConnection, $message);
        } catch (\Exception $ex) {

        }
        $this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be left open after request");
    }

    /**
     * @expectedException \Exception
     * @expectedExceptionMessage TEST EXCEPTION
     */
    public function testExceptionHandling()
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {throw new \Exception("TEST EXCEPTION");}, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);
    }

    public function testIfHttp11ConnectionIsClosedAfterTimeout()
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        /** @var HeartBeatMessageInterface|MessageComponentInterface $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function() {}, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertFalse($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be left open after request");

        for($i = 0; $i < 5; $i++) {
            // simulate 5 seconds
            $httpAdapter->onHeartBeat($testConnection);
        }

        $this->assertTrue($testConnection->isConnectionClosed(), "HTTP 1.1 connection should be closed after keep-alive timeout");
    }

    public function responseBodyProvider()
    {
        return [
            ['TEST MESSAGE!',  true],
            ["TEST\nMessage\n!", true],
            [str_pad('TEST STRING', 10000, '-', STR_PAD_RIGHT), true],
            ['Another test message!',  false],
            ["Another multiline\nmessage\n!", false],
        ];
    }

    /**
     * @param string $responseBody
     * @dataProvider responseBodyProvider
     */
    public function testIfResponseBodyIsCorrect($responseBody)
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        /** @var HeartBeatMessageInterface|MessageComponentInterface $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function() use ($responseBody) {
            echo $responseBody;
        }, null, $testConnection);

        $httpAdapter->onMessage($testConnection, $message);

        $rawResponse = Response::fromString($testConnection->getSentData());
        $this->assertEquals($responseBody, $rawResponse->getBody());
    }

    /**
     * @param string $responseBody
     * @dataProvider responseBodyProvider
     */
    public function testIfResponseStreamIsCorrect($responseBody)
    {
        file_put_contents($this->getTmpDir() . 'test.file', $responseBody);
        $this->fileHandle = fopen($this->getTmpDir() . 'test.file', 'r');

        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        /** @var HeartBeatMessageInterface|MessageComponentInterface $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function(Request $request, Response\Stream $response) {
            $response->setStream($this->fileHandle);
        }, null, $testConnection);

        $httpAdapter->onMessage($testConnection, $message);

        $rawResponse = Response::fromString($testConnection->getSentData());
        $this->assertEquals($responseBody, $rawResponse->getBody());
    }

    /**
     * @param string $responseBody
     * @dataProvider responseBodyProvider
     */
    public function testIfChunkedResponseBodyIsCorrect($responseBody)
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost'], "1.1");
        $testConnection = new SocketTestConnection(null);
        /** @var HeartBeatMessageInterface|MessageComponentInterface $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function() use ($responseBody) {
            echo $responseBody;

            $response = new Response();
            $response->getHeaders()->addHeader(new TransferEncoding("chunked"));

            return $response;
        }, null, $testConnection);

        $httpAdapter->onMessage($testConnection, $message);

        $rawResponse = Response::fromString($testConnection->getSentData());
        $this->assertEquals($responseBody, $rawResponse->getBody());
    }

    /**
     * @param string $responseBody
     * @param bool $isChunkedEncoding
     * @dataProvider responseBodyProvider
     */
    public function testIfDeflatedResponseBodyIsCorrect($responseBody, $isChunkedEncoding)
    {
        $message = $this->getHttpGetRequestString("/", ['Host' => 'localhost', 'Accept-Encoding' => 'gzip, deflate'], "1.1");
        $testConnection = new SocketTestConnection(null);
        /** @var HeartBeatMessageInterface|MessageComponentInterface $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function($request, $response) use ($responseBody, $isChunkedEncoding) {
            if (!$isChunkedEncoding) {
                $response->getHeaders()->addHeader(new ContentLength(strlen($responseBody)));
            }
            echo $responseBody;
        }, null, $testConnection);

        $httpAdapter->onOpen($testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $rawResponse = Response::fromString($testConnection->getSentData());

        if (strlen($responseBody) >= 4096 && function_exists('deflate_init')) {
            $this->assertEquals('gzip', $rawResponse->getHeaders()->get('Content-Encoding')->getFieldValue());
            $this->assertFalse($rawResponse->getHeaders()->has('Content-Length'));
        } else {
            $this->assertFalse($rawResponse->getHeaders()->has('Content-Encoding'));
        }
        $this->assertEquals($responseBody, $rawResponse->getBody());

    }

    public function testIfHttp11ConnectionIsClosedWithConnectionHeaderAfterSingleRequest()
    {
        $message = $this->getHttpGetRequestString("/", ["Connection" => "close", 'Host' => '127.0.0.1:80'], "1.1");
        $testConnection = new SocketTestConnection(null);
        $httpAdapter = $this->getHttpMessageParser(function() {}, null, $testConnection);
        $httpAdapter->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 SocketTestConnection(null);
        /** @var Response $response */
        $response = null;
        $requestHandler = function($_request, $_response) use (&$response) {$response = $_response; };
        $httpAdapter = $this->getHttpMessageParser($requestHandler, null, $testConnection);
        $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 SocketTestConnection(null);
        $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 SocketTestConnection(null);
            /** @var Request $request */
            $request = null;

            $errorOccured = false;

            $errorHandler = function($request, $exception) use (& $errorOccured) {
                $errorOccured = $exception;
            };

            $requestHandler = function ($_request) use (&$request) {
                $request = $_request;
            };
            $httpAdapter = $this->getHttpMessageParser($requestHandler, $errorHandler);
            $httpAdapter->onOpen($testConnection);

            $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("/", $request->getUri()->getPath());
            foreach ($postData as $key => $value) {
                $this->assertEquals($value, $request->getPost($key), "Request object should contain valid POST data for key $key");
            }
        }
    }

    public function getQueryData()
    {
        return [
            ["test" => "ok"],
            ["test1" => "ok", "test2" => "test2"],
            ["test1" => "test2", "test3" => "test4", "test4" => ["aaa" => "bbb"], "test5" => 12]
        ];
    }

    /**
     * @dataProvider getQueryData
     */
    public function testIfQueryDataIsCorrectlyInterpreted()
    {
        $queryData = func_get_args();
        $queryString = http_build_query($queryData);
        $message = $this->getHttpGetRequestString("/test?" . $queryString);
        $testConnection = new SocketTestConnection(null);
        for($chunkSize = 1, $messageSize = strlen($message); $chunkSize < $messageSize; $chunkSize++) {
            /** @var Request $request */
            $request = null;
            $errorOccurred = false;

            $errorHandler = function($request, $exception) use (& $errorOccurred) {
                $errorOccurred = $exception;
            };

            $requestHandler = function ($_request) use (&$request) {
                $request = $_request;
            };
            $httpAdapter = $this->getHttpMessageParser($requestHandler, $errorHandler);
            $httpAdapter->onOpen($testConnection);

            $chunks = str_split($message, $chunkSize);
            foreach ($chunks as $index => $chunk) {
                $httpAdapter->onMessage($testConnection, $chunk);

                if ($errorOccurred) {
                    $this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccurred->getMessage());
                }
            }

            $this->assertEquals("/test", $request->getUri()->getPath());
            foreach ($queryData as $key => $value) {
                $this->assertEquals($value, $request->getQuery($key), "Request object should contain valid GET data for key $key");
            }
        }
    }

    public function testIfOptionsHeadAndTraceReturnEmptyBody()
    {
        foreach (["HEAD", "TRACE", "OPTIONS"] as $method) {
            $testString = "$method test string";

            $message = $this->getHttpCustomMethodRequestString($method, "/", []);
            $testConnection = new SocketTestConnection(null);
            /** @var Request $request */
            $request = null;
            $requestHandler = function(Request $_request, Response $_response) use (&$request, &$response, $testString) {
                $request = $_request;
                $_response->getHeaders()->addHeader(new ContentLength(strlen($testString)));
                echo $testString;
            };
            $httpAdapter = $this->getHttpMessageParser($requestHandler, $requestHandler);
            $httpAdapter->onOpen($testConnection);
            $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), $rawResponse->getHeaders()->get('Content-Length')->getFieldValue(), "Incorrect Content-Length header returned by $method response");
        }
    }

    public function testIfMultipleRequestsAreHandledByOneMessageInstance()
    {
        $testString = '';
        $requestHandler = function($_request) use (&$request, &$response, & $testString) {$request = $_request; echo $testString; };
        $testConnection = new SocketTestConnection(null);

        /** @var Request $request */
        $request = null;
        $httpAdapter = $this->getHttpMessageParser($requestHandler);
        $httpAdapter->onOpen($testConnection);

        for($i = 1; $i < 10; $i++) {
            $pad = str_repeat("A", $i);
            $testString = "$pad test string";

            $message = $this->getHttpCustomMethodRequestString('GET', "/", ['host' => 'localhost'] ,'1.1');

            $httpAdapter->onMessage($testConnection, $message);
            $rawResponse = Response::fromString($testConnection->getSentData());

            $this->assertEquals($testString, $rawResponse->getBody(), "Original content should be returned in 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 SocketTestConnection(null);
            /** @var Request $request */
            $request = null;
            $fileList = [];
            $tmpDir = $this->getTmpDir();

            $errorOccurred = false;

            $errorHandler = function($request, $exception) use (& $errorOccurred) {
                $errorOccurred = $exception;
            };
            $requestHandler = function (Request $_request) use (&$request, & $fileList, $tmpDir) {
                $request = $_request;

                foreach ($request->getFiles() as $formName => $fileArray) {
                    foreach ($fileArray as $file) {
                        rename($file['tmp_name'], $tmpDir . $file['name']);
                        $fileList[$formName] = $file['name'];
                    }
                }
            };
            $httpAdapter = $this->getHttpMessageParser($requestHandler, $errorHandler);
            $httpAdapter->onOpen($testConnection);

            $chunks = str_split($message, $chunkSize);
            foreach ($chunks as $index => $chunk) {
                $httpAdapter->onMessage($testConnection, $chunk);

                if ($errorOccurred) {
                    $this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccurred->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 SocketTestConnection(null);
            /** @var Request $request */
            $request = null;
            $fileList = [];
            $tmpDir = $this->getTmpDir();

            $errorOccurred = false;

            $errorHandler = function ($request, $exception) use (& $errorOccurred) {
                $errorOccurred = $exception;
            };


            $requestHandler = function (Request $_request) use (&$request, & $fileList, $tmpDir) {
                $request = $_request;
                return new Response();
            };

            $httpAdapter = $this->getHttpMessageParser($requestHandler, $errorHandler);
            $httpAdapter->onOpen($testConnection);

            $chunks = str_split($message, $chunkSize);
            foreach ($chunks as $index => $chunk) {
                $httpAdapter->onMessage($testConnection, $chunk);

                if ($errorOccurred) {
                    $this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccurred->getMessage());
                }
            }

            $rawResponse = Response::fromString($testConnection->getSentData());
            $httpAdapter->onClose($testConnection);

            $this->assertEquals("Hello_World", $request->getContent(), "HTTP response should have returned 'Hello_World', received: " . $rawResponse->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 SocketTestConnection(null);
            /** @var Request $request */
            $request = null;
            $fileList = [];
            $tmpDir = $this->getTmpDir();

            $errorOccurred = false;

            $errorHandler = function ($request, $exception) use (& $errorOccurred) {
                $errorOccurred = $exception;
            };

            $requestHandler = function (Request $_request) use (&$request, & $fileList, $tmpDir) {
                $request = $_request;
                return new Response();
            };

            $httpAdapter = $this->getHttpMessageParser($requestHandler, $errorHandler, $testConnection);

            $chunks = str_split($message, $chunkSize);
            foreach ($chunks as $index => $chunk) {
                $httpAdapter->onMessage($testConnection, $chunk);

                if ($errorOccurred) {
                    $this->fail("Error handler caught an error when parsing chunk #$index: " . $errorOccurred->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 SocketTestConnection(null);
        /** @var Request $request */
        $request = null;
        $requestHandler = function($_request) use (&$request) {$request = $_request; };
        $httpAdapter = $this->getHttpMessageParser($requestHandler, $requestHandler);
        $httpAdapter->onOpen($testConnection);
        $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());
    }

    /**
     * @return mixed[]
     */
    public function getInvalidMessages()
    {
        return [
            ["\r\n\r\n"],
            ["DUMMY / HTTP/1.0\r\n\r\n"],
            ["GET / HTTP/10.1\r\n\r\n"],
        ];
    }

    /**
     * @dataProvider getInvalidMessages
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessageRegExp /Incorrect headers/
     * @param string $message
     */
    public function testIfMessageWithInvalidHeadersIsHandled($message)
    {
        $dispatcherLaunched = false;
        $testConnection = new SocketTestConnection(null);
        /** @var Message $httpAdapter */
        $httpAdapter = $this->getHttpMessageParser(function() use (& $dispatcherLaunched) {$dispatcherLaunched = true;}, null, $testConnection);
        $httpAdapter->onMessage($testConnection, $message);

        $this->assertTrue($dispatcherLaunched, "Dispatcher should be called");

        $this->assertEquals(1, $httpAdapter->getNumberOfFinishedRequests());
    }

    protected function getBuffer()
    {
        $result = ob_get_clean();
        ob_start();

        return $result;
    }

    /**
     * @param callback $dispatcher
     * @param callback $errorHandler
     * @param ConnectionInterface $connection
     * @return MessageComponentInterface
     */
    protected function getHttpMessageParser($dispatcher, $errorHandler = null, ConnectionInterface $connection = null)
    {
        $dispatcherWrapper = function($request, $response) use ($dispatcher) {
            $dispatcher($request, $response);
        };

        $adapter = new Message($dispatcherWrapper, $errorHandler);
        if ($connection) {
            $adapter->onOpen($connection);
        }

        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;
    }
}