Пример #1
0
 /**
  * Parse the next HTTP response from the given stream.
  * 
  * @param ReadableStream $stream
  * @param string $line HTTP response line (mighty already be received while checking for 100 continue).
  * @param bool $dropBody Drop response body (needed when the response is caused by a HEAD request).
  * @return HttpResponse
  * 
  * @throws StreamClosedException When no HTTP response line could be parsed.
  */
 public function parseResponse(ReadableStream $stream, string $line = null, bool $dropBody = false) : \Generator
 {
     if ($line === null) {
         $i = 0;
         do {
             if ($i++ > 3 || null === ($line = (yield $stream->readLine()))) {
                 throw new StreamClosedException('Stream closed before HTTP response line was read');
             }
         } while ($line === '');
     }
     $m = null;
     if (!\preg_match("'^HTTP/(1\\.[01])\\s+([1-5][0-9]{2})(.*)\$'i", \trim($line), $m)) {
         throw new StreamClosedException('Invalid HTTP response line received');
     }
     $response = new HttpResponse();
     $response = $response->withProtocolVersion($m[1]);
     $response = $response->withStatus((int) $m[2], \trim($m[3]));
     $response = (yield from $this->parseHeaders($stream, $response));
     if ($dropBody || Http::isResponseWithoutBody($response->getStatusCode())) {
         $body = new StringBody();
         if (!$dropBody) {
             $response = $response->withHeader('Content-Length', '0');
         }
     } else {
         $body = Body::fromMessage($stream, $response);
     }
     static $remove = ['TE', 'Trailer'];
     foreach ($remove as $name) {
         $response = $response->withoutHeader($name);
     }
     return $response->withBody($body);
 }
Пример #2
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;
 }
Пример #3
0
 /**
  * Send the given HTTP response using FCGI records.
  * 
  * @param HttpRequest $request
  * @param HttpResponse $response
  */
 public function sendResponse(HttpRequest $request, HttpResponse $response) : \Generator
 {
     $response = $this->normalizeResponse($request, $response);
     $body = $response->getBody();
     $size = (yield $body->getSize());
     $head = $request->getMethod() === Http::HEAD;
     $nobody = $head || Http::isResponseWithoutBody($response->getStatusCode());
     $buffer = $this->serializeHeaders($response, $nobody ? null : $size);
     (yield $this->conn->sendRecord(new Record(Record::FCGI_VERSION_1, Record::FCGI_STDOUT, $this->id, $buffer . "\r\n")));
     if ($body instanceof DeferredBody) {
         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' => '-']);
         }
         if ($nobody) {
             $body->close(false);
         } else {
             $task = new Coroutine($this->sendDeferredResponse($request, $body), true);
             $this->pending->attach($task);
             $task->when(function () use($task) {
                 if ($this->pending->contains($task)) {
                     $this->pending->detach($task);
                 }
             });
             (yield $task);
         }
     } else {
         $sent = 0;
         if (!$nobody) {
             $bodyStream = (yield $body->getReadableStream());
             try {
                 $channel = $bodyStream->channel(4096, $size);
                 while (null !== ($chunk = (yield $channel->receive()))) {
                     $sent += (yield $this->conn->sendRecord(new Record(Record::FCGI_VERSION_1, Record::FCGI_STDOUT, $this->id, $chunk)));
                 }
             } finally {
                 $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 ?: '-']);
         }
     }
     (yield $this->conn->sendRecord(new Record(Record::FCGI_VERSION_1, Record::FCGI_STDOUT, $this->id, '')));
     (yield $this->conn->sendRecord(new Record(Record::FCGI_VERSION_1, Record::FCGI_STDERR, $this->id, '')));
     $this->conn->closeHandler($this->id);
 }
Пример #4
0
 /**
  * Check if the given response payload should be compressed.
  * 
  * @param HttpRequest $request
  * @param HttpResponse $response
  * @return bool
  */
 protected function isCompressable(HttpRequest $request, HttpResponse $response) : bool
 {
     if ($request->getMethod() === Http::HEAD) {
         return false;
     }
     if ($response->getBody() instanceof DeferredBody || Http::isResponseWithoutBody($response->getStatusCode())) {
         return false;
     }
     if ($response->hasHeader('Content-Encoding') || !$response->hasHeader('Content-Type')) {
         return false;
     }
     try {
         $media = $response->getContentType()->getMediaType();
     } catch (InvalidMediaTypeException $e) {
         return false;
     }
     if (isset($this->types[(string) $media])) {
         return true;
     }
     foreach ($media->getSubTypes() as $sub) {
         if (isset($this->subTypes[$sub])) {
             return true;
         }
     }
     return false;
 }