Exemple #1
0
 /**
  * We have to keep a static reference of eio event streams
  * because if we don't garbage collection can unload eio's
  * underlying pipe via a system close() call before it's
  * finished and generate a SIGPIPE.
  */
 public function __construct()
 {
     if (empty(self::$stream)) {
         \eio_init();
         self::$stream = \eio_get_event_stream();
     }
     $this->callableDecrementor = function () {
         \call_user_func($this->incrementor, -1);
     };
     $this->incrementor = function ($increment) {
         switch ($increment) {
             case 1:
             case -1:
                 $this->pending += $increment;
                 break;
             default:
                 throw new FilesystemException("Invalid pending event increment; 1 or -1 required");
         }
         if ($this->pending === 0) {
             \Amp\disable($this->watcher);
         } elseif ($this->pending === 1) {
             \Amp\enable($this->watcher);
         }
     };
     $this->watcher = \Amp\onReadable(self::$stream, function () {
         while (\eio_npending()) {
             \eio_poll();
         }
     }, $options = ["enable" => false]);
 }
Exemple #2
0
 protected function output(string $message)
 {
     if (empty($this->isDead)) {
         $this->writeQueue[] = pack("N", \strlen($message));
         $this->writeQueue[] = $message;
         \Amp\enable($this->writeWatcherId);
     }
 }
Exemple #3
0
 private function processSendResult($sendResult)
 {
     if ($sendResult === 0) {
         // Send didn't complete in the initial pg_send_query() call;
         // enable the write watcher.
         \Amp\enable($this->writeWatcher);
         return;
     }
     if ($sendResult === FALSE) {
         $this->failCurrentOperation();
     }
 }
Exemple #4
0
 /**
  * @return Promise which will succeed after $str was written. It will contain the total number of already written bytes to the process
  */
 public function write($str)
 {
     if (strlen($str) === 0) {
         throw new \InvalidArgumentException("String to be written cannot be empty");
     }
     if (!$this->proc) {
         throw new \RuntimeException("Process was not yet launched");
     }
     $this->writeBuf .= $str;
     \Amp\enable($this->stdin);
     $this->writeTotal += strlen($str);
     $deferred = $this->writeDeferreds[$this->writeTotal] = new Deferred();
     return $deferred->promise();
 }
Exemple #5
0
 public function parser(Client $client) : \Generator
 {
     $maxHeaderSize = $client->options->maxHeaderSize;
     $maxBodySize = $client->options->maxBodySize;
     $bodyEmitSize = $client->options->ioGranularity;
     $buffer = "";
     do {
         // break potential references
         unset($traceBuffer, $protocol, $method, $uri, $headers);
         $traceBuffer = null;
         $headers = [];
         $contentLength = null;
         $isChunked = false;
         $protocol = null;
         $uri = null;
         $method = null;
         $parseResult = ["id" => 0, "trace" => &$traceBuffer, "protocol" => &$protocol, "method" => &$method, "uri" => &$uri, "headers" => &$headers, "body" => ""];
         if ($client->parserEmitLock) {
             do {
                 if (\strlen($buffer) > $maxHeaderSize + $maxBodySize) {
                     \Amp\disable($client->readWatcher);
                     $client->parserEmitLock = false;
                 }
                 $buffer .= yield;
             } while ($client->parserEmitLock);
             \Amp\enable($client->readWatcher);
         }
         $client->parserEmitLock = true;
         while (1) {
             $buffer = \ltrim($buffer, "\r\n");
             if ($headerPos = \strpos($buffer, "\r\n\r\n")) {
                 $startLineAndHeaders = \substr($buffer, 0, $headerPos + 2);
                 $buffer = (string) \substr($buffer, $headerPos + 4);
                 break;
             } elseif ($maxHeaderSize > 0 && strlen($buffer) > $maxHeaderSize) {
                 $error = "Bad Request: header size violation";
                 break 2;
             }
             $buffer .= yield;
         }
         $startLineEndPos = \strpos($startLineAndHeaders, "\n");
         $startLine = \rtrim(substr($startLineAndHeaders, 0, $startLineEndPos), "\r\n");
         $rawHeaders = \substr($startLineAndHeaders, $startLineEndPos + 1);
         $traceBuffer = $startLineAndHeaders;
         if (!($method = \strtok($startLine, " "))) {
             $error = "Bad Request: invalid request line";
             break;
         }
         if (!($uri = \strtok(" "))) {
             $error = "Bad Request: invalid request line";
             break;
         }
         $protocol = \strtok(" ");
         if (stripos($protocol, "HTTP/") !== 0) {
             $error = "Bad Request: invalid request line";
             break;
         }
         $protocol = \substr($protocol, 5);
         if ($protocol != "1.1" && $protocol != "1.0") {
             // @TODO eventually add an option to disable HTTP/2.0 support???
             if ($protocol == "2.0") {
                 $client->httpDriver = $this->http2;
                 $client->requestParser = $client->httpDriver->parser($client);
                 $client->requestParser->send("{$startLineAndHeaders}\r\n{$buffer}");
                 return;
             } else {
                 $error = HttpDriver::BAD_VERSION;
                 break;
             }
         }
         if ($rawHeaders) {
             if (\strpos($rawHeaders, "\n ") || \strpos($rawHeaders, "\n\t")) {
                 $error = "Bad Request: multi-line headers deprecated by RFC 7230";
                 break;
             }
             if (!\preg_match_all(self::HEADER_REGEX, $rawHeaders, $matches)) {
                 $error = "Bad Request: header syntax violation";
                 break;
             }
             list(, $fields, $values) = $matches;
             $headers = [];
             foreach ($fields as $index => $field) {
                 $headers[$field][] = $values[$index];
             }
             if ($headers) {
                 $headers = \array_change_key_case($headers);
             }
             $contentLength = $headers["content-length"][0] ?? null;
             if (isset($headers["transfer-encoding"])) {
                 $value = $headers["transfer-encoding"][0];
                 $isChunked = (bool) \strcasecmp($value, "identity");
             }
             // @TODO validate that the bytes in matched headers match the raw input. If not there is a syntax error.
         }
         if ($contentLength > $maxBodySize) {
             $error = "Bad request: entity too large";
             break;
         } elseif ($method == "HEAD" || $method == "TRACE" || $method == "OPTIONS" || $contentLength === 0) {
             // No body allowed for these messages
             $hasBody = false;
         } else {
             $hasBody = $isChunked || $contentLength;
         }
         if (!$hasBody) {
             ($this->parseEmitter)([HttpDriver::RESULT, $parseResult, null], $client);
             continue;
         }
         ($this->parseEmitter)([HttpDriver::ENTITY_HEADERS, $parseResult, null], $client);
         $body = "";
         if ($isChunked) {
             while (1) {
                 while (false === ($lineEndPos = \strpos($buffer, "\r\n"))) {
                     $buffer .= yield;
                 }
                 $line = \substr($buffer, 0, $lineEndPos);
                 $buffer = \substr($buffer, $lineEndPos + 2);
                 $hex = \trim(\ltrim($line, "0")) ?: 0;
                 $chunkLenRemaining = \hexdec($hex);
                 if ($lineEndPos === 0 || $hex != \dechex($chunkLenRemaining)) {
                     $error = "Bad Request: hex chunk size expected";
                     break 2;
                 }
                 if ($chunkLenRemaining === 0) {
                     while (!isset($buffer[1])) {
                         $buffer .= yield;
                     }
                     $firstTwoBytes = \substr($buffer, 0, 2);
                     if ($firstTwoBytes === "\r\n") {
                         $buffer = \substr($buffer, 2);
                         break;
                         // finished ($is_chunked loop)
                     }
                     do {
                         if ($trailerSize = \strpos($buffer, "\r\n\r\n")) {
                             $trailers = \substr($buffer, 0, $trailerSize + 2);
                             $buffer = \substr($buffer, $trailerSize + 4);
                         } else {
                             $buffer .= yield;
                             $trailerSize = \strlen($buffer);
                             $trailers = null;
                         }
                         if ($maxHeaderSize > 0 && $trailerSize > $maxHeaderSize) {
                             $error = "Trailer headers too large";
                             break 3;
                         }
                     } while (!isset($trailers));
                     if (\strpos($trailers, "\n ") || \strpos($trailers, "\n\t")) {
                         $error = "Bad Request: multi-line trailers deprecated by RFC 7230";
                         break 2;
                     }
                     if (!\preg_match_all(self::HEADER_REGEX, $trailers, $matches)) {
                         $error = "Bad Request: trailer syntax violation";
                         break 2;
                     }
                     list(, $fields, $values) = $matches;
                     $trailers = [];
                     foreach ($fields as $index => $field) {
                         $trailers[$field][] = $values[$index];
                     }
                     if ($trailers) {
                         $trailers = \array_change_key_case($trailers);
                         foreach (["transfer-encoding", "content-length", "trailer"] as $remove) {
                             unset($trailers[$remove]);
                         }
                         if ($trailers) {
                             $headers = \array_merge($headers, $trailers);
                         }
                     }
                     break;
                     // finished ($is_chunked loop)
                 } elseif ($chunkLenRemaining > $maxBodySize) {
                     $error = "Bad Request: excessive chunk size";
                     break 2;
                 } else {
                     $bodyBufferSize = 0;
                     while (1) {
                         $bufferLen = \strlen($buffer);
                         // These first two (extreme) edge cases prevent errors where the packet boundary ends after
                         // the \r and before the \n at the end of a chunk.
                         if ($bufferLen === $chunkLenRemaining || $bufferLen === $chunkLenRemaining + 1) {
                             $buffer .= yield;
                             continue;
                         } elseif ($bufferLen >= $chunkLenRemaining + 2) {
                             $body .= substr($buffer, 0, $chunkLenRemaining);
                             $buffer = substr($buffer, $chunkLenRemaining + 2);
                             $bodyBufferSize += $chunkLenRemaining;
                         } else {
                             $body .= $buffer;
                             $bodyBufferSize += $bufferLen;
                             $chunkLenRemaining -= $bufferLen;
                         }
                         if ($bodyBufferSize >= $bodyEmitSize) {
                             ($this->parseEmitter)([HttpDriver::ENTITY_PART, ["id" => 0, "body" => $body], null], $client);
                             $body = '';
                             $bodyBufferSize = 0;
                         }
                         if ($bufferLen >= $chunkLenRemaining + 2) {
                             $chunkLenRemaining = null;
                             continue 2;
                             // next chunk ($is_chunked loop)
                         } else {
                             $buffer = yield;
                         }
                     }
                 }
             }
         } else {
             $bufferDataSize = \strlen($buffer);
             while ($bufferDataSize < $contentLength) {
                 if ($bufferDataSize >= $bodyEmitSize) {
                     ($this->parseEmitter)([HttpDriver::ENTITY_PART, ["id" => 0, "body" => $buffer], null], $client);
                     $buffer = "";
                     $contentLength -= $bufferDataSize;
                 }
                 $buffer .= yield;
                 $bufferDataSize = \strlen($buffer);
             }
             if ($bufferDataSize === $contentLength) {
                 $body = $buffer;
                 $buffer = "";
             } else {
                 $body = substr($buffer, 0, $contentLength);
                 $buffer = (string) \substr($buffer, $contentLength);
             }
         }
         if ($body != "") {
             ($this->parseEmitter)([HttpDriver::ENTITY_PART, ["id" => 0, "body" => $body], null], $client);
         }
         ($this->parseEmitter)([HttpDriver::ENTITY_RESULT, $parseResult, null], $client);
     } while (true);
     // An error occurred...
     // stop parsing here ...
     ($this->parseEmitter)([HttpDriver::ERROR, $parseResult, $error], $client);
     while (1) {
         yield;
     }
 }
Exemple #6
0
 private function onWritable(string $watcherId, $socket, $client)
 {
     $bytesWritten = @fwrite($socket, $client->writeBuffer);
     if ($bytesWritten === false) {
         if (!is_resource($socket) || @feof($socket)) {
             $client->isDead = true;
             $this->close($client);
         }
     } elseif ($bytesWritten === strlen($client->writeBuffer)) {
         $client->writeBuffer = "";
         \Amp\disable($watcherId);
         if ($client->onWriteDrain) {
             ($client->onWriteDrain)($client);
         }
     } else {
         $client->writeBuffer = substr($client->writeBuffer, $bytesWritten);
         \Amp\enable($watcherId);
     }
 }
Exemple #7
0
 private function timeout()
 {
     $this->now = $now = time();
     foreach ($this->closeTimeouts as $clientId => $expiryTime) {
         if ($expiryTime < $now) {
             $this->unloadClient($this->clients[$clientId]);
             unset($this->closeTimeouts[$clientId]);
         } else {
             break;
         }
     }
     foreach ($this->heartbeatTimeouts as $clientId => $expiryTime) {
         if ($expiryTime < $now) {
             $client = $this->clients[$clientId];
             unset($this->heartbeatTimeouts[$clientId]);
             $this->heartbeatTimeouts[$clientId] = $now + $this->heartbeatPeriod;
             $this->sendHeartbeatPing($client);
         } else {
             break;
         }
     }
     foreach ($this->lowCapacityClients as $id => $client) {
         $client->capacity += $this->maxBytesPerMinute / 60;
         if ($client->capacity > $this->maxBytesPerMinute) {
             unset($this->lowCapacityClients[$id]);
         }
         if ($client->capacity > 8192 && !isset($this->highFramesPerSecondClients[$id])) {
             \Amp\enable($client->readWatcher);
         }
     }
     foreach ($this->highFramesPerSecondClients as $id => $client) {
         $client->framesLastSecond -= $this->maxFramesPerSecond;
         if ($client->framesLastSecond < $this->maxFramesPerSecond) {
             unset($this->highFramesPerSecondClients[$id]);
             if ($client->capacity > 8192) {
                 \Amp\enable($client->readWatcher);
             }
         }
     }
 }
Exemple #8
0
 private function onDeadIpcClient(string $readWatcherId, $ipcClient)
 {
     \Amp\cancel($readWatcherId);
     @fclose($ipcClient);
     unset($this->ipcClients[$readWatcherId]);
     $this->defunctProcessCount++;
     \Amp\enable($this->procGarbageWatcher);
 }
Exemple #9
0
 private function write(Rfc6455Client $client, $frameInfo) : Promise
 {
     if ($client->closedAt) {
         return new Failure(new ClientException());
     }
     $msg = $frameInfo["msg"];
     $len = \strlen($msg);
     $w = chr($frameInfo["fin"] << 7 | $frameInfo["rsv"] << 4 | $frameInfo["opcode"]);
     if ($len > 0xffff) {
         $w .= "" . pack('J', $len);
     } elseif ($len > 0x7d) {
         $w .= "~" . pack('n', $len);
     } else {
         $w .= chr($len);
     }
     $w .= $msg;
     \Amp\enable($client->writeWatcher);
     if ($client->writeBuffer != "") {
         if ($frameInfo["opcode"] >= 0x8) {
             $client->writeControlQueue[] = $w;
             $deferred = $client->writeDeferredControlQueue[] = new Deferred();
         } else {
             $client->writeDataQueue[] = $w;
             $deferred = $client->writeDeferredDataQueue[] = new Deferred();
         }
     } else {
         $client->writeBuffer = $w;
         $deferred = $client->writeDeferred = new Deferred();
     }
     return $deferred->promise();
 }
Exemple #10
0
 private function incrementPending()
 {
     if ($this->pending++ === 0) {
         \Amp\enable($this->watcher);
     }
 }
Exemple #11
0
 private function initializeIdleTimeout(SocketPoolStruct $poolStruct)
 {
     if (isset($poolStruct->idleWatcher)) {
         \Amp\enable($poolStruct->idleWatcher);
     } else {
         $poolStruct->idleWatcher = \Amp\once(function () use($poolStruct) {
             $this->unloadSocket($poolStruct->uri, $poolStruct->id);
         }, $poolStruct->msIdleTimeout);
     }
 }
 /**
  * @param array $strings
  * @return Promise
  */
 public function send(array $strings)
 {
     return \Amp\pipe($this->connect(), function () use($strings) {
         $payload = $this->parsePayload($strings);
         $this->outputBuffer .= $payload;
         $this->outputBufferLength += strlen($payload);
         if ($this->reader !== null) {
             \Amp\enable($this->reader);
         }
         if ($this->writer !== null) {
             \Amp\enable($this->writer);
         }
     });
 }
Exemple #13
0
 private function onWritable(string $watcherId, $socket, $client)
 {
     $bytesWritten = @\fwrite($socket, $client->writeBuffer);
     if ($bytesWritten === false) {
         if (!\is_resource($socket) || @\feof($socket)) {
             $client->isDead = true;
             $this->close($client);
         }
     } else {
         if ($bytesWritten === \strlen($client->writeBuffer)) {
             $client->writeBuffer = "";
             \Amp\disable($watcherId);
             if ($client->onWriteDrain) {
                 ($client->onWriteDrain)($client);
             }
         } else {
             $client->writeBuffer = \substr($client->writeBuffer, $bytesWritten);
             \Amp\enable($watcherId);
         }
         $client->bufferSize -= $bytesWritten;
         if ($client->bufferPromisor && $client->bufferSize <= $client->options->softStreamCap) {
             $client->bufferPromisor->succeed();
             $client->bufferPromisor = null;
         }
     }
 }
Exemple #14
0
function __finalizeResult($state, $serverId, $requestId, $error = null, $result = null)
{
    if (empty($state->pendingRequests[$requestId])) {
        return;
    }
    list($promisor, $name) = $state->pendingRequests[$requestId];
    $server = $state->serverIdMap[$serverId];
    unset($state->pendingRequests[$requestId], $server->pendingRequests[$requestId]);
    if (empty($server->pendingRequests)) {
        $state->serverIdTimeoutMap[$server->id] = $state->now + IDLE_TIMEOUT;
        \Amp\disable($server->watcherId);
        \Amp\enable($state->serverTimeoutWatcher);
    }
    if ($error) {
        $promisor->fail($error);
    } else {
        foreach ($result as $type => $records) {
            $minttl = INF;
            foreach ($records as list(, $ttl)) {
                if ($ttl && $minttl > $ttl) {
                    $minttl = $ttl;
                }
            }
            $state->arrayCache->set("{$name}#{$type}", $records, $minttl);
        }
        $promisor->succeed($result);
    }
}
Exemple #15
0
 /**
  * @return Promise which will succeed after $str was written. It will contain the total number of already written bytes to the process
  */
 public function write($str)
 {
     assert(strlen($str) > 0);
     if (!$this->proc) {
         throw new \RuntimeException("Process was not yet launched");
     }
     $this->writeBuf .= $str;
     \Amp\enable($this->stdin);
     $this->writeTotal += strlen($str);
     $deferred = $this->writeDeferreds[$this->writeTotal] = new Deferred();
     return $deferred->promise();
 }
Exemple #16
0
 public static function reader($watcher, $socket, $info)
 {
     $buffer =& $info[0];
     $data = @fread($socket, 8192);
     if ($data != "") {
         if ($buffer == "") {
             \Amp\enable($info[1]);
         }
         $buffer .= $data;
         if (\strlen($buffer) > self::MAX_INTERMEDIARY_BUFFER) {
             \Amp\disable($watcher);
         }
     } elseif (!is_resource($socket) || @feof($socket)) {
         \Amp\cancel($watcher);
         if ($buffer == "") {
             \Amp\cancel($info[1]);
         } else {
             $info[2] = true;
         }
     }
 }