/** * Convert SSE event source into an HTTP response. */ public function __invoke(HttpRequest $request, $source) { if ($source instanceof EventSource) { $response = new HttpResponse(Http::OK, ['Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache']); return $response->withBody(new EventBody($source)); } }
/** * Create a new HTTP text response. * * @param string $contents Text to be transfered as response body. * @param string $type media type of the text payload. * @param string $charset Charset to be used (default to UTF-8).. */ public function __construct(string $contents, string $type = 'text/plain', string $charset = null) { $type = new ContentType($type); if ($type->getMediaType()->isText()) { $type->setParam('charset', $charset ?? 'utf-8'); } parent::__construct(Http::OK, ['Content-Type' => (string) $type]); $this->body = new StringBody($contents); }
/** * Create HTTP file response. * * @param string $file Absolute path of the file to be transfered. * @param string $type Media type of the file (will be guessed from extension if not provided). * @param string $charset Charset to be supplied when media type is text format. */ public function __construct(string $file, string $type = null, string $charset = null) { $type = new ContentType($type ?? Filesystem::guessMimeTypeFromFilename($file)); if ($type->getMediaType()->isText()) { $type->setParam('charset', $charset ?? 'utf-8'); } parent::__construct(Http::OK, ['Content-Type' => (string) $type]); $this->body = new FileBody($file); $this->file = $file; }
/** * Create an XML repsonse from the given payload. * * Supports XML strings, DOM documents / nodes and SimpleXML elements. * * @param mixed $payload */ public function __construct($payload) { if ($payload instanceof \DOMDocument) { $payload = $payload->saveXML(); } elseif ($payload instanceof \DOMNode) { $payload = $payload->ownerDocument->saveXml($payload); } elseif ($payload instanceof \SimpleXMLElement) { $payload = $payload->asXML(); } else { $payload = (string) $payload; } parent::__construct(Http::OK, ['Content-Type' => 'application/xml']); $this->body = new StringBody($payload); }
/** * Create an HTTP redirect response. * * @param string $uri The target URI to redirect the recipient to. * @param int $status HTTP status code to be used for the redirect. * * @throws \InvalidArgumentException When the given HTTP status code is not usable as a redirect code. */ public function __construct($uri, int $status = Http::SEE_OTHER) { switch ($status) { case Http::MOVED_PERMANENTLY: case Http::FOUND: case Http::SEE_OTHER: case Http::TEMPORARY_REDIRECT: case Http::PERMANENT_REDIRECT: // These are valid redirect codes ;) break; default: throw new \InvalidArgumentException(\sprintf('Invalid HTTP redirect status code: "%s"', $status)); } parent::__construct($status, ['Location' => (string) Uri::parse($uri)]); }
/** * Assert that an upgrade to WebSockets is possible. * * @param HttpRequest $request * @param HttpResponse $response * @return HttpResponse when upgrading is impossible, NULL if everything's fine. */ protected function assertUpgradePossible(HttpRequest $request, HttpResponse $response) { if ($request->getMethod() !== Http::METHOD_GET) { return $response->withStatus(Http::CODE_METHOD_NOT_ALLOWED)->withHeader('Allow', 'GET'); } if ($request->getProtocolVersion() !== '1.1') { return $response->withStatus(Http::CODE_HTTP_VERSION_NOT_SUPPORTED); } if (!$request->hasHeader('Sec-WebSocket-Key')) { return $response->withStatus(Http::CODE_BAD_REQUEST, 'Missing Sec-Websocket-Key header'); } if ($request->hasHeader('Sec-Websocket-Version') && !in_array('13', $request->getHeader('Sec-Websocket-Version'), true)) { return $response->withStatus(Http::CODE_BAD_REQUEST, 'Web socket version 13 required')->withHeader('Sec-WebSocket-Version', '13'); } }
/** * {@inheritdoc} */ public function upgradeConnection(SocketStream $socket, HttpRequest $request, HttpResponse $response) : \Generator { $endpoint = $response->getAttribute(Endpoint::class); if (!$endpoint instanceof Endpoint) { throw new \InvalidArgumentException('No endpoint object passed to WebSocket handler'); } if ($this->logger) { $this->logger->debug('HTTP/{protocol} connection from {peer} upgraded to WebSocket', ['protocol' => $request->getProtocolVersion(), 'peer' => $socket->getRemoteAddress()]); } $conn = new Connection($socket, false, $response->getHeaderLine('Sec-WebSocket-Protocol')); if ($this->logger) { $conn->setLogger($this->logger); } if ($deflate = $response->getAttribute(PerMessageDeflate::class)) { $conn->enablePerMessageDeflate($deflate); } yield from $this->delegateToEndpoint($conn, $endpoint); }
/** * Create a new JSON-encoded HTTP response. * * @param mixed $payload Payload to be transfered. * @param bool $encode Encode payload as JSON (turn this off for payloads that are already encoded as JSON)? * @param int $options Options to be passed to JSON encoder. */ public function __construct($payload, bool $encode = true, int $options = null) { static $defaultOptions = \JSON_UNESCAPED_SLASHES | \JSON_HEX_AMP | \JSON_HEX_APOS | \JSON_HEX_QUOT | \JSON_HEX_TAG; parent::__construct(Http::OK, ['Content-Type' => 'application/json;charset="utf-8"']); $this->body = new StringBody($encode ? \json_encode($payload, $options ?? $defaultOptions) : $payload); }
/** * Serialize HTTP headers to be sent in an FCGI record. * * @param HttpResponse $response * @param int $size * @return string */ protected function serializeHeaders(HttpResponse $response, int $size = null) : string { $reason = \trim($response->getReasonPhrase()); if ('' === $reason) { $reason = Http::getReason($response->getStatusCode()); } $buffer = \sprintf("Status: %03u%s\r\n", $response->getStatusCode(), \rtrim(' ' . $reason)); if ($size !== null) { $buffer .= "Content-Length: {$size}\r\n"; } foreach ($response->getHeaders() as $name => $header) { $name = Http::normalizeHeaderName($name); foreach ($header as $value) { $buffer .= $name . ': ' . $value . "\r\n"; } } return $buffer; }
/** * Negotiate permessage-deflate settings with the server using the given handshake HTTP response. * * @param HttpResponse $response * @return PerMessageDeflate Or null when the server did not enable the extension. */ protected function negotiatePerMessageDeflate(HttpResponse $response) { static $zlib; $extension = null; if ($zlib ?? ($zlib = \function_exists('inflate_init'))) { foreach ($response->getHeaderTokens('Sec-WebSocket-Extensions') as $ext) { if (\strtolower($ext->getValue()) === 'permessage-deflate') { $extension = $ext; break; } } } if ($extension === null) { return; } try { return PerMessageDeflate::fromHeaderToken($extension); } catch (\OutOfRangeException $e) { return; } }
protected function shouldConnectionBeClosed(HttpResponse $response) : bool { if (!$this->keepAlive) { return true; } if ($response->getProtocolVersion() === '1.0' && !\in_array('keep-alive', $response->getHeaderTokenValues('Connection'), true)) { return true; } if (!$response->hasHeader('Content-Length') && 'chunked' !== \strtolower($response->getHeaderLine('Transfer-Encoding'))) { return true; } return false; }
/** * Serialize HTTP response headers into a string. */ protected function serializeHeaders(HttpResponse $response, bool &$close, int $size = null, bool $nobody = false, bool $deferred = false) : string { $reason = \trim($response->getReasonPhrase()); if ($reason === '') { $reason = \trim(Http::getReason($response->getStatusCode())); } if (!$response->hasHeader('Connection')) { $response = $response->withHeader('Connection', $close ? 'close' : 'keep-alive'); } if (!$close) { $response = $response->withHeader('Keep-Alive', '30'); } $buffer = \sprintf("HTTP/%s %u%s\r\n", $response->getProtocolVersion(), $response->getStatusCode(), \rtrim(' ' . $reason)); if (!$nobody) { if ($deferred) { $close = true; } else { if ((double) $response->getProtocolVersion() > 1) { if ($size === null) { $buffer .= "Transfer-Encoding: chunked\r\n"; } else { $buffer .= "Content-Length: {$size}\r\n"; } } elseif ($size !== null) { $buffer .= "Content-Length: {$size}\r\n"; } else { $close = true; } } } foreach ($response->getHeaders() as $name => $header) { $name = Http::normalizeHeaderName($name); foreach ($header as $value) { $buffer .= $name . ': ' . $value . "\r\n"; } } return $buffer; }
/** * 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; }