Skip to content

Commit

Permalink
Merge pull request #11 from PHPFastCGI/fastcgi-request
Browse files Browse the repository at this point in the history
FastCGI request object
  • Loading branch information
AndrewCarterUK committed Aug 26, 2015
2 parents f6a6494 + 18cb2cc commit 071920b
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 226 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ A FastCGI daemon written in PHP.

Using this daemon, applications can stay alive between HTTP requests whilst operating behind the protection of a FastCGI enabled web server.

The daemon requires a handler to be defined that accepts PSR-7 requests and returns PSR-7 responses.
The daemon requires a handler to be defined that accepts request objects and returns PSR-7 or HttpFoundation responses.

The [Speedfony Bundle](https://github.com/PHPFastCGI/SpeedfonyBundle) integrates this daemon with the symfony2 framework.
The [Slimmer package](https://github.com/PHPFastCGI/Slimmer) integrates this daemon with the Slim v3 framework.
Expand All @@ -27,11 +27,13 @@ Below is an example of a simple 'Hello, World!' FastCGI application in PHP.
require_once dirname(__FILE__) . '/../vendor/autoload.php';

use PHPFastCGI\FastCGIDaemon\ApplicationFactory;
use Psr\Http\Message\ServerRequestInterface;
use PHPFastCGI\FastCGIDaemon\Http\RequestInterface;
use Zend\Diactoros\Response\HtmlResponse;

// A simple kernel. This is the core of your application
$kernel = function (ServerRequestInterface $request) {
$kernel = function (RequestInterface $request) {
// $request->getServerRequest() returns PSR-7 server request object
// $request->getHttpFoundationRequest() returns HTTP foundation request object
return new HtmlResponse('<h1>Hello, World!</h1>');
};

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"psr/http-message": "~1.0",
"psr/log": "~1.0",
"zendframework/zend-diactoros": "~1.0",
"symfony/http-foundation": "~2.7",
"symfony/console": "~2.5"
},
"require-dev": {
Expand Down
4 changes: 2 additions & 2 deletions src/CallbackWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace PHPFastCGI\FastCGIDaemon;

use Psr\Http\Message\ServerRequestInterface;
use PHPFastCGI\FastCGIDaemon\Http\RequestInterface;

/**
* Wraps a callback (such as a closure, function or class and method pair) as an
Expand Down Expand Up @@ -34,7 +34,7 @@ public function __construct($handler)
/**
* {@inheritdoc}
*/
public function handleRequest(ServerRequestInterface $request)
public function handleRequest(RequestInterface $request)
{
return call_user_func($this->callback, $request);
}
Expand Down
20 changes: 9 additions & 11 deletions src/Connection/StreamSocketConnectionPool.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,18 @@ protected function acceptConnection(ConnectionHandlerFactoryInterface $connectio
{
$clientSocket = @stream_socket_accept($this->serverSocket);

if (false === $clientSocket) {
return;
}

stream_set_blocking($clientSocket, 0);
if (false !== $clientSocket) {
stream_set_blocking($clientSocket, 0);

$connection = new StreamSocketConnection($clientSocket);
$handler = $connectionHandlerFactory->createConnectionHandler($connection);
$connection = new StreamSocketConnection($clientSocket);
$handler = $connectionHandlerFactory->createConnectionHandler($connection);

$id = spl_object_hash($connection);
$id = spl_object_hash($connection);

$this->clientSockets[$id] = $clientSocket;
$this->connections[$id] = $connection;
$this->connectionHandlers[$id] = $handler;
$this->clientSockets[$id] = $clientSocket;
$this->connections[$id] = $connection;
$this->connectionHandlers[$id] = $handler;
}
}

/**
Expand Down
65 changes: 48 additions & 17 deletions src/ConnectionHandler/ConnectionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
use PHPFastCGI\FastCGIDaemon\DaemonInterface;
use PHPFastCGI\FastCGIDaemon\Exception\DaemonException;
use PHPFastCGI\FastCGIDaemon\Exception\ProtocolException;
use PHPFastCGI\FastCGIDaemon\Http\RequestBuilder;
use PHPFastCGI\FastCGIDaemon\Http\Request;
use PHPFastCGI\FastCGIDaemon\KernelInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse;
use Zend\Diactoros\Stream;

/**
* The default implementation of the ConnectionHandlerInterface.
Expand Down Expand Up @@ -112,7 +114,7 @@ public function close()
$this->bufferLength = 0;

foreach ($this->requests as $request) {
$request['builder']->clean();
fclose($request['stdin']);
}

$this->requests = [];
Expand Down Expand Up @@ -203,6 +205,8 @@ protected function writeResponse($requestId, $headerData, StreamInterface $strea

$this->writeRecord($requestId, DaemonInterface::FCGI_STDOUT, $writeData);
} while ($writeSize === 65535);

$this->writeRecord($requestId, DaemonInterface::FCGI_STDOUT);
}

/**
Expand All @@ -220,9 +224,7 @@ protected function endRequest($requestId, $appStatus = 0, $protocolStatus = Daem

$keepAlive = $this->requests[$requestId]['keepAlive'];

if (isset($this->requests[$requestId]['builder'])) {
$this->requests[$requestId]['builder']->clean();
}
fclose($this->requests[$requestId]['stdin']);

unset($this->requests[$requestId]);

Expand Down Expand Up @@ -286,7 +288,11 @@ protected function processBeginRequestRecord($requestId, $contentData)

$keepAlive = DaemonInterface::FCGI_KEEP_CONNECTION & $content['flags'];

$this->requests[$requestId] = ['keepAlive' => $keepAlive];
$this->requests[$requestId] = [
'keepAlive' => $keepAlive,
'stdin' => fopen('php://temp', 'r+'),
'params' => [],
];

if ($this->shutdown) {
$this->endRequest($requestId, 0, DaemonInterface::FCGI_OVERLOADED);
Expand All @@ -297,8 +303,6 @@ protected function processBeginRequestRecord($requestId, $contentData)
$this->endRequest($requestId, 0, DaemonInterface::FCGI_UNKNOWN_ROLE);
return;
}

$this->requests[$requestId]['builder'] = new RequestBuilder();
}

/**
Expand Down Expand Up @@ -349,7 +353,7 @@ protected function processParamsRecord($requestId, $contentData)

$content = unpack($contentFormat, $contentData);

$this->requests[$requestId]['builder']->addParam($content['name'], $content['value']);
$this->requests[$requestId]['params'][$content['name']] = $content['value'];
}

/**
Expand All @@ -372,7 +376,7 @@ protected function processStdinRecord($requestId, $contentData)
return;
}

$this->requests[$requestId]['builder']->addStdin($contentData);
fwrite($this->requests[$requestId]['stdin'], $contentData);
}

/**
Expand Down Expand Up @@ -400,16 +404,23 @@ protected function processAbortRequestRecord($requestId)
*/
protected function dispatchRequest($requestId)
{
$request = $this->requests[$requestId]['builder']->getRequest();
$request = new Request(
$this->requests[$requestId]['params'],
$this->requests[$requestId]['stdin']
);

try {
$response = $this->kernel->handleRequest($request);

if (!$response instanceof ResponseInterface) {
throw new \LogicException('Kernel must return a PSR-7 HTTP response message');
if ($response instanceof ResponseInterface) {
$this->sendResponse($requestId, $response);
} elseif ($response instanceof HttpFoundationResponse) {
$this->sendHttpFoundationResponse($requestId, $response);
} else {
throw new \LogicException('Kernel must return a PSR-7 or HttpFoundation response message');
}

$this->sendResponse($requestId, $response);
$this->endRequest($requestId);
} catch (\Exception $exception) {
$this->logger->error($exception->getMessage());

Expand All @@ -425,7 +436,10 @@ protected function dispatchRequest($requestId)
*/
protected function sendResponse($requestId, ResponseInterface $response)
{
$headerData = "Status: {$response->getStatusCode()} {$response->getReasonPhrase()}\r\n";
$statusCode = $response->getStatusCode();
$reasonPhrase = $response->getReasonPhrase();

$headerData = "Status: {$statusCode} {$reasonPhrase}\r\n";

foreach ($response->getHeaders() as $name => $values) {
$headerData .= $name.': '.implode(', ', $values)."\r\n";
Expand All @@ -434,8 +448,25 @@ protected function sendResponse($requestId, ResponseInterface $response)
$headerData .= "\r\n";

$this->writeResponse($requestId, $headerData, $response->getBody());
}

$this->writeRecord($requestId, DaemonInterface::FCGI_STDOUT);
$this->endRequest($requestId);
/**
* Send a HttpFoundation response to the client.
*
* @param int $requestId The request id to respond to
* @param HttpFoundationResponse $response The HTTP foundation response message
*/
protected function sendHttpFoundationResponse($requestId, HttpFoundationResponse $response)
{
$statusCode = $response->getStatusCode();

$headerData = "Status: {$statusCode}\r\n";
$headerData .= $response->headers . "\r\n";

$stream = new Stream('php://memory', 'r+');
$stream->write($response->getContent());
$stream->rewind();

$this->writeResponse($requestId, $headerData, $stream);
}
}
85 changes: 63 additions & 22 deletions src/Http/RequestBuilder.php → src/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

namespace PHPFastCGI\FastCGIDaemon\Http;

use Symfony\Component\HttpFoundation\Request as HttpFoundationRequest;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\ServerRequestFactory;

/**
* The default implementation of the RequestBuilderInterface using the Zend
* Diactoros PSR-7 implementation.
* The default implementation of the RequestInterface
*/
class RequestBuilder implements RequestBuilderInterface
class Request implements RequestInterface
{
/**
* @var string[]
* @var array
*/
protected $params;

Expand All @@ -23,41 +23,51 @@ class RequestBuilder implements RequestBuilderInterface

/**
* Constructor.
*
* @param array $params The FastCGI server params as an associative array
* @param resource $stdin The FastCGI stdin data as a stream resource
*/
public function __construct()
public function __construct(array $params, $stdin)
{
$this->params = [];
$this->stdin = fopen('php://temp', 'r+');

foreach ($params as $name => $value) {
$this->params[strtoupper($name)] = $value;
}

$this->stdin = $stdin;

rewind($this->stdin);
}

/**
* {@inheritdoc}
*/
public function addParam($name, $value)
public function getParams()
{
$this->params[strtoupper($name)] = $value;
return $this->params;
}

/**
* {@inheritdoc}
*/
public function addStdin($data)
public function getQuery()
{
fwrite($this->stdin, $data);
$query = [];

if (isset($this->params['QUERY_STRING'])) {
parse_str($this->params['QUERY_STRING'], $query);
}

return $query;
}

/**
* {@inheritdoc}
*/
public function getRequest()
public function getPost()
{
rewind($this->stdin);

$query = $post = $cookies = [];

if (isset($this->params['QUERY_STRING'])) {
parse_str($this->params['QUERY_STRING'], $query);
}
$post = [];

if (isset($this->params['REQUEST_METHOD']) && isset($this->params['CONTENT_TYPE'])) {
$requestMethod = $this->params['REQUEST_METHOD'];
Expand All @@ -71,6 +81,16 @@ public function getRequest()
}
}

return $post;
}

/**
* {@inheritdoc}
*/
public function getCookies()
{
$cookies = [];

if (isset($this->params['HTTP_COOKIE'])) {
$cookiePairs = explode(';', $this->params['HTTP_COOKIE']);

Expand All @@ -80,6 +100,26 @@ public function getRequest()
}
}

return $cookies;
}

/**
* {@inheritdoc}
*/
public function getStdin()
{
return $this->stdin;
}

/**
* {@inheritdoc}
*/
public function getServerRequest()
{
$query = $this->getQuery();
$post = $this->getPost();
$cookies = $this->getCookies();

$server = ServerRequestFactory::normalizeServer($this->params);
$headers = ServerRequestFactory::marshalHeaders($server);
$uri = ServerRequestFactory::marshalUriFromServer($server, $headers);
Expand All @@ -96,11 +136,12 @@ public function getRequest()
/**
* {@inheritdoc}
*/
public function clean()
public function getHttpFoundationRequest()
{
fclose($this->stdin);
$query = $this->getQuery();
$post = $this->getPost();
$cookies = $this->getCookies();

$this->params = [];
$this->stdin = null;
return new HttpFoundationRequest($query, $post, [], $cookies, [], $this->params, $this->stdin);
}
}
Loading

0 comments on commit 071920b

Please sign in to comment.