/** * 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); }
/** * {@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); }); }
/** * {@inheritdoc} */ public function getReadableStream() : Awaitable { return LoopConfig::currentFilesystem()->readStream($this->file); }
/** * 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))); }); }
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()); }
/** * 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; }