Example #1
0
 /**
  * {@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;
 }
Example #2
0
 /**
  * {@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);
 }
Example #3
0
 /**
  * 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));
 }
Example #4
0
 /**
  * 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;
 }