/** * 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 getRemotePort() : int { return $this->socket->getRemotePort(); }
/** * @coroutine * * @param int $code * @param \Icicle\Socket\Socket $socket * * @return \Generator * * @resolve \Icicle\Http\Message|Response */ private function createErrorResponse(int $code, Socket $socket) : \Generator { try { yield from $this->log->log(Log::NOTICE, 'Error reading request from %s:%d (Status Code: %d)', $socket->getRemoteAddress(), $socket->getRemotePort(), $code); $response = $this->handler->onError($code, $socket); if ($response instanceof \Generator) { $response = (yield from $response); } elseif ($response instanceof Awaitable) { $response = (yield $response); } if (!$response instanceof Response) { throw new InvalidResultError(sprintf('A %s object was not returned from %::onError().', Response::class, get_class($this->handler)), $response); } } catch (Throwable $exception) { yield from $this->log->log(Log::ERROR, "Uncaught exception when creating response to an error from %s:%d on %s:%d in file %s on line %d: %s", $socket->getRemoteAddress(), $socket->getRemotePort(), $socket->getLocalAddress(), $socket->getLocalPort(), $exception->getFile(), $exception->getLine(), $exception->getMessage()); $response = (yield from $this->createDefaultErrorResponse(500)); } return $response; }