/** * {@inheritdoc} */ public function writeRequest(Socket $socket, Request $request, float $timeout = 0) : \Generator { $written = (yield from $socket->write($this->encoder->encodeRequest($request))); $stream = $request->getBody(); if ($stream->isReadable()) { $written += (yield from Stream\pipe($stream, $socket, false, 0, null, $timeout)); } return $written; }
/** * @param \Icicle\Stream\Structures\Buffer $buffer * @param \Icicle\Socket\Socket $socket * @param float|int $timeout * * @return \Generator * * @throws \Icicle\Http\Exception\MessageException * @throws \Icicle\Http\Exception\ParseException */ protected function readHeaders(Buffer $buffer, Socket $socket, float $timeout = 0) : \Generator { $size = 0; $headers = []; do { while (false === ($position = $buffer->search("\r\n"))) { if ($buffer->getLength() >= $this->maxSize) { throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); } $buffer->push(yield from $socket->read(0, null, $timeout)); } $length = $position + 2; $line = $buffer->shift($length); if (2 === $length) { return $headers; } $size += $length; $parts = explode(':', $line, 2); if (2 !== count($parts)) { throw new ParseException('Found header without colon.'); } list($name, $value) = $parts; $name = Message\decode($name); $value = Message\decode(trim($value)); // No check for case as Message class will automatically combine similarly named headers. if (!isset($headers[$name])) { $headers[$name] = [$value]; } else { $headers[$name][] = $value; } } while ($size < $this->maxSize); throw new MessageException(Response::REQUEST_HEADER_TOO_LARGE, sprintf('Message header exceeded maximum size of %d bytes.', $this->maxSize)); }
/** * 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 bool */ public function isCryptoEnabled() : bool { return $this->socket->isCryptoEnabled(); }
/** * @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; }