Esempio n. 1
0
 /**
  * Check if the HTTP request matches a public file and server it as needed.
  * 
  * @param HttpRequest $request
  * @param NextMiddleware $next
  * @return HttpResponse
  */
 public function __invoke(HttpRequest $request, NextMiddleware $next) : \Generator
 {
     static $methods = [Http::HEAD, Http::GET];
     if (!\in_array($request->getMethod(), $methods, true)) {
         return yield from $next($request);
     }
     $path = '/' . \trim($request->getRequestTarget(), '/');
     if ($this->basePath !== '/') {
         if (0 !== \strpos($path, $this->basePath)) {
             return yield from $next($request);
         }
         $path = \substr($path, \strlen($this->basePath) - 1);
     }
     $file = Filesystem::normalizePath($this->directory . \substr($path, 1));
     if (0 !== \strpos($file, $this->directory)) {
         return yield from $next($request);
     }
     if (!(yield LoopConfig::currentFilesystem()->isFile($file))) {
         return yield from $next($request);
     }
     return $this->createResponse($request, $file);
 }
Esempio n. 2
0
 /**
  * {@inheritdoc}
  */
 public function getReadableStream() : Awaitable
 {
     if ($this->temp) {
         $this->temp->rewind();
         return new Success(new BufferedBodyStream($this->temp, $this->stream, $this->bufferSize, $this));
     }
     if ($this->size !== null) {
         return new Success(new ReadableMemoryStream($this->buffer));
     }
     return new Coroutine(function () {
         $buffer = (yield $this->stream->readBuffer($this->bufferSize));
         $len = \strlen($buffer);
         if ($len < $this->bufferSize) {
             $this->buffer = $buffer;
             $this->size = $len;
             return new ReadableMemoryStream($buffer);
         }
         $this->temp = (yield LoopConfig::currentFilesystem()->tempStream());
         $this->offset += (yield $this->temp->write($buffer));
         return new BufferedBodyStream($this->temp, $this->stream, $this->bufferSize, $this);
     });
 }
Esempio n. 3
0
 /**
  * {@inheritdoc}
  */
 public function getReadableStream() : Awaitable
 {
     return LoopConfig::currentFilesystem()->readStream($this->file);
 }
Esempio n. 4
0
 /**
  * Open the given resource for streaming reads.
  * 
  * @param string $path K1 resource path (k1:// URL scheme is optional).
  * @return ReadableStream
  */
 public function readStream(string $path) : Awaitable
 {
     return new Coroutine(function () use($path) {
         return (yield LoopConfig::currentFilesystem()->readStream($this->locator->locateFile($path)));
     });
 }
Esempio n. 5
0
 protected function sendRequest(SocketStream $socket, HttpRequest $request, int &$sent) : \Generator
 {
     $request = $this->normalizeRequest($request);
     $body = $request->getBody();
     $size = (yield $body->getSize());
     $sendfile = false;
     if ($body instanceof FileBody && $socket->isSendfileSupported()) {
         $sendfile = true;
         $chunk = $size ? '' : null;
     } else {
         if ($request->getProtocolVersion() == '1.0' && $size === null) {
             if (!$body->isCached()) {
                 $body = new BufferedBody((yield $body->getReadableStream()));
             }
             (yield $body->discard());
             $size = (yield $body->getSize());
         }
         $bodyStream = (yield $body->getReadableStream());
         $clen = $size === null ? 4089 : 4096;
         $chunk = (yield $bodyStream->readBuffer($clen));
         $len = \strlen($chunk ?? '');
         if ($chunk === null) {
             $size = 0;
         } elseif ($len < $clen) {
             $size = $len;
         }
     }
     $buffer = $this->serializeHeaders($request, $size);
     $expect = false;
     if ($this->expectContinue && $chunk !== null && $request->getProtocolVersion() == '1.1') {
         $expect = true;
         $buffer .= "Expect: 100-continue\r\n";
     }
     (yield $socket->write($buffer . "\r\n"));
     (yield $socket->flush());
     if ($expect) {
         if (!\preg_match("'^HTTP/1\\.1\\s+100(?:\$|\\s)'i", $line = (yield $socket->readLine()))) {
             try {
                 return $line;
             } finally {
                 if (isset($bodyStream)) {
                     $bodyStream->close();
                 }
             }
         }
     }
     if ($sendfile) {
         if ($size) {
             $sent += (yield LoopConfig::currentFilesystem()->sendfile($body->getFile(), $socket->getSocket(), $size));
         }
     } elseif ($size === null) {
         $sent += (yield $socket->write(\dechex($len) . "\r\n" . $chunk . "\r\n"));
         if ($len === $clen) {
             // Align each chunk with length and line breaks to fit into 4 KB payload.
             $sent += (yield new CopyBytes($bodyStream, $socket, true, null, 4089, function (string $chunk) {
                 return \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n";
             }));
         }
         (yield $socket->write("0\r\n\r\n"));
     } elseif ($size > 0) {
         $sent += (yield $socket->write($chunk));
         if ($len === $clen) {
             $sent += (yield new CopyBytes($bodyStream, $socket, true, $size - $len));
         }
     }
     (yield $socket->flush());
 }
Esempio n. 6
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;
 }