From 60969d8825071fabf0ff6705a3283155cfff3ee4 Mon Sep 17 00:00:00 2001 From: Johann Zelger Date: Thu, 2 Apr 2015 23:13:48 +0200 Subject: [PATCH] first implementations for proxy module --- bin/webserver | 2 +- etc/webserver.xml | 47 ++-- resources/templates/www/error.phtml | 7 +- .../HttpConnectionHandler.php | 17 +- .../WebServer/Modules/CoreModule.php | 9 + .../WebServer/Modules/DeflateModule.php | 6 +- .../WebServer/Modules/ProxyModule.php | 237 ++++++++++++++++++ 7 files changed, 297 insertions(+), 28 deletions(-) create mode 100644 src/AppserverIo/WebServer/Modules/ProxyModule.php diff --git a/bin/webserver b/bin/webserver index e6b7af7..fe91db1 100755 --- a/bin/webserver +++ b/bin/webserver @@ -25,4 +25,4 @@ BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; [ -z "$PHP_BIN" ] && PHP_BIN="/opt/appserver/bin/php"; # call webserver script -$PHP_BIN -dappserver.php_sapi=webserver -ddate.timezone=Europe/Berlin -dauto_globals_jit=0 $BASE_DIR/../src/server.php $@ +$PHP_BIN -ddisplay_errors=1 -dappserver.php_sapi=webserver -ddate.timezone=Europe/Berlin -dauto_globals_jit=0 $BASE_DIR/../src/server.php $@ diff --git a/etc/webserver.xml b/etc/webserver.xml index 7c6d851..970cb31 100644 --- a/etc/webserver.xml +++ b/etc/webserver.xml @@ -230,22 +230,25 @@ - - + + - - - - - - - - - + + + + + + + + + + - + + + @@ -430,17 +433,17 @@ - - + + - - - - - - - - + + + + + + + + diff --git a/resources/templates/www/error.phtml b/resources/templates/www/error.phtml index 457963e..c356ce5 100644 --- a/resources/templates/www/error.phtml +++ b/resources/templates/www/error.phtml @@ -16,7 +16,12 @@

getStatusCode() ?> getStatusReasonPhrase() ?>

-

+
+ getStatusReasonPhrase() !== $exception->getMessage()): ?> +

getMessage() ?>

+ +


", $exception->__toString()); ?>

+

Please report bugs to
https://github.com/appserver-io/webserver/issues/new diff --git a/src/AppserverIo/WebServer/ConnectionHandlers/HttpConnectionHandler.php b/src/AppserverIo/WebServer/ConnectionHandlers/HttpConnectionHandler.php index 29ea593..94d847e 100644 --- a/src/AppserverIo/WebServer/ConnectionHandlers/HttpConnectionHandler.php +++ b/src/AppserverIo/WebServer/ConnectionHandlers/HttpConnectionHandler.php @@ -38,6 +38,7 @@ use AppserverIo\Http\HttpQueryParser; use AppserverIo\Http\HttpRequestParser; use AppserverIo\Http\HttpResponseStates; +use AppserverIo\Http\HttpProtocol; /** * Class HttpConnectionHandler @@ -301,6 +302,7 @@ public function handle(SocketInterface $connection, WorkerInterface $worker) // init keep alive settings $keepAliveTimeout = (int) $serverConfig->getKeepAliveTimeout(); $keepAliveMax = (int) $serverConfig->getKeepAliveMax(); + // init keep alive connection flag $keepAliveConnection = false; @@ -341,7 +343,7 @@ public function handle(SocketInterface $connection, WorkerInterface $worker) $keepAliveConnection = false; // set first line from connection - $line = $connection->readLine(self::HTTP_CONNECTION_READ_LENGTH); + $line = $connection->readLine(self::HTTP_CONNECTION_READ_LENGTH, $keepAliveTimeout); /** * In the interest of robustness, servers SHOULD ignore any empty @@ -443,6 +445,10 @@ public function handle(SocketInterface $connection, WorkerInterface $worker) } catch (\Exception $e) { // set status code given by exception // if 0 is comming set 500 by default + + echo $e; + echo __METHOD__ . __LINE__ . PHP_EOL; + $response->setStatusCode($e->getCode() ? $e->getCode() : 500); $this->renderErrorPage($e); } @@ -538,6 +544,7 @@ public function prepareResponse() { // get local var refs $response = $this->getParser()->getResponse(); + // prepare headers in response object to be ready for delivery $response->prepareHeaders(); } @@ -557,7 +564,11 @@ public function sendResponse() // write response headers $connection->write($response->getHeaderString()); // stream response body to connection - $connection->copyStream($response->getBodyStream()); + + $contentLength = $response->getHeader(HttpProtocol::HEADER_CONTENT_LENGTH); + + $connection->copyStream($response->getBodyStream(), (int)$contentLength); + } /** @@ -680,7 +691,7 @@ public function shutdown() $worker = $this->getWorker(); $request = $this->getParser()->getRequest(); $response = $this->getParser()->getResponse(); - + // check if connections is still alive if ($connection) { // call current fileahandler module's shutdown hook if exists diff --git a/src/AppserverIo/WebServer/Modules/CoreModule.php b/src/AppserverIo/WebServer/Modules/CoreModule.php index 9f43226..e67f631 100644 --- a/src/AppserverIo/WebServer/Modules/CoreModule.php +++ b/src/AppserverIo/WebServer/Modules/CoreModule.php @@ -168,6 +168,12 @@ public function populateRequestContext(RequestContextInterface $requestContext) $requestContext->setServerVar(ServerVars::PATH_TRANSLATED, $documentRoot . $pathInfo); } + // first check if wildcard file handler was registered + if (isset($handlers['.*'])) { + // set wildcard filehandler which will overload all specific filehandlers at this point + $possibleValidPathExtension = '*'; + } + // check if file handler is defined for that script and expand request context if (isset($handlers['.' . $possibleValidPathExtension])) { // set the file handler to use for modules being able to react on this setting @@ -219,6 +225,9 @@ public function process(RequestInterface $request, ResponseInterface $response, $this->populateRequestContext($requestContext); // check if file handler is not core module anymore + + var_dump($requestContext->getServerVar(ServerVars::SERVER_HANDLER)); + if ($requestContext->getServerVar(ServerVars::SERVER_HANDLER) !== self::MODULE_NAME) { // stop processing return; diff --git a/src/AppserverIo/WebServer/Modules/DeflateModule.php b/src/AppserverIo/WebServer/Modules/DeflateModule.php index 8acc243..843883f 100644 --- a/src/AppserverIo/WebServer/Modules/DeflateModule.php +++ b/src/AppserverIo/WebServer/Modules/DeflateModule.php @@ -117,8 +117,12 @@ public function process(RequestInterface $request, ResponseInterface $response, if (ModuleHooks::RESPONSE_PRE !== $hook) { return; } + // check if content type header exists if not stop processing + if (!$response->hasHeader(Protocol::HEADER_CONTENT_TYPE)) { + return; + } // check if no accept encoding headers are sent - if (! $request->hasHeader(Protocol::HEADER_ACCEPT_ENCODING)) { + if (!$request->hasHeader(Protocol::HEADER_ACCEPT_ENCODING)) { return; } // check if response was encoded before and exit than diff --git a/src/AppserverIo/WebServer/Modules/ProxyModule.php b/src/AppserverIo/WebServer/Modules/ProxyModule.php new file mode 100644 index 0000000..c794e2a --- /dev/null +++ b/src/AppserverIo/WebServer/Modules/ProxyModule.php @@ -0,0 +1,237 @@ + + * @copyright 2015 TechDivision GmbH + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link https://github.com/appserver-io/webserver + * @link http://www.appserver.io/ + */ + +namespace AppserverIo\WebServer\Modules; + +use AppserverIo\Psr\HttpMessage\Protocol; +use AppserverIo\Psr\HttpMessage\RequestInterface; +use AppserverIo\Psr\HttpMessage\ResponseInterface; +use AppserverIo\WebServer\Interfaces\HttpModuleInterface; +use AppserverIo\Http\HttpResponseStates; +use AppserverIo\Server\Dictionaries\ModuleHooks; +use AppserverIo\Server\Dictionaries\ServerVars; +use AppserverIo\Server\Exceptions\ModuleException; +use AppserverIo\Server\Interfaces\RequestContextInterface; +use AppserverIo\Server\Interfaces\ServerContextInterface; +use AppserverIo\Server\Sockets\StreamSocket; +use AppserverIo\Http\HttpRequest; +use AppserverIo\Http\HttpProtocol; +use AppserverIo\Psr\HttpMessage\StreamInterface; + +/** + * Class ProxyModule + * + * @author Johann Zelger + * @copyright 2015 TechDivision GmbH + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link https://github.com/appserver-io/webserver + * @link http://www.appserver.io/ + */ +class ProxyModule implements HttpModuleInterface +{ + /** + * Defines the module's name + * + * @var string + */ + const MODULE_NAME = 'proxy'; + + /** + * Holds connection to backend + * + * @var StreamSocket + */ + protected $connection; + + /** + * Implements module logic for given hook + * + * @param \AppserverIo\Psr\HttpMessage\RequestInterface $request A request object + * @param \AppserverIo\Psr\HttpMessage\ResponseInterface $response A response object + * @param \AppserverIo\Server\Interfaces\RequestContextInterface $requestContext A requests context instance + * @param int $hook The current hook to process logic for + * + * @return bool + * @throws \AppserverIo\Server\Exceptions\ModuleException + */ + public function process(RequestInterface $request, ResponseInterface $response, RequestContextInterface $requestContext, $hook) + { + // if wrong hook is coming do nothing + if (ModuleHooks::REQUEST_POST !== $hook) { + return; + } + + + try { + + // check if connection object was initialised but connection resource is not ready + if ($this->connection) { + + var_dump($this->connection->getConnectionResource()); + var_dump(stream_get_meta_data($this->connection->getConnectionResource())); + + if (!is_resource($this->connection->getConnectionResource())) { + // unset connection instance to for a new one to be created + unset($this->connection); + } + } + + // check if no connection instance is there + if (!$this->connection) { + // create and connect to defined backend + echo '#### CONNECT TO BACKEND #### in Thread ' . \Thread::getCurrentThreadId() . PHP_EOL; + $this->connection = StreamSocket::getClientInstance('tcp://127.0.0.1:80'); + $response->setBodyStream($this->connection->getConnectionResource()); + } + + $connection = $this->connection; + + $rawRequestString = sprintf('%s %s %s' . "\r\n", $request->getMethod(), $request->getUri(), HttpProtocol::VERSION_1_1 ); + $headers = $request->getHeaders(); + + foreach ($headers as $headerName => $headerValue) { + $rawRequestString .= $headerName . HttpProtocol::HEADER_SEPARATOR . $headerValue . "\r\n"; + } + + $rawRequestString .= "\r\n"; + + var_dump($connection->write($rawRequestString)); + + var_dump(socket_get_status($connection->getConnectionResource())); + + usleep(1000); + + $statusLine = $connection->readLine(1024, 5); + + list($httpVersion, $responseStatusCode) = explode(' ', $statusLine); + + $response->setStatusCode($responseStatusCode); + + $line = ''; + $messageHeaders = ''; + + while (!in_array($line, array("\r\n", "\n"))) { + // read next line + $line = $connection->readLine(); + // enhance headers + $messageHeaders .= $line; + } + + // remove ending CRLF's before parsing + $messageHeaders = trim($messageHeaders); + // check if headers are empty + if (strlen($messageHeaders) === 0) { + throw new HttpException('Missing headers'); + } + + // delimit headers by CRLF + $headerLines = explode("\r\n", $messageHeaders); + + + + // iterate all headers + foreach ($headerLines as $headerLine) { + + // extract header info + $extractedHeaderInfo = explode(HttpProtocol::HEADER_SEPARATOR, trim($headerLine)); + + if ((!$extractedHeaderInfo) || ($extractedHeaderInfo[0] === $headerLine)) { + throw new HttpException('Wrong header format'); + } + + // split name and value + list($headerName, $headerValue) = $extractedHeaderInfo; + + // add header + $response->addHeader(trim($headerName), trim($headerValue)); + + } + + // check if connection should be closed + if ($response->getHeader(HttpProtocol::HEADER_CONNECTION) === HttpProtocol::HEADER_CONNECTION_VALUE_CLOSE) { + $connection->close(); + unset($connection); + + echo "CLOSE CONNECTION########################" . PHP_EOL; + } + + } catch(\Exception $e) { + + echo '#### EXCEPTION #### in Thread ' . \Thread::getCurrentThreadId() . PHP_EOL; + + echo $e; + // reset connection + unset($this->connection); + } + + $response->setState(HttpResponseStates::DISPATCH); + } + + /** + * Return an array of module names which should be executed first + * + * @return array The array of module names + */ + public function getDependencies() + { + return []; + } + + /** + * Returns the module name + * + * @return string The module name + */ + public function getModuleName() + { + return self::MODULE_NAME; + } + + /** + * Initiates the module + * + * @param \AppserverIo\Server\Interfaces\ServerContextInterface $serverContext The server's context instance + * + * @return bool + * @throws \AppserverIo\Server\Exceptions\ModuleException + */ + public function init(ServerContextInterface $serverContext) + { + echo __METHOD__ . PHP_EOL; + $this->serverContext = $serverContext; + } + + /** + * Prepares the module for upcoming request in specific context + * + * @return bool + * @throws \AppserverIo\Server\Exceptions\ModuleException + */ + public function prepare() + { + // nothing to prepare for this module + } + + public function __destruct() + { + echo __METHOD__ . PHP_EOL; + } + +} \ No newline at end of file