/** * {@inheritdoc} */ public function readBody(HttpRequest $request, \ReflectionClass $type) : \Generator { $input = (yield $request->getBody()->getContents()); $xml = new \DOMDocument(); $xml->formatOutput = false; \libxml_clear_errors(); $errorHandling = \libxml_use_internal_errors(true); $entities = \libxml_disable_entity_loader(true); try { $success = @$xml->loadXML($input, \LIBXML_NONET | \LIBXML_NOENT); $errors = \libxml_get_errors(); } catch (\Throwable $e) { if (!empty($errors) && $this->logger) { $this->logErrors($errors); } throw new StatusException(Http::BAD_REQUEST, 'Invalid XML input', [], $e); } finally { \libxml_use_internal_errors($errorHandling); \libxml_disable_entity_loader($entities); } if (!empty($errors) || empty($success) || $xml === NULL || !$xml instanceof \DOMDocument) { if (!empty($errors) && $this->logger) { $this->logErrors($errors); } throw new StatusException(Http::BAD_REQUEST, 'Invalid XML input'); } return $xml; }
/** * {@inheritdoc} */ public function readBody(HttpRequest $request, \ReflectionClass $type) : \Generator { $data = @\json_decode((yield $request->getBody()->getContents()), true); if ($data === null && \json_last_error() !== \JSON_ERROR_NONE) { throw new StatusException(Http::BAD_REQUEST, \sprintf('Malformed JSON data received: "%s"', \json_last_error_msg())); } return new JsonBody($data); }
/** * Automatically follow HTTP redirects according to HTTP status code and location header. * * Uncached HTTP request bodies will be cached prior to being sent to the remote endpoint. * * @param HttpRequest $request * @param NextMiddleware $next * @return HttpResponse * * @throws TooManyRedirectsException When the maximum number of redirects for a single HTTP request has been exceeded. */ public function __invoke(HttpRequest $request, NextMiddleware $next) : \Generator { $body = $request->getBody(); if (!$body->isCached()) { $request = $request->withBody(new BufferedBody((yield $body->getReadableStream()))); } for ($i = -1; $i < $this->maxRedirects; $i++) { $response = (yield from $next($request)); switch ($response->getStatusCode()) { case Http::MOVED_PERMANENTLY: case Http::FOUND: case Http::SEE_OTHER: $request = $request->withMethod(Http::GET); $request = $request->withoutHeader('Content-Type'); $request = $request->withBody(new StringBody()); break; case Http::TEMPORARY_REDIRECT: case Http::PERMANENT_REDIRECT: // Replay request to a different URL. break; default: return $response; } try { $uri = Uri::parse($response->getHeaderLine('Location')); $target = $uri->getPath(); if ('' !== ($query = $uri->getQuery())) { $target .= '?' . $query; } $request = $request->withUri($uri); $request = $request->withRequestTarget($target); } finally { (yield $response->getBody()->discard()); } } throw new TooManyRedirectsException(\sprintf('Limit of %s HTTP redirects exceeded', $this->maxRedirects)); }
/** * Coroutine that sends the given HTTP response to the connected client. */ protected function sendResponse(SocketStream $socket, HttpRequest $request, HttpResponse $response, bool $close) : \Generator { // Discard request body in another coroutine. $request->getBody()->discard(); $response = $this->normalizeResponse($request, $response); $http11 = $response->getProtocolVersion() == '1.1'; $head = $request->getMethod() === Http::HEAD; $nobody = $head || Http::isResponseWithoutBody($response->getStatusCode()); $sendfile = false; $body = $response->getBody(); if ($body instanceof DeferredBody) { return yield from $this->sendDeferredResponse($socket, $request, $response, $nobody, $close); } $size = (yield $body->getSize()); if (!$nobody) { if ($body instanceof FileBody && $socket->isSendfileSupported()) { $sendfile = true; } else { $bodyStream = (yield $body->getReadableStream()); if ($nobody || $size === 0) { $chunk = null; $size = 0; $len = 0; } else { $clen = $size === null ? 4089 : 4096; $chunk = (yield $bodyStream->readBuffer($clen)); $len = \strlen($chunk ?? ''); } if ($chunk === null) { $size = 0; } elseif ($len < $clen) { $size = $len; } } } (yield $socket->write($this->serializeHeaders($response, $close, $size, $nobody) . "\r\n")); (yield $socket->flush()); $sent = 0; try { if (!$nobody) { if ($sendfile) { if ($size) { $sent += (yield LoopConfig::currentFilesystem()->sendfile($body->getFile(), $socket->getSocket(), $size)); } } elseif ($http11 && $size === null) { $sent += (yield $socket->write(\dechex($len) . "\r\n" . $chunk . "\r\n")); if ($len === $clen) { $sent += (yield new CopyBytes($bodyStream, $socket, false, null, 4089, function (string $chunk) { return \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; })); } $sent += (yield $socket->write("0\r\n\r\n")); } elseif ($chunk !== null) { $sent += (yield $socket->write($chunk)); if ($len === $clen) { $sent += (yield new CopyBytes($bodyStream, $socket, false, $size === null ? null : $size - $len)); } } (yield $socket->flush()); } } finally { if (isset($bodyStream)) { $bodyStream->close(); } } if ($this->logger) { $this->logger->info('{ip} "{method} {target} HTTP/{protocol}" {status} {size}', ['ip' => $request->getClientAddress(), 'method' => $request->getMethod(), 'target' => $request->getRequestTarget(), 'protocol' => $request->getProtocolVersion(), 'status' => $response->getStatusCode(), 'size' => $sent ?: '-']); } return !$close; }