PHP Classes

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