Skip to content

Instantly share code, notes, and snippets.

@kentr
Last active February 17, 2016 19:59
Show Gist options
  • Save kentr/df78111c371194742898 to your computer and use it in GitHub Desktop.
Save kentr/df78111c371194742898 to your computer and use it in GitHub Desktop.
Suggested HttpKernel::mapRequest() code to inject full server vars into the Symfony request.
<?php
namespace PHPPM\Bridges;
use PHPPM\AppBootstrapInterface;
use PHPPM\Bootstraps\BootstrapInterface;
use PHPPM\Bridges\BridgeInterface;
use React\Http\Request as ReactRequest;
use React\Http\Response as ReactResponse;
use Stack\Builder;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\StreamedResponse as SymfonyStreamedResponse;
use Symfony\Component\HttpKernel\TerminableInterface;
class HttpKernel implements BridgeInterface
{
/**
* An application implementing the HttpKernelInterface
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $application;
/**
* Bootstrap an application implementing the HttpKernelInterface.
*
* In the process of bootstrapping we decorate our application with any number of
* *middlewares* using StackPHP's Stack\Builder.
*
* The app bootstraping itself is actually proxied off to an object implementing the
* PHPPM\Bridges\BridgeInterface interface which should live within your app itself and
* be able to be autoloaded.
*
* @param string $appBootstrap The name of the class used to bootstrap the application
* @param string|null $appenv The environment your application will use to bootstrap (if any)
* @see http://stackphp.com
*/
public function bootstrap($appBootstrap, $appenv)
{
// include applications autoload
$autoloader = dirname(realpath($_SERVER['SCRIPT_NAME'])) . '/vendor/autoload.php';
if (file_exists($autoloader)) {
require_once $autoloader;
}
$appBootstrap = $this->normalizeAppBootstrap($appBootstrap);
$bootstrap = new $appBootstrap($appenv);
if ($bootstrap instanceof BootstrapInterface) {
$this->application = $bootstrap->getApplication();
if ($bootstrap instanceof StackableBootstrapInterface) {
$stack = new Builder();
$stack = $bootstrap->getStack($stack);
$this->application = $stack->resolve($this->application);
}
}
}
/**
* Handle a request using a HttpKernelInterface implementing application.
*
* @param \React\Http\Request $request
* @param \React\Http\Response $response
*/
public function onRequest(ReactRequest $request, ReactResponse $response)
{
if (null === $this->application) {
return;
}
$content = '';
$headers = $request->getHeaders();
$contentLength = isset($headers['Content-Length']) ? (int) $headers['Content-Length'] : 0;
$request->on('data', function($data)
use ($request, $response, &$content, $contentLength)
{
// read data (may be empty for GET request)
$content .= $data;
// handle request after receive
if (strlen($content) >= $contentLength) {
$syRequest = self::mapRequest($request, $content);
try {
$syResponse = $this->application->handle($syRequest);
} catch (\Exception $exception) {
$response->writeHead(500); // internal server error
$response->end();
return;
}
self::mapResponse($response, $syResponse);
if ($this->application instanceof TerminableInterface) {
$this->application->terminate($syRequest, $syResponse);
}
}
});
}
/**
* Convert React\Http\Request to Symfony\Component\HttpFoundation\Request
*
* @param ReactRequest $reactRequest
* @return SymfonyRequest $syRequest
*/
protected static function mapRequest(ReactRequest $reactRequest, $content) {
$method = strtoupper($reactRequest->getMethod());
$headers = $reactRequest->getHeaders();
$query = $reactRequest->getQuery();
$post = array();
$requestIsPostType = in_array(
$method,
array('POST', 'PUT', 'DELETE', 'PATCH')
);
// Parse body?
if (isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded'))
&& $requestIsPostType
) {
parse_str($content, $post);
}
$cookies = array();
if (isset($headers['Cookie'])) {
$headersCookie = explode(';', $headers['Cookie']);
foreach ($headersCookie as $cookie) {
list($name, $value) = explode('=', trim($cookie));
$cookies[$name] = $value;
}
}
// Add any query string to URI so SymfonyRequest::create() can access it.
$uri = $reactRequest->getPath() .
(empty($query) ? '' : '?' . http_build_query($query));
// SymfonyRequest::create() expects $parameters to contain either
// $_GET or $_POST.
$parameters = $requestIsPostType ? $post : $query;
$syRequest = SymfonyRequest::create(
// $uri, $method, $parameters, $cookies, $files, $server, $content.
$uri,
$method,
$parameters,
$cookies,
array(),
array(),
$content
);
$syRequest->headers->replace($headers);
// Set CGI/1.1 (RFC 3875) server vars.
if (empty($_ENV)) {
// In some cases with cli, $_ENV isn't set, so get with getenv().
// @see http://stackoverflow.com/questions/8798294/getenv-vs-env-in-php/21473853#21473853
// @todo: Make this more efficient to eliminate running per request.
// Static variable?
$_ENV['DOCUMENT_ROOT'] = getenv('DOCUMENT_ROOT');
$_ENV['SCRIPT_NAME'] = getenv('SCRIPT_NAME');
}
$serverVars = array_merge(
$syRequest->server->all(),
array(
'DOCUMENT_ROOT' => $_ENV['DOCUMENT_ROOT'],
'GATEWAY_INTERFACE' => 'CGI/1.1',
'SCRIPT_NAME' => $_ENV['SCRIPT_NAME'],
// SCRIPT_FILENAME contains the name of the php-pm startup script.
// Must override here.
'SCRIPT_FILENAME' => $_ENV['DOCUMENT_ROOT'] . $_ENV['SCRIPT_NAME'],
)
);
$syRequest->server->replace($serverVars);
return $syRequest;
}
/**
* Convert Symfony\Component\HttpFoundation\Response to React\Http\Response
*
* @param ReactResponse $reactResponse
* @param SymfonyResponse $syResponse
*/
protected static function mapResponse(ReactResponse $reactResponse,
SymfonyResponse $syResponse)
{
$headers = $syResponse->headers->all();
$reactResponse->writeHead($syResponse->getStatusCode(), $headers);
// @TODO convert StreamedResponse in an async manner
if ($syResponse instanceof SymfonyStreamedResponse) {
ob_start();
$syResponse->sendContent();
$content = ob_get_contents();
ob_end_clean();
}
else {
$content = $syResponse->getContent();
}
$reactResponse->end($content);
}
/**
* @param $appBootstrap
* @return string
* @throws \RuntimeException
*/
protected function normalizeAppBootstrap($appBootstrap)
{
$appBootstrap = str_replace('\\\\', '\\', $appBootstrap);
if (false === class_exists($appBootstrap)) {
$appBootstrap = '\\' . $appBootstrap;
if (false === class_exists($appBootstrap)) {
throw new \RuntimeException('Could not find bootstrap class ' . $appBootstrap);
}
return $appBootstrap;
}
return $appBootstrap;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment