/** * Handles the connection with the connected client in a proper way the given * protocol type and version expects for example. * * @param \AppserverIo\Psr\Socket\SocketInterface $connection The connection to handle * @param \AppserverIo\Server\Interfaces\WorkerInterface $worker The worker how started this handle * * @return bool Weather it was responsible to handle the firstLine or not. * @throws \Exception */ public function handle(SocketInterface $connection, WorkerInterface $worker) { // register shutdown handler once to avoid strange memory consumption problems $this->registerShutdown(); // add connection ref to self $this->connection = $connection; $this->worker = $worker; $serverConfig = $this->getServerConfig(); // get instances for short calls $requestContext = $this->getRequestContext(); $parser = $this->getParser(); // Get our query parser $queryParser = $parser->getQueryParser(); // Get the request and response $request = $parser->getRequest(); $response = $parser->getResponse(); // init keep alive settings $keepAliveTimeout = (int) $serverConfig->getKeepAliveTimeout(); $keepAliveMax = (int) $serverConfig->getKeepAliveMax(); // init keep alive connection flag $keepAliveConnection = false; // init the request parser $parser->init(); do { // try to handle request if its a http request try { // reset connection info to server vars $requestContext->setServerVar(ServerVars::REMOTE_ADDR, $connection->getAddress()); $requestContext->setServerVar(ServerVars::REMOTE_PORT, $connection->getPort()); // start time measurement for keep-alive timeout $keepaliveStartTime = microtime(true); // time settings $requestContext->setServerVar(ServerVars::REQUEST_TIME, time()); /** * Todo: maybe later on there have to be other time vars too especially for rewrite module. * * REQUEST_TIME_FLOAT * TIME_YEAR * TIME_MON * TIME_DAY * TIME_HOUR * TIME_MIN * TIME_SEC * TIME_WDAY * TIME */ // process modules by hook REQUEST_PRE $this->processModules(ModuleHooks::REQUEST_PRE); // init keep alive connection flag $keepAliveConnection = false; // set first line from connection $line = $connection->readLine(self::HTTP_CONNECTION_READ_LENGTH, $keepAliveTimeout); /** * In the interest of robustness, servers SHOULD ignore any empty * line(s) received where a Request-Line is expected. * In other words, if * the server is reading the protocol stream at the beginning of a * message and receives a CRLF first, it should ignore the CRLF. * * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.1 */ if (in_array($line, array("\r\n", "\n"))) { // ignore the first CRLF and go on reading the expected start-line. $line = $connection->readLine(self::HTTP_CONNECTION_READ_LENGTH); } // parse read line $parser->parseStartLine($line); /** * Parse headers in a proper way * * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 */ $messageHeaders = ''; while (!in_array($line, array("\r\n", "\n"))) { // read next line $line = $connection->readLine(); // enhance headers $messageHeaders .= $line; } // parse headers $parser->parseHeaders($messageHeaders); // process connection type keep-alive if (strcasecmp($request->getHeader(Protocol::HEADER_CONNECTION), Protocol::HEADER_CONNECTION_VALUE_KEEPALIVE) === 0) { // calculate keep-alive idle time for comparison with keep-alive timeout $keepAliveIdleTime = microtime(true) - $keepaliveStartTime; // only if max connections or keep-alive timeout not reached yet if ($keepAliveMax > 0 && $keepAliveIdleTime < $keepAliveTimeout) { // enable keep alive connection $keepAliveConnection = true; // set keep-alive headers $response->addHeader(Protocol::HEADER_CONNECTION, Protocol::HEADER_CONNECTION_VALUE_KEEPALIVE); $response->addHeader(Protocol::HEADER_KEEP_ALIVE, "timeout: {$keepAliveTimeout}, max: {$keepAliveMax}"); // decrease keep-alive max --$keepAliveMax; } } // check if message body will be transmitted if ($request->hasHeader(Protocol::HEADER_CONTENT_LENGTH)) { // get content-length header if (($contentLength = (int) $request->getHeader(Protocol::HEADER_CONTENT_LENGTH)) > 0) { // check if given content length is not greater than post_max_size from php ini if ($this->getPostMaxSize() < $contentLength) { // throw 500 server error throw new \Exception(sprintf("Post max size '%s' exceeded", $this->getPostMaxSize(false)), 500); } // copy connection stream to body stream by given content length $request->copyBodyStream($connection->getConnectionResource(), $contentLength); // get content out for oldschool query parsing todo: refactor query parsing $content = $request->getBodyContent(); // check if request has to be parsed depending on Content-Type header if ($queryParser->isParsingRelevant($request->getHeader(Protocol::HEADER_CONTENT_TYPE))) { // checks if request has multipart formdata or not preg_match('/boundary=(.*)$/', $request->getHeader(Protocol::HEADER_CONTENT_TYPE), $boundaryMatches); // check if boundaryMatches are found // todo: refactor content string var to be able to use bodyStream if (count($boundaryMatches) > 0) { $parser->parseMultipartFormData($content); } else { $queryParser->parseStr($content); } } } } // set parsed query and multipart form params to request $request->setParams($queryParser->getResult()); // init connection & protocol server vars $this->initServerVars(); // process modules by hook REQUEST_POST $this->processModules(ModuleHooks::REQUEST_POST); // if no module dispatched response throw internal server error 500 if (!$response->hasState(HttpResponseStates::DISPATCH)) { throw new \Exception('Response state is not dispatched', 500); } } catch (SocketReadTimeoutException $e) { // break the request processing due to client timeout break; } catch (SocketReadException $e) { // break the request processing due to peer reset break; } catch (SocketServerException $e) { // break the request processing break; } catch (\Exception $e) { // set status code given by exception // if 0 is comming set 500 by default $response->setStatusCode($e->getCode() ? $e->getCode() : 500); $this->renderErrorPage($e); } // process modules by hook RESPONSE_PRE $this->processModules(ModuleHooks::RESPONSE_PRE); // send response to connected client $this->prepareResponse(); // send response to connected client $this->sendResponse(); // process modules by hook RESPONSE_POST $this->processModules(ModuleHooks::RESPONSE_POST); // check if keep alive-loop is finished to close connection before log access and init vars // to avoid waiting on non keep alive requests for that if ($keepAliveConnection !== true) { $connection->close(); } // log informations for access log etc... $this->logAccess(); // init context vars afterwards to avoid performance issues $requestContext->initVars(); // init the request parser for next request $parser->init(); } while ($keepAliveConnection === true); // close connection if not closed yet $connection->close(); }