/** * Services requests for a connected client. * * Manages the server-side state for the remote client and interprets client commands. */ private function handleClient(Socket $socket) : Generator { yield from log()->log(Log::INFO, 'Accepted client from %s:%d on %s:%d', $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort()); // Create a new context object for this client. $context = new ClientContext($socket, $this->encoder, $this->accessLayer); try { // Send a welcome message. $welcome = new Response(200, 'Ready'); yield from $context->writeResponse($welcome); // Command loop while ($socket->isOpen()) { // Parse incoming commands. $command = (yield from $context->readCommand()); // Determine the command name and choose how to handle it. $handler = null; switch ($command->name()) { // Mandatory commands case 'CAPABILITIES': $handler = $this->getHandler(handlers\CapabilitiesHandler::class); break; case 'HEAD': $handler = $this->getHandler(handlers\HeadHandler::class); break; case 'HELP': $handler = $this->getHandler(handlers\HelpHandler::class); break; // We don't advertise MODE support, but some readers need it // We don't advertise MODE support, but some readers need it case 'MODE': $handler = $this->getHandler(handlers\ModeHandler::class); break; case 'STAT': $handler = $this->getHandler(handlers\StatHandler::class); break; case 'QUIT': yield from $context->writeResponse(new Response(205, 'Closing connection')); break 2; // stop command loop // LIST commands // stop command loop // LIST commands case 'LIST': $handler = $this->getHandler(handlers\ListHandler::class); break; // NEWNEWS commands // NEWNEWS commands case 'NEWNEWS': $handler = $this->getHandler(handlers\NewNewsHandler::class); break; // POST commands // POST commands case 'POST': $handler = $this->getHandler(handlers\PostHandler::class); break; // READER commands // READER commands case 'ARTICLE': $handler = $this->getHandler(handlers\ArticleHandler::class); break; case 'BODY': $handler = $this->getHandler(handlers\BodyHandler::class); break; case 'DATE': $handler = $this->getHandler(handlers\DateHandler::class); break; case 'GROUP': $handler = $this->getHandler(handlers\GroupHandler::class); break; case 'LAST': $handler = $this->getHandler(handlers\LastHandler::class); break; case 'LISTGROUP': $handler = $this->getHandler(handlers\ListGroupHandler::class); break; case 'NEWGROUPS': $handler = $this->getHandler(handlers\NewGroupsHandler::class); break; case 'NEXT': $handler = $this->getHandler(handlers\NextHandler::class); break; } // Unknown command if (!$handler) { yield from $context->writeResponse(new Response(500, 'Unknown command')); } else { // Execute the selected command handler. try { yield from $handler->handle($command, $context); } catch (\Throwable $e) { yield from log()->log(Log::ERROR, 'Error handling command: ' . $e); yield from $context->writeResponse(new Response(502, 'Server error')); } } } } catch (UnreadableException $e) { // Client disconnected. } catch (UnwritableException $e) { // Client disconnected. } catch (ClosedException $e) { // Client disconnected. } yield from log()->log(Log::INFO, 'Disconnected client from %s:%d on %s:%d', $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort()); // Close the connection to the client. if ($socket->isOpen()) { $socket->close(); } }
/** * @return int */ public function getLocalPort() : int { return $this->socket->getLocalPort(); }
/** * @param \Icicle\Socket\Socket $socket * @param int $cryptoMethod * @param float|int $timeout * @param bool $allowPersistent * * @return \Generator * * @resolve null */ private function process(Socket $socket, int $cryptoMethod, float $timeout, bool $allowPersistent) : \Generator { $count = 0; assert(yield from $this->log->log(Log::DEBUG, 'Accepted client from %s:%d on %s:%d', $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort())); try { if (0 !== $cryptoMethod) { yield from $socket->enableCrypto($cryptoMethod, $timeout); } do { $request = null; try { /** @var \Icicle\Http\Message\Request $request */ $request = (yield from $this->driver->readRequest($socket, $timeout)); ++$count; /** @var \Icicle\Http\Message\Response $response */ $response = (yield from $this->createResponse($request, $socket)); assert(yield from $this->log->log(Log::DEBUG, 'Responded to request from %s:%d for %s with %d %s', $socket->getRemoteAddress(), $socket->getRemotePort(), $request->getUri(), $response->getStatusCode(), $response->getReasonPhrase())); } catch (TimeoutException $exception) { // Request timeout. if (0 < $count) { assert(yield from $this->log->log(Log::DEBUG, 'Keep-alive timeout from %s:%d on %s:%d', $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort())); return; // Keep-alive timeout expired. } $response = (yield from $this->createErrorResponse(Response::REQUEST_TIMEOUT, $socket)); } catch (MessageException $exception) { // Bad request. $response = (yield from $this->createErrorResponse($exception->getCode(), $socket)); } catch (InvalidValueException $exception) { // Invalid value in message header. $response = (yield from $this->createErrorResponse(Response::BAD_REQUEST, $socket)); } catch (ParseException $exception) { // Parse error in request. $response = (yield from $this->createErrorResponse(Response::BAD_REQUEST, $socket)); } $response = (yield from $this->driver->buildResponse($response, $request, $timeout, $allowPersistent)); try { yield from $this->driver->writeResponse($socket, $response, $request, $timeout); } finally { $response->getBody()->close(); } } while (strtolower($response->getHeader('Connection')) === 'keep-alive'); } catch (Throwable $exception) { yield from $this->log->log(Log::NOTICE, "Error when handling request from %s:%d: %s", $socket->getRemoteAddress(), $socket->getRemotePort(), $exception->getMessage()); } finally { $socket->close(); } assert(yield from $this->log->log(Log::DEBUG, 'Disconnected client from %s:%d on %s:%d', $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort())); }