Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Bootstraps/BootstrapInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace PHPPM\Bootstraps;

/**
* All application bootstraps must implement this interface
*/
interface BootstrapInterface
{
public function getApplication();
}
7 changes: 0 additions & 7 deletions Bootstraps/Drupal.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ public function initialize($appenv, $debug)
$this->debug = $debug;
}

/**
* @return string
*/
public function getStaticDirectory() {
return './';
}

/**
* Create a Drupal application.
*/
Expand Down
7 changes: 0 additions & 7 deletions Bootstraps/Laravel.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ public function initialize($appenv, $debug)
putenv("APP_ENV=" . $this->appenv);
}

/**
* {@inheritdoc}
*/
public function getStaticDirectory() {
return 'public/';
}

/**
* {@inheritdoc}
*/
Expand Down
13 changes: 3 additions & 10 deletions Bootstraps/Symfony.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ public function initialize($appenv, $debug)
$this->debug = $debug;
}

/**
* @return string
*/
public function getStaticDirectory()
{
return 'web/';
}

/**
* Create a Symfony application
*
Expand All @@ -58,9 +50,10 @@ public function getApplication()

//since we need to change some services, we need to manually change some services
$app = new \AppKernel($this->appenv, $this->debug);
// We need to change some services, before the boot, because they would
// otherwise be instantiated and passed to other classes which makes it
// impossible to replace them.

//we need to change some services, before the boot, because they would otherwise
//be instantiated and passed to other classes which makes it impossible to replace them.
Utils::bindAndCall(function() use ($app) {
// init bundles
$app->initializeBundles();
Expand Down
114 changes: 51 additions & 63 deletions Bridges/HttpKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
use PHPPM\Bootstraps\BootstrapInterface;
use PHPPM\Bootstraps\HooksInterface;
use PHPPM\Bootstraps\RequestClassProviderInterface;
use PHPPM\React\HttpResponse;
use PHPPM\Utils;
use React\EventLoop\LoopInterface;
use React\Http\Request as ReactRequest;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use RingCentral\Psr7;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
Expand Down Expand Up @@ -43,7 +44,7 @@ class HttpKernel implements BridgeInterface
* @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)
* @param boolean $debug If debug is enabled
* @see http://stackphp.com
* @param LoopInterface $loop Event loop
*/
public function bootstrap($appBootstrap, $appenv, $debug, LoopInterface $loop)
{
Expand All @@ -61,23 +62,11 @@ public function bootstrap($appBootstrap, $appenv, $debug, LoopInterface $loop)
/**
* {@inheritdoc}
*/
public function getStaticDirectory()
{
return $this->bootstrap->getStaticDirectory();
}

/**
* Handle a request using a HttpKernelInterface implementing application.
*
* @param ReactRequest $request
* @param HttpResponse $response
*
* @throws \Exception
*/
public function onRequest(ReactRequest $request, HttpResponse $response)
public function handle(ServerRequestInterface $request)
{
if (null === $this->application) {
return;
// internal server error
return new Psr7\Response(500, ['Content-type' => 'text/plain'], 'Application not configured during bootstrap');
}

$syRequest = $this->mapRequest($request);
Expand All @@ -94,18 +83,18 @@ public function onRequest(ReactRequest $request, HttpResponse $response)

$syResponse = $this->application->handle($syRequest);
} catch (\Exception $exception) {
$response->writeHead(500); // internal server error
$response->end();
// internal server error
$response = new Psr7\Response(500, ['Content-type' => 'text/plain'], $exception->getMessage());

// end buffering if we need to throw
@ob_end_clean();
throw $exception;
return $response;
}

// should not receive output from application->handle()
@ob_end_clean();

$this->mapResponse($response, $syResponse);
$response = $this->mapResponse($syResponse);

if ($this->application instanceof TerminableInterface) {
$this->application->terminate($syRequest, $syResponse);
Expand All @@ -114,6 +103,8 @@ public function onRequest(ReactRequest $request, HttpResponse $response)
if ($this->bootstrap instanceof HooksInterface) {
$this->bootstrap->postHandle($this->application);
}

return $response;
}

/**
Expand All @@ -122,48 +113,51 @@ public function onRequest(ReactRequest $request, HttpResponse $response)
* @param ReactRequest $reactRequest
* @return SymfonyRequest $syRequest
*/
protected function mapRequest(ReactRequest $reactRequest)
protected function mapRequest(ServerRequestInterface $psrRequest)
{
$method = $reactRequest->getMethod();
$headers = $reactRequest->getHeaders();
$query = $reactRequest->getQuery();
$method = $psrRequest->getMethod();
$query = $psrRequest->getQueryParams();

// cookies
$_COOKIE = [];

$sessionCookieSet = false;
$headersCookie = explode(';', $psrRequest->getHeaderLine('Cookie'));

if (isset($headers['Cookie']) || isset($headers['cookie'])) {
$headersCookie = explode(';', isset($headers['Cookie']) ? $headers['Cookie'] : $headers['cookie']);
foreach ($headersCookie as $cookie) {
list($name, $value) = explode('=', trim($cookie));
$_COOKIE[$name] = $value;
foreach ($headersCookie as $cookie) {
list($name, $value) = explode('=', trim($cookie));
$_COOKIE[$name] = $value;

if ($name === session_name()) {
session_id($value);
$sessionCookieSet = true;
}
if ($name === session_name()) {
session_id($value);
$sessionCookieSet = true;
}
}

if (!$sessionCookieSet && session_id()) {
//session id already set from the last round but not got from the cookie header,
//so generate a new one, since php is not doing it automatically with session_start() if session
//has already been started.
// session id already set from the last round but not obtained
// from the cookie header, so generate a new one, since php is
// not doing it automatically with session_start() if session
// has already been started.
session_id(Utils::generateSessionId());
}

$files = $reactRequest->getFiles();
$post = $reactRequest->getPost();
// files
$files = $psrRequest->getUploadedFiles();

// @todo check howto handle additional headers

// @todo check howto support other HTTP methods with bodies
$post = $psrRequest->getParsedBody() ?: array();

if ($this->bootstrap instanceof RequestClassProviderInterface) {
$class = $this->bootstrap->requestClass();
}
else {
$class = '\Symfony\Component\HttpFoundation\Request';
$class = SymfonyRequest::class;
}

/** @var SymfonyRequest $syRequest */
$syRequest = new $class($query, $post, $attributes = [], $_COOKIE, $files, $_SERVER, $reactRequest->getBody());
$syRequest = new $class($query, $post, $attributes = [], $_COOKIE, $files, $_SERVER, $psrRequest->getBody());

$syRequest->setMethod($method);

Expand All @@ -173,10 +167,10 @@ protected function mapRequest(ReactRequest $reactRequest)
/**
* Convert Symfony\Component\HttpFoundation\Response to React\Http\Response
*
* @param HttpResponse $reactResponse
* @param SymfonyResponse $syResponse
« @return ResponseInterface
*/
protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syResponse)
protected function mapResponse(SymfonyResponse $syResponse)
{
// end active session
if (PHP_SESSION_ACTIVE === session_status()) {
Expand Down Expand Up @@ -241,33 +235,27 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR
$headers['Set-Cookie'] = $cookies;
}

if ($syResponse instanceof SymfonyStreamedResponse) {
$reactResponse->writeHead($syResponse->getStatusCode(), $headers);

// asynchronously get content
ob_start(function($buffer) use ($reactResponse) {
$reactResponse->write($buffer);
return '';
}, 4096);
$psrResponse = new Psr7\Response($syResponse->getStatusCode(), $headers);

// get contents
ob_start();
if ($syResponse instanceof SymfonyStreamedResponse) {
$syResponse->sendContent();

// flush remaining content
@ob_end_flush();
$reactResponse->end();
$content = @ob_get_clean();
}
else {
ob_start();
$content = $syResponse->getContent();
@ob_end_flush();
}

if (!isset($headers['Content-Length'])) {
$headers['Content-Length'] = strlen($content);
}

$reactResponse->writeHead($syResponse->getStatusCode(), $headers);
$reactResponse->end($content);
if (!isset($headers['Content-Length'])) {
$psrResponse = $psrResponse->withAddedHeader('Content-Length', strlen($content));
}

$psrResponse = $psrResponse->withBody(Psr7\stream_for($content));

return $psrResponse;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "php-pm/httpkernel-adapter",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php-pm/php-pm": "dev-master",
"symfony/http-foundation": "^2.6|^3.0",
"symfony/http-kernel": "^2.6|^3.0",
"php-pm/php-pm": "dev-master"
"ringcentral/psr7": "^1.2"
},
"autoload": {
"psr-4": {
Expand Down
Loading