/** * Assert that the given response indicates a succeeded WebSocket handshake. */ protected function assertHandshakeSucceeded(HttpResponse $response, string $nonce, array $protocols) { if ($response->getStatusCode() !== Http::SWITCHING_PROTOCOLS) { throw new \RuntimeException(\sprintf('Unexpected HTTP response code: %s', $response->getStatusCode())); } if (!\in_array('upgrade', $response->getHeaderTokenValues('Connection'), true)) { throw new \RuntimeException(\sprintf('HTTP connection header did not contain upgrade: "%s"', $response->getHeaderLine('Connection'))); } if ('websocket' !== \strtolower($response->getHeaderLine('Upgrade'))) { throw new \RuntimeException(\sprintf('HTTP upgrade header did not specify websocket: "%s"', $response->getHeaderLine('Upgrade'))); } if (\base64_encode(\sha1($nonce . self::GUID, true)) !== $response->getHeaderLine('Sec-WebSocket-Accept')) { throw new \RuntimeException('Failed to verify Sec-WebSocket-Accept HTTP header'); } if ($response->hasHeader('Sec-WebSocket-Protocol')) { if (!\in_array($response->getHeaderLine('Sec-WebSocket-Protocol'), $protocols, true)) { throw new \OutOfRangeException(\sprintf('Unsupported protocol: "%s"', $response->getHeaderLine('Sec-WebSocket-Protocol'))); } } }
/** * 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; }
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; }
/** * 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; }