コード例 #1
0
ファイル: XmlDocumentReader.php プロジェクト: koolkode/k1
 /**
  * {@inheritdoc}
  */
 public function readBody(HttpRequest $request, \ReflectionClass $type) : \Generator
 {
     $input = (yield $request->getBody()->getContents());
     $xml = new \DOMDocument();
     $xml->formatOutput = false;
     \libxml_clear_errors();
     $errorHandling = \libxml_use_internal_errors(true);
     $entities = \libxml_disable_entity_loader(true);
     try {
         $success = @$xml->loadXML($input, \LIBXML_NONET | \LIBXML_NOENT);
         $errors = \libxml_get_errors();
     } catch (\Throwable $e) {
         if (!empty($errors) && $this->logger) {
             $this->logErrors($errors);
         }
         throw new StatusException(Http::BAD_REQUEST, 'Invalid XML input', [], $e);
     } finally {
         \libxml_use_internal_errors($errorHandling);
         \libxml_disable_entity_loader($entities);
     }
     if (!empty($errors) || empty($success) || $xml === NULL || !$xml instanceof \DOMDocument) {
         if (!empty($errors) && $this->logger) {
             $this->logErrors($errors);
         }
         throw new StatusException(Http::BAD_REQUEST, 'Invalid XML input');
     }
     return $xml;
 }
コード例 #2
0
ファイル: JsonBodyReader.php プロジェクト: koolkode/k1
 /**
  * {@inheritdoc}
  */
 public function readBody(HttpRequest $request, \ReflectionClass $type) : \Generator
 {
     $data = @\json_decode((yield $request->getBody()->getContents()), true);
     if ($data === null && \json_last_error() !== \JSON_ERROR_NONE) {
         throw new StatusException(Http::BAD_REQUEST, \sprintf('Malformed JSON data received: "%s"', \json_last_error_msg()));
     }
     return new JsonBody($data);
 }
コード例 #3
0
ファイル: EventBody.php プロジェクト: koolkode/async-http
 /**
  * {@inheritdoc}
  */
 public function start(HttpRequest $request, LoggerInterface $logger = null)
 {
     $this->logger = $logger;
     if ($this->logger) {
         $this->address = $request->getClientAddress();
         $this->logger->debug('Enabled SSE for {address} using HTTP/{version}', ['address' => $this->address, 'version' => $request->getProtocolVersion()]);
     }
 }
コード例 #4
0
 /**
  * {@inheritdoc}
  */
 public function send(HttpRequest $request) : Awaitable
 {
     if (!$request->hasHeader('User-Agent')) {
         $request = $request->withHeader('User-Agent', $this->userAgent);
     }
     $invoke = \Closure::bind(function (HttpRequest $request) {
         $next = new NextMiddleware($this->middlewares, function (HttpRequest $request) {
             return \array_shift($this->responses);
         });
         return new Coroutine($next($request));
     }, $this, HttpClient::class);
     return $invoke($request);
 }
コード例 #5
0
ファイル: ResourceMiddleware.php プロジェクト: koolkode/k1
 /**
  * {@inheritdoc}
  */
 public function __invoke(HttpRequest $request, NextMiddleware $next) : \Generator
 {
     static $methods = [Http::HEAD => true, Http::GET => true];
     if (empty($methods[$request->getMethod()])) {
         return yield from $next($request);
     }
     $path = '/' . \trim(Uri::decode($request->getRequestTarget()), '/');
     if (0 !== \strpos($path, $this->base)) {
         return yield from $next($request);
     }
     $path = \substr($path, \strlen($this->base));
     try {
         $file = $this->locator->locatePublicFile($path);
     } catch (ResourceNotFoundException $e) {
         return yield from $next($request);
     }
     $ext = false === ($pos = \strpos($file, '.')) ? '' : \strtolower(\substr($file, $pos + 1));
     foreach ($this->processors as $processor) {
         $result = $processor($request, $file, $ext);
         if ($result instanceof \Generator) {
             $result = (yield from $result);
         }
         if ($result instanceof HttpResponse) {
             $response = $result;
             break;
         }
     }
     if (!isset($response)) {
         $response = new FileResponse($file);
     }
     if (!$response->hasHeader('Cache-Control')) {
         $response = $response->withHeader('Cache-Control', \sprintf('public, max-age=%u', $this->ttl));
         if ($request->getProtocolVersion() === '1.0') {
             $response = $response->withHeader('Expires', \gmdate(Http::DATE_RFC1123, \time() + $this->ttl));
         }
     }
     return $response;
 }
コード例 #6
0
 /**
  * Automatically follow HTTP redirects according to HTTP status code and location header.
  * 
  * Uncached HTTP request bodies will be cached prior to being sent to the remote endpoint.
  * 
  * @param HttpRequest $request
  * @param NextMiddleware $next
  * @return HttpResponse
  * 
  * @throws TooManyRedirectsException When the maximum number of redirects for a single HTTP request has been exceeded.
  */
 public function __invoke(HttpRequest $request, NextMiddleware $next) : \Generator
 {
     $body = $request->getBody();
     if (!$body->isCached()) {
         $request = $request->withBody(new BufferedBody((yield $body->getReadableStream())));
     }
     for ($i = -1; $i < $this->maxRedirects; $i++) {
         $response = (yield from $next($request));
         switch ($response->getStatusCode()) {
             case Http::MOVED_PERMANENTLY:
             case Http::FOUND:
             case Http::SEE_OTHER:
                 $request = $request->withMethod(Http::GET);
                 $request = $request->withoutHeader('Content-Type');
                 $request = $request->withBody(new StringBody());
                 break;
             case Http::TEMPORARY_REDIRECT:
             case Http::PERMANENT_REDIRECT:
                 // Replay request to a different URL.
                 break;
             default:
                 return $response;
         }
         try {
             $uri = Uri::parse($response->getHeaderLine('Location'));
             $target = $uri->getPath();
             if ('' !== ($query = $uri->getQuery())) {
                 $target .= '?' . $query;
             }
             $request = $request->withUri($uri);
             $request = $request->withRequestTarget($target);
         } finally {
             (yield $response->getBody()->discard());
         }
     }
     throw new TooManyRedirectsException(\sprintf('Limit of %s HTTP redirects exceeded', $this->maxRedirects));
 }
コード例 #7
0
ファイル: Dispatcher.php プロジェクト: koolkode/k1
 public function __invoke(HttpRequest $request, HttpDriverContext $context) : \Generator
 {
     $path = \str_replace('+', '%20', $request->getRequestTarget());
     $routing = new RoutingContext($this->container, $this->compiler, $path, $request->getMethod(), $this->routable->getMiddlewares());
     $this->routeRequest($routing);
     if (!$routing->isMatch()) {
         return $routing->createNoMatchResponse();
     }
     $matches = [];
     if ($request->hasHeader('Content-Type')) {
         $type = $request->getContentType()->getMediaType();
         foreach ($routing->getMatches() as $match) {
             if ($match->handler->canConsume($type)) {
                 $matches[] = $match;
             }
         }
     } else {
         foreach ($routing->getMatches() as $match) {
             if ($match->handler->isConsumer()) {
                 continue;
             }
             $matches[] = $match;
         }
     }
     if (!$matches) {
         return $routing->createUnsupportedMediaTypeResponse();
     }
     $accept = $request->getAccept();
     $result = $matches[0];
     foreach ($accept->getMediaTypes() as $media) {
         foreach ($matches as $match) {
             foreach ($match->handler->getProducedMediaTypes() as $type) {
                 if ($media->is($type)) {
                     $result = $match;
                     break 3;
                 }
             }
         }
     }
     if (empty($result->middlewares)) {
         return yield from $this->dispatchRequest($request, $context, $result);
     }
     $next = new NextMiddleware($result->middlewares, function (HttpRequest $request) use($context, $result) {
         return yield from $this->dispatchRequest($request, $context, $result);
     }, $this->logger);
     return yield from $next($request);
 }
コード例 #8
0
 /**
  * Send handshake HTTP/1.1 response to the client to enable protocol switching.
  * 
  * @param BufferedDuplexStreamInterface $socket
  * @param HttpRequest $request
  */
 protected function sendHandshake(BufferedDuplexStreamInterface $socket, HttpRequest $request) : \Generator
 {
     $accept = base64_encode(sha1($request->getHeaderLine('Sec-WebSocket-Key') . self::GUID, true));
     $message = sprintf("HTTP/%s 101 Switching Protocols\r\n", $request->getProtocolVersion());
     $message .= "Connection: Upgrade\r\n";
     $message .= "Upgrade: websocket\r\n";
     $message .= sprintf("Sec-WebSocket-Accept: %s\r\n", $accept);
     $message .= "Sec-WebSocket-Version: 13\r\n";
     $message .= "\r\n";
     yield from $socket->write($message);
 }
コード例 #9
0
 /**
  * Assert that the given HTTP request can be upgraded to the WebSocket protocol.
  */
 protected function assertUpgradePossible(HttpRequest $request)
 {
     if ($request->getMethod() !== Http::GET) {
         throw new StatusException(Http::METHOD_NOT_ALLOWED, 'WebSocket upgrade requires an HTTP GET request', ['Allow' => Http::GET, 'Sec-Websocket-Version' => '13']);
     }
     if (!$request->hasHeader('Sec-Websocket-Key')) {
         throw new StatusException(Http::BAD_REQUEST, 'Missing Sec-Websocket-Key HTTP header', ['Sec-Websocket-Version' => '13']);
     }
     if (!\in_array('13', $request->getHeaderTokenValues('Sec-Websocket-Version'), true)) {
         throw new StatusException(Http::BAD_REQUEST, 'Secure websocket version 13 required', ['Sec-Websocket-Version' => '13']);
     }
 }
コード例 #10
0
ファイル: PublishFiles.php プロジェクト: koolkode/async-http
 /**
  * Create an HTTP file response for the matched file.
  * 
  * @param HttpRequest $request
  * @param string $file
  * @return HttpResponse
  */
 protected function createResponse(HttpRequest $request, string $file) : HttpResponse
 {
     $response = new FileResponse($file);
     $response = $response->withHeader('Cache-Control', \sprintf('public, max-age=%u', $this->ttl));
     if ($request->getProtocolVersion() === '1.0') {
         $response = $response->withHeader('Expires', \gmdate(Http::DATE_RFC1123, \time() + $this->ttl));
     }
     return $response;
 }
コード例 #11
0
 /**
  * Convert the given outcome of an action into an HTTP response.
  * 
  * @param HttpRequest $request
  * @param mixed $result
  * @return HttpResponse
  */
 public function respond(HttpRequest $request, $result) : HttpResponse
 {
     if ($result instanceof HttpResponse) {
         return $result->withProtocolVersion($request->getProtocolVersion());
     }
     foreach ($this->responders as $responder) {
         $response = ($responder->callback)($request, $result);
         if ($response instanceof HttpResponse) {
             return $response->withProtocolVersion($request->getProtocolVersion());
         }
     }
     $reason = \sprintf('Expecting HttpResponse, given %s', \is_object($result) ? \get_class($result) : \gettype($result));
     throw new \RuntimeException($reason);
 }
コード例 #12
0
ファイル: Handler.php プロジェクト: koolkode/async-http
 /**
  * Normalize the given HTTP response to be sent using FCGI records.
  * 
  * @param HttpRequest $request
  * @param HttpResponse $response
  * @return HttpResponse
  */
 protected function normalizeResponse(HttpRequest $request, HttpResponse $response) : HttpResponse
 {
     static $remove = ['Connection', 'Content-Length', 'Keep-Alive', 'Status', 'Trailer', 'Transfer-Encoding', 'Upgrade'];
     $response = $response->withProtocolVersion($request->getProtocolVersion());
     foreach ($remove as $name) {
         $response = $response->withoutHeader($name);
     }
     return $response->withHeader('Date', \gmdate(Http::DATE_RFC1123));
 }
コード例 #13
0
ファイル: HttpClient.php プロジェクト: koolkode/async-http
 /**
  * Send the given HTTP request and fetch the HTTP response from the server.
  * 
  * @param HttpRequest $request The request to be sent (body will be closed after it has been sent).
  * @return HttpResponse The HTTP response as returned by the target server.
  */
 public function send(HttpRequest $request) : Awaitable
 {
     if (!$request->hasHeader('User-Agent')) {
         $request = $request->withHeader('User-Agent', $this->userAgent);
     }
     $next = new NextMiddleware($this->middlewares, function (HttpRequest $request) {
         $connecting = new \SplObjectStorage();
         $uri = $request->getUri();
         foreach ($this->connectors as $connector) {
             if (!$connector->isRequestSupported($request)) {
                 continue;
             }
             $context = (yield $connector->getConnectorContext($uri));
             if ($context->connected) {
                 foreach ($connecting as $conn) {
                     $connecting[$conn]->dispose();
                 }
                 return (yield $connector->send($context, $request));
             }
             $connecting[$connector] = $context;
         }
         try {
             $socket = (yield $this->connectSocket($request->getUri()));
             $meta = $socket->getMetadata();
             try {
                 $connector = $this->chooseConnector($request, \trim($meta['crypto']['alpn_protocol'] ?? ''), $meta);
             } catch (\Throwable $e) {
                 $socket->close();
                 throw $e;
             }
             $context = $connecting[$connector];
             $context->socket = $socket;
             $connecting->detach($connector);
             return (yield $connector->send($context, $request));
         } finally {
             foreach ($connecting as $conn) {
                 $connecting[$conn]->dispose();
             }
         }
     }, $this->logger);
     return new Coroutine($next($request));
 }
コード例 #14
0
ファイル: Driver.php プロジェクト: koolkode/async-http
 /**
  * Check for a pre-parsed HTTP/2 connection preface.
  */
 protected function isPrefaceRequest(HttpRequest $request) : bool
 {
     if ($request->getMethod() !== 'PRI') {
         return false;
     }
     if ($request->getRequestTarget() !== '*') {
         return false;
     }
     if ($request->getProtocolVersion() !== '2.0') {
         return false;
     }
     return true;
 }
コード例 #15
0
ファイル: Connector.php プロジェクト: koolkode/async-http
 /**
  * {@inheritdoc}
  */
 public function send(HttpConnectorContext $context, HttpRequest $request) : Awaitable
 {
     if (!$context instanceof ConnectorContext) {
         throw new \InvalidArgumentException('Invalid connector context passed');
     }
     return new Coroutine(function () use($context, $request) {
         $uri = $request->getUri();
         $key = \sprintf('%s://%s', $uri->getScheme(), $uri->getHostWithPort(true));
         if ($context->conn) {
             $conn = $context->conn;
         } else {
             $conn = new Connection($context->socket, new HPack($this->hpackContext));
             if ($this->logger) {
                 $conn->setLogger($this->logger);
             }
             (yield $conn->performClientHandshake());
             $this->connections[$key] = $conn;
         }
         $request = $request->withProtocolVersion('2.0');
         $request = $request->withHeader('Date', \gmdate(Http::DATE_RFC1123));
         $sent = 0;
         $response = (yield $conn->openStream()->sendRequest($request, $sent));
         if ($this->logger) {
             $this->logger->info('{ip} "{method} {target} HTTP/{protocol}" {status} {size}', ['ip' => $request->getClientAddress(), 'method' => $request->getMethod(), 'target' => $request->getRequestTarget(), 'protocol' => $response->getProtocolVersion(), 'status' => $response->getStatusCode(), 'size' => $sent ?: '-']);
         }
         if (isset($this->connecting[$key])) {
             $context->connected = true;
             $context->conn = $conn;
             $context->dispose();
         }
         return $response;
     });
 }
コード例 #16
0
 /**
  * Get all known hop IP addresses as provided by an HTTP proxy.
  * 
  * @param HttpRequest $request
  * @return array
  */
 public function getAddresses(HttpRequest $request) : array
 {
     $addresses = [];
     foreach ($this->addressHeaders as $name) {
         if ($request->hasHeader($name)) {
             foreach ($request->getHeaderTokenValues($name, false) as $ip) {
                 $addresses[] = $ip;
             }
         }
     }
     return $addresses;
 }
コード例 #17
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;
 }
コード例 #18
0
ファイル: Connector.php プロジェクト: koolkode/async-http
 protected function serializeHeaders(HttpRequest $request, int $size = null)
 {
     if (\in_array('upgrade', $request->getHeaderTokenValues('Connection'))) {
         $request = $request->withHeader('Connection', 'upgrade');
     } else {
         $request = $request->withHeader('Connection', $this->keepAlive ? 'keep-alive' : 'close');
     }
     $buffer = \sprintf("%s %s HTTP/%s\r\n", $request->getMethod(), $request->getRequestTarget(), $request->getProtocolVersion());
     if ($this->keepAlive) {
         $buffer .= \sprintf("Keep-Alive: timeout=%u\r\n", $this->pool->getMaxLifetime());
     }
     if ($size === null) {
         $buffer .= "Transfer-Encoding: chunked\r\n";
     } else {
         $buffer .= "Content-Length: {$size}\r\n";
     }
     foreach ($request->getHeaders() as $name => $header) {
         $name = Http::normalizeHeaderName($name);
         foreach ($header as $value) {
             $buffer .= $name . ': ' . $value . "\r\n";
         }
     }
     return $buffer;
 }
コード例 #19
0
ファイル: Driver.php プロジェクト: koolkode/async-http
 /**
  * Normalize HTTP response object prior to being sent to the client.
  */
 protected function normalizeResponse(HttpRequest $request, HttpResponse $response) : HttpResponse
 {
     static $remove = ['Content-Length', 'Keep-Alive', 'Trailer', 'Transfer-Encoding'];
     $response = $response->withProtocolVersion($request->getProtocolVersion());
     foreach ($remove as $name) {
         $response = $response->withoutHeader($name);
     }
     $conn = [];
     foreach ($response->getHeaderTokenValues('Connection') as $token) {
         switch ($token) {
             case 'close':
             case 'keep-alive':
                 // Ignore these...
                 break;
             default:
                 $conn[] = $token;
         }
     }
     if (empty($conn)) {
         $response = $response->withoutHeader('Connection');
     } else {
         $response = $response->withHeader('Connection', \implode(', ', $conn));
     }
     return $response->withHeader('Date', \gmdate(Http::DATE_RFC1123));
 }