From 786ae848e32b9cdcf0041fecc4984502925ca780 Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 27 May 2016 13:13:18 +0200 Subject: [PATCH 1/2] Asynchronously handle streamed responses --- Bridges/HttpKernel.php | 44 +++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Bridges/HttpKernel.php b/Bridges/HttpKernel.php index 47d5994..3242528 100644 --- a/Bridges/HttpKernel.php +++ b/Bridges/HttpKernel.php @@ -78,17 +78,20 @@ public function onRequest(ReactRequest $request, HttpResponse $response) $syRequest = $this->mapRequest($request); - //start buffering the output, so cgi is not sending any http headers - //this is necessary because it would break session handling since - //headers_sent() returns true if any unbuffered output reaches cgi stdout. - ob_start(); - try { + // start buffering the output, so cgi is not sending any http headers + // this is necessary because it would break session handling since + // headers_sent() returns true if any unbuffered output reaches cgi stdout. + ob_start(); + if ($this->bootstrap instanceof HooksInterface) { $this->bootstrap->preHandle($this->application); } $syResponse = $this->application->handle($syRequest); + + // should not receive output from application->handle() + ob_end_clean(); } catch (\Exception $exception) { $response->writeHead(500); // internal server error $response->end(); @@ -170,15 +173,10 @@ protected function mapRequest(ReactRequest $reactRequest) */ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syResponse) { - //end active session + // end active session if (PHP_SESSION_ACTIVE === session_status()) { session_write_close(); - session_unset(); //reset $_SESSION - } - - $content = $syResponse->getContent(); - if ($syResponse instanceof SymfonyStreamedResponse) { - $syResponse->sendContent(); + session_unset(); // reset $_SESSION } $nativeHeaders = []; @@ -200,8 +198,8 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR } } - //after reading all headers we need to reset it, so next request - //operates on a clean header. + // after reading all headers we need to reset it, so next request + // operates on a clean header. header_remove(); $headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCase()); @@ -240,12 +238,22 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR $reactResponse->writeHead($syResponse->getStatusCode(), $headers); - $stdOut = ''; - while ($buffer = @ob_get_clean()) { - $stdOut .= $buffer; + // asynchronously get content + ob_start(function($buffer) use ($reactResponse) { + $reactResponse->write($buffer); + return ''; + }, 4096); + + if ($syResponse instanceof SymfonyStreamedResponse) { + $syResponse->sendContent(); + } + else { + echo($syResponse->getContent()); } - $reactResponse->end($stdOut . $content); + // flush remaining content + @ob_end_flush(); + $reactResponse->end(); } /** From 6f80ba037fa484e49f3640559b90a308282422f6 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 7 Jun 2016 13:56:28 +0200 Subject: [PATCH 2/2] Cleanup and add Content-Length --- Bridges/HttpKernel.php | 52 ++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Bridges/HttpKernel.php b/Bridges/HttpKernel.php index 3242528..dd2d542 100644 --- a/Bridges/HttpKernel.php +++ b/Bridges/HttpKernel.php @@ -78,26 +78,29 @@ public function onRequest(ReactRequest $request, HttpResponse $response) $syRequest = $this->mapRequest($request); - try { - // start buffering the output, so cgi is not sending any http headers - // this is necessary because it would break session handling since - // headers_sent() returns true if any unbuffered output reaches cgi stdout. - ob_start(); + // start buffering the output, so cgi is not sending any http headers + // this is necessary because it would break session handling since + // headers_sent() returns true if any unbuffered output reaches cgi stdout. + ob_start(); + try { if ($this->bootstrap instanceof HooksInterface) { $this->bootstrap->preHandle($this->application); } $syResponse = $this->application->handle($syRequest); - - // should not receive output from application->handle() - ob_end_clean(); } catch (\Exception $exception) { $response->writeHead(500); // internal server error $response->end(); + + // end buffering if we need to throw + @ob_end_clean(); throw $exception; } + // should not receive output from application->handle() + @ob_end_clean(); + $this->mapResponse($response, $syResponse); if ($this->application instanceof TerminableInterface) { @@ -236,24 +239,33 @@ protected function mapResponse(HttpResponse $reactResponse, SymfonyResponse $syR $headers['Set-Cookie'] = $cookies; } - $reactResponse->writeHead($syResponse->getStatusCode(), $headers); + if ($syResponse instanceof SymfonyStreamedResponse) { + $reactResponse->writeHead($syResponse->getStatusCode(), $headers); - // asynchronously get content - ob_start(function($buffer) use ($reactResponse) { - $reactResponse->write($buffer); - return ''; - }, 4096); + // asynchronously get content + ob_start(function($buffer) use ($reactResponse) { + $reactResponse->write($buffer); + return ''; + }, 4096); - if ($syResponse instanceof SymfonyStreamedResponse) { $syResponse->sendContent(); + + // flush remaining content + @ob_end_flush(); + $reactResponse->end(); } else { - echo($syResponse->getContent()); - } + ob_start(); + $content = $syResponse->getContent(); + @ob_end_flush(); - // flush remaining content - @ob_end_flush(); - $reactResponse->end(); + if (!isset($headers['Content-Length'])) { + $headers['Content-Length'] = strlen($content); + } + + $reactResponse->writeHead($syResponse->getStatusCode(), $headers); + $reactResponse->end($content); + } } /**