Example #1
0
 /**
  * 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));
     }
 }
Example #2
0
 /**
  * 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);
 }
Example #3
0
 /**
  * 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;
 }
Example #4
0
 /**
  * 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);
 }
Example #5
0
 /**
  * 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);
 }
Example #8
0
 /**
  * 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);
 }
Example #9
0
 /**
  * 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;
 }
Example #10
0
 /**
  * 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;
     }
 }
Example #11
0
 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;
 }
Example #12
0
 /**
  * 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;
 }
Example #13
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;
 }