/** * {@inheritdoc} */ public function send(HttpRequest $request) : Awaitable { if (!$request->hasHeader('User-Agent')) { $request = $request->withHeader('User-Agent', $this->userAgent); } $invoke = \Closure::bind(function (HttpRequest $request) { $next = new NextMiddleware($this->middlewares, function (HttpRequest $request) { return \array_shift($this->responses); }); return new Coroutine($next($request)); }, $this, HttpClient::class); return $invoke($request); }
public function __invoke(HttpRequest $request, HttpDriverContext $context) : \Generator { $path = \str_replace('+', '%20', $request->getRequestTarget()); $routing = new RoutingContext($this->container, $this->compiler, $path, $request->getMethod(), $this->routable->getMiddlewares()); $this->routeRequest($routing); if (!$routing->isMatch()) { return $routing->createNoMatchResponse(); } $matches = []; if ($request->hasHeader('Content-Type')) { $type = $request->getContentType()->getMediaType(); foreach ($routing->getMatches() as $match) { if ($match->handler->canConsume($type)) { $matches[] = $match; } } } else { foreach ($routing->getMatches() as $match) { if ($match->handler->isConsumer()) { continue; } $matches[] = $match; } } if (!$matches) { return $routing->createUnsupportedMediaTypeResponse(); } $accept = $request->getAccept(); $result = $matches[0]; foreach ($accept->getMediaTypes() as $media) { foreach ($matches as $match) { foreach ($match->handler->getProducedMediaTypes() as $type) { if ($media->is($type)) { $result = $match; break 3; } } } } if (empty($result->middlewares)) { return yield from $this->dispatchRequest($request, $context, $result); } $next = new NextMiddleware($result->middlewares, function (HttpRequest $request) use($context, $result) { return yield from $this->dispatchRequest($request, $context, $result); }, $this->logger); return yield from $next($request); }
/** * Assert that an upgrade to WebSockets is possible. * * @param HttpRequest $request * @param HttpResponse $response * @return HttpResponse when upgrading is impossible, NULL if everything's fine. */ protected function assertUpgradePossible(HttpRequest $request, HttpResponse $response) { if ($request->getMethod() !== Http::METHOD_GET) { return $response->withStatus(Http::CODE_METHOD_NOT_ALLOWED)->withHeader('Allow', 'GET'); } if ($request->getProtocolVersion() !== '1.1') { return $response->withStatus(Http::CODE_HTTP_VERSION_NOT_SUPPORTED); } if (!$request->hasHeader('Sec-WebSocket-Key')) { return $response->withStatus(Http::CODE_BAD_REQUEST, 'Missing Sec-Websocket-Key header'); } if ($request->hasHeader('Sec-Websocket-Version') && !in_array('13', $request->getHeader('Sec-Websocket-Version'), true)) { return $response->withStatus(Http::CODE_BAD_REQUEST, 'Web socket version 13 required')->withHeader('Sec-WebSocket-Version', '13'); } }
/** * Assert that the given HTTP request can be upgraded to the WebSocket protocol. */ protected function assertUpgradePossible(HttpRequest $request) { if ($request->getMethod() !== Http::GET) { throw new StatusException(Http::METHOD_NOT_ALLOWED, 'WebSocket upgrade requires an HTTP GET request', ['Allow' => Http::GET, 'Sec-Websocket-Version' => '13']); } if (!$request->hasHeader('Sec-Websocket-Key')) { throw new StatusException(Http::BAD_REQUEST, 'Missing Sec-Websocket-Key HTTP header', ['Sec-Websocket-Version' => '13']); } if (!\in_array('13', $request->getHeaderTokenValues('Sec-Websocket-Version'), true)) { throw new StatusException(Http::BAD_REQUEST, 'Secure websocket version 13 required', ['Sec-Websocket-Version' => '13']); } }
/** * Send the given HTTP request and fetch the HTTP response from the server. * * @param HttpRequest $request The request to be sent (body will be closed after it has been sent). * @return HttpResponse The HTTP response as returned by the target server. */ public function send(HttpRequest $request) : Awaitable { if (!$request->hasHeader('User-Agent')) { $request = $request->withHeader('User-Agent', $this->userAgent); } $next = new NextMiddleware($this->middlewares, function (HttpRequest $request) { $connecting = new \SplObjectStorage(); $uri = $request->getUri(); foreach ($this->connectors as $connector) { if (!$connector->isRequestSupported($request)) { continue; } $context = (yield $connector->getConnectorContext($uri)); if ($context->connected) { foreach ($connecting as $conn) { $connecting[$conn]->dispose(); } return (yield $connector->send($context, $request)); } $connecting[$connector] = $context; } try { $socket = (yield $this->connectSocket($request->getUri())); $meta = $socket->getMetadata(); try { $connector = $this->chooseConnector($request, \trim($meta['crypto']['alpn_protocol'] ?? ''), $meta); } catch (\Throwable $e) { $socket->close(); throw $e; } $context = $connecting[$connector]; $context->socket = $socket; $connecting->detach($connector); return (yield $connector->send($context, $request)); } finally { foreach ($connecting as $conn) { $connecting[$conn]->dispose(); } } }, $this->logger); return new Coroutine($next($request)); }
/** * Dispatch the given HTTP request to the given action. */ protected function processRequest(HttpDriverContext $context, SocketStream $socket, callable $action, bool &$upgraded, HttpRequest $request, bool $close) : \Generator { static $remove = ['Keep-Alive', 'Content-Length', 'Transfer-Encoding']; // No need for TCP_NODELAY as the connection will be closed anyways flushing the last chunk without delay. if ($close) { $socket->setTcpNoDelay(false); } try { if ($request->getProtocolVersion() == '1.1' && !$request->hasHeader('Host')) { throw new StatusException(Http::BAD_REQUEST, 'Missing HTTP Host header'); } foreach ($remove as $name) { $request = $request->withoutHeader($name); } $next = new NextMiddleware($context->getMiddlewares(), function (HttpRequest $request) use($context, $action) { $response = $action($request, $context->withPrependedResponder(function (HttpRequest $request, $result) { return $this->upgradeResult($request, $result); })); if ($response instanceof \Generator) { $response = (yield from $response); } if (!$response instanceof HttpResponse) { $response = $this->upgradeResult($request, $response); } return $context->respond($request, $response); }, $this->logger); $response = (yield from $next($request)); $handler = $response->getAttribute(UpgradeResultHandler::class); if ($handler instanceof UpgradeResultHandler) { $upgraded = true; return yield from $this->upgradeResultConnection($handler, $socket, $request, $response); } return yield from $this->sendResponse($socket, $request, $response, $close); } catch (\Throwable $e) { return yield from $this->sendErrorResponse($socket, $request, $e); } }
/** * Get all known hop IP addresses as provided by an HTTP proxy. * * @param HttpRequest $request * @return array */ public function getAddresses(HttpRequest $request) : array { $addresses = []; foreach ($this->addressHeaders as $name) { if ($request->hasHeader($name)) { foreach ($request->getHeaderTokenValues($name, false) as $ip) { $addresses[] = $ip; } } } return $addresses; }