Example #1
0
 private function enableWriteWatcher(HttpTunnelStruct $struct)
 {
     if ($struct->writeWatcher === null) {
         $struct->writeWatcher = \Amp\onWritable($struct->socket, function () use($struct) {
             $this->doWrite($struct);
         });
     }
 }
Example #2
0
 private function enableWriteWatcher()
 {
     if (empty($this->writeWatcher)) {
         $this->writeWatcher = \Amp\onWritable($this->socket, function () {
             $this->doWrite();
         });
     }
 }
Example #3
0
 private function establish()
 {
     \Amp\pipe(\Amp\file\get($this->path), 'Amp\\Socket\\connect')->when(function ($e, $sock) {
         if ($e) {
             $this->failAll();
             return;
         }
         $this->sock = $sock;
         $this->writeWatcher = \Amp\onWritable($sock, $this->writer);
     });
 }
Example #4
0
 public function __construct(Console $console, $ipcSock)
 {
     if ($console->isArgDefined("color")) {
         $this->setAnsify($console->getArg("color"));
     }
     $level = $console->getArg("log");
     $level = isset(self::LEVELS[$level]) ? self::LEVELS[$level] : $level;
     $this->setOutputLevel($level);
     $onWritable = $this->makePrivateCallable("onWritable");
     $this->ipcSock = $ipcSock;
     $this->writeWatcherId = \Amp\onWritable($ipcSock, $onWritable, ["enable" => false]);
 }
Example #5
0
 private function establish()
 {
     $unix = in_array("unix", \stream_get_transports(), true);
     if ($unix) {
         $promise = \Amp\Socket\connect("unix://{$this->path}.sock");
     } else {
         $promise = \Amp\pipe(\Amp\file\get($this->path), 'Amp\\Socket\\connect');
     }
     $promise->when(function ($e, $sock) {
         if ($e) {
             $this->failAll();
             return;
         }
         $this->sock = $sock;
         $this->writeWatcher = \Amp\onWritable($sock, $this->writer);
     });
 }
Example #6
0
 /**
  * @param int $buffer one of the self::BUFFER_* constants. Determines whether it will buffer the stdout and/or stderr data internally
  * @return Promise is updated with ["out", $data] or ["err", $data] for data received on stdout or stderr
  * That Promise will be resolved to a stdClass object with stdout, stderr (when $buffer is true), exit (holding exit code) and signal (only present when terminated via signal) properties
  */
 public function exec($buffer = self::BUFFER_NONE)
 {
     if ($this->proc) {
         throw new \RuntimeException("Process was already launched");
     }
     $cwd = isset($this->options["cwd"]) ? $this->options["cwd"] : NULL;
     $env = isset($this->options["env"]) ? $this->options["env"] : NULL;
     if (stripos(PHP_OS, "WIN") !== 0) {
         $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"], ["pipe", "w"]];
         $this->proc = @proc_open("{$this->cmd}; echo \$? >&3", $fds, $pipes, $cwd, $env, $this->options);
     } else {
         $options = $this->options;
         $options["bypass_shell"] = true;
         $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]];
         $this->proc = @proc_open($this->cmd, $fds, $pipes, $cwd, $env, $options);
     }
     if (!$this->proc) {
         return new Failure(new \RuntimeException("Failed executing command: {$this->cmd}"));
     }
     $this->writeBuf = "";
     $this->writeTotal = 0;
     $this->writeCur = 0;
     stream_set_blocking($pipes[0], false);
     stream_set_blocking($pipes[1], false);
     stream_set_blocking($pipes[2], false);
     if (isset($pipes[3])) {
         stream_set_blocking($pipes[3], false);
         $this->openPipes = 3;
     } else {
         $this->openPipes = 2;
     }
     $this->deferred = new Deferred();
     $result = new \stdClass();
     if ($buffer & self::BUFFER_STDOUT) {
         $result->stdout = "";
     }
     if ($buffer & self::BUFFER_STDERR) {
         $result->stderr = "";
     }
     $cleanup = function () use($result) {
         \Amp\cancel($this->stdin);
         $deferreds = $this->writeDeferreds;
         $this->writeDeferreds = [];
         $status = \proc_get_status($this->proc);
         if ($status["running"] === false && $status["signaled"]) {
             $result->signal = $status["termsig"];
             $result->exit = $status["exitcode"];
         }
         if (!isset($this->exit)) {
             $result->exit = proc_close($this->proc);
         }
         unset($this->proc);
         $this->deferred->succeed($result);
         foreach ($deferreds as $deferred) {
             $deferred->fail(new \Exception("Write could not be completed, process finished"));
         }
     };
     $this->stdout = \Amp\onReadable($pipes[1], function ($watcher, $sock) use($result, $cleanup) {
         if ("" == ($data = @\fread($sock, 8192))) {
             \Amp\cancel($watcher);
             if (--$this->openPipes == 0) {
                 \Amp\immediately($cleanup);
             }
         } else {
             if (isset($result->stdout)) {
                 $result->stdout .= $data;
             }
             $this->deferred->update(["out", $data]);
         }
     });
     $this->stderr = \Amp\onReadable($pipes[2], function ($watcher, $sock) use($result, $cleanup) {
         if ("" == ($data = @\fread($sock, 8192))) {
             \Amp\cancel($watcher);
             if (--$this->openPipes == 0) {
                 \Amp\immediately($cleanup);
             }
         } else {
             if (isset($result->stderr)) {
                 $result->stderr .= $data;
             }
             $this->deferred->update(["err", $data]);
         }
     });
     $this->stdin = \Amp\onWritable($pipes[0], function ($watcher, $sock) {
         $this->writeCur += @\fwrite($sock, $this->writeBuf);
         if ($this->writeCur == $this->writeTotal) {
             \Amp\disable($watcher);
         }
         while (($next = key($this->writeDeferreds)) !== null && $next <= $this->writeCur) {
             $this->writeDeferreds[$next]->succeed($this->writeCur);
             unset($this->writeDeferreds[$next]);
         }
     }, ["enable" => false]);
     if (isset($pipes[3])) {
         $this->exit = \Amp\onReadable($pipes[3], function ($watcher, $sock) use($result, $cleanup) {
             stream_set_blocking($sock, true);
             // it should never matter, but just to be really 100% sure.
             $result->exit = (int) stream_get_contents($sock);
             \Amp\cancel($watcher);
             if (--$this->openPipes == 0) {
                 \Amp\immediately($cleanup);
             }
         });
     }
     return $this->deferred->promise();
 }
Example #7
0
 private function importClient($socket, string $peerName, HttpDriver $http)
 {
     $client = new Client();
     $client->id = (int) $socket;
     $client->socket = $socket;
     $client->httpDriver = $http;
     $client->exporter = $this->exporter;
     $client->remainingKeepAlives = $this->options->maxKeepAliveRequests ?: null;
     $portStartPos = strrpos($peerName, ":");
     $client->clientAddr = substr($peerName, 0, $portStartPos);
     $client->clientPort = substr($peerName, $portStartPos + 1);
     $serverName = stream_socket_get_name($socket, false);
     $portStartPos = strrpos($serverName, ":");
     $client->serverAddr = substr($serverName, 0, $portStartPos);
     $client->serverPort = substr($serverName, $portStartPos + 1);
     $meta = stream_get_meta_data($socket);
     $client->cryptoInfo = $meta["crypto"] ?? [];
     $client->isEncrypted = (bool) $client->cryptoInfo;
     $client->requestParser = $http->parser($client);
     $client->requestParser->send("");
     $client->readWatcher = \Amp\onReadable($socket, $this->onReadable, $options = ["enable" => true, "cb_data" => $client]);
     $client->writeWatcher = \Amp\onWritable($socket, $this->onWritable, $options = ["enable" => false, "cb_data" => $client]);
     $this->renewKeepAliveTimeout($client);
     $this->clients[$client->id] = $client;
 }
Example #8
0
 public function send($socket)
 {
     $deferred = new Deferred();
     stream_set_blocking($socket, false);
     $data = $this->getRequest();
     \Amp\onWritable($socket, function ($writer, $socket) use($deferred, &$data) {
         if ($bytes = fwrite($socket, $data)) {
             if ($bytes < \strlen($data)) {
                 $data = substr($data, $bytes);
                 return;
             }
             $size = 8192;
             \Amp\onReadable($socket, function ($reader, $socket) use($deferred, &$size) {
                 /* make attention to not read too much data */
                 $data = stream_socket_recvfrom($socket, $size, STREAM_PEEK);
                 if (false === ($pos = strpos($data, "\r\n\r\n"))) {
                     if (\strlen($data) == $size) {
                         $size *= 2;
                         // unbounded??
                     }
                     return;
                 }
                 \Amp\cancel($reader);
                 $deferred->succeed($this->parseResponse(fread($socket, $pos + 4)));
             });
         } else {
             $deferred->succeed(null);
         }
         \Amp\cancel($writer);
     });
     return $deferred->promise();
 }
Example #9
0
 public function reapClient($watcherId, InternalRequest $ireq)
 {
     $client = new Rfc6455Client();
     $client->capacity = $this->maxBytesPerMinute;
     $client->connectedAt = $this->now;
     $socket = $ireq->client->socket;
     $client->id = (int) $socket;
     $client->socket = $socket;
     $client->writeBuffer = $ireq->client->writeBuffer;
     $client->serverRefClearer = ($ireq->client->exporter)($ireq->client);
     $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client, "max_msg_size" => $this->maxMsgSize, "max_frame_size" => $this->maxFrameSize, "validate_utf8" => $this->validateUtf8, "text_only" => $this->textOnly, "threshold" => $this->autoFrameSize]);
     $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]);
     $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]);
     $this->clients[$client->id] = $client;
     $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod;
     resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"]));
     return $client;
 }
Example #10
0
 public function reapClient($watcherId, InternalRequest $ireq)
 {
     $client = $ireq->client->socket;
     list($reverse, $externBuf) = $ireq->locals["aerys.reverse.socket"];
     $serverRefClearer = ($ireq->client->exporter)($ireq->client)();
     $internBuf = "";
     $clientWrite = \Amp\onWritable($client, [self::class, "writer"], ["cb_data" => [&$externBuf, &$reverseRead, &$extern], "enable" => false, "keep_alive" => false]);
     $reverseWrite = \Amp\onWritable($reverse, [self::class, "writer"], ["cb_data" => [&$internBuf, &$clientRead, &$intern], "enable" => false, "keep_alive" => false]);
     $clientRead = \Amp\onReadable($client, [self::class, "reader"], ["cb_data" => [&$internBuf, $reverseWrite, &$intern], "keep_alive" => false]);
     $reverseRead = \Amp\onReadable($reverse, [self::class, "reader"], ["cb_data" => [&$externBuf, $clientWrite, &$intern], "keep_alive" => false]);
 }
Example #11
0
 public function reapClient($watcherId, InternalRequest $ireq)
 {
     $client = new Rfc6455Client();
     $client->connectedAt = $this->now;
     $socket = $ireq->client->socket;
     $client->id = (int) $socket;
     $client->socket = $socket;
     $client->writeBuffer = $ireq->client->writeBuffer;
     $client->serverRefClearer = ($ireq->client->exporter)($ireq->client);
     $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client]);
     $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]);
     $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]);
     $this->clients[$client->id] = $client;
     $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod;
     resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"]));
     return $client;
 }
Example #12
0
 public function do(InternalRequest $ireq)
 {
     $headers = yield;
     if ($headers[":status"] == 101) {
         $yield = (yield $headers);
     } else {
         return $headers;
         // detach if we don't want to establish websocket connection
     }
     while ($yield !== null) {
         $yield = (yield $yield);
     }
     \Amp\immediately(function () use($ireq) {
         $client = new Rfc6455Client();
         $client->connectedAt = $this->now;
         $socket = $ireq->client->socket;
         $client->id = (int) $socket;
         $client->socket = $socket;
         $client->writeBuffer = $ireq->client->writeBuffer;
         $client->serverRefClearer = ($ireq->client->exporter)($ireq->client);
         $client->parser = $this->parser([$this, "onParse"], $options = ["cb_data" => $client]);
         $client->readWatcher = \Amp\onReadable($socket, [$this, "onReadable"], $options = ["enable" => true, "cb_data" => $client]);
         $client->writeWatcher = \Amp\onWritable($socket, [$this, "onWritable"], $options = ["enable" => $client->writeBuffer != "", "cb_data" => $client]);
         $this->clients[$client->id] = $client;
         $this->heartbeatTimeouts[$client->id] = $this->now + $this->heartbeatPeriod;
         resolve($this->tryAppOnOpen($client->id, $ireq->locals["aerys.websocket"]));
     });
 }
Example #13
0
 /**
  * @return Promise
  */
 private function connect()
 {
     if ($this->promisor) {
         return $this->promisor->promise();
     }
     // Already connected
     if (is_resource($this->socket)) {
         return new Success();
     }
     $this->promisor = new \Amp\Deferred();
     /** @var $socketPromise Promise */
     $socketPromise = \Amp\Socket\connect($this->uri, ['timeout' => 1000]);
     $socketPromise->when(function ($error, $socket) {
         $promisor = $this->promisor;
         $this->promisor = null;
         $this->socket = $socket;
         $this->reader = \Amp\onReadable($this->socket, [$this, "onRead"]);
         $this->writer = \Amp\onWritable($this->socket, [$this, "onWrite"]);
         $promisor->succeed();
     });
     return $this->promisor->promise();
 }
Example #14
0
 private function loadNewServer($uri)
 {
     if (!($socket = @\stream_socket_client($uri, $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT))) {
         throw new ResolutionException(sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
     }
     \stream_set_blocking($socket, false);
     $id = (int) $socket;
     $server = new \StdClass();
     $server->id = $id;
     $server->uri = $uri;
     $server->socket = $socket;
     $server->buffer = "";
     $server->length = INF;
     $server->pendingRequests = [];
     $server->watcherId = \Amp\onReadable($socket, $this->makePrivateCallable("onReadable"), ["enable" => true, "keep_alive" => true]);
     $this->serverIdMap[$id] = $server;
     $this->serverUriMap[$uri] = $server;
     if (substr($uri, 0, 6) == "tcp://") {
         $promisor = new Deferred();
         $server->connect = $promisor->promise();
         $watcher = \Amp\onWritable($server->socket, static function ($watcher) use($server, $promisor, &$timer) {
             \Amp\cancel($watcher);
             \Amp\cancel($timer);
             unset($server->connect);
             $promisor->succeed();
         });
         $timer = \Amp\once(function () use($id, $promisor, $watcher, $uri) {
             \Amp\cancel($watcher);
             $this->unloadServer($id);
             $promisor->fail(new TimeoutException("Name resolution timed out, could not connect to server at {$uri}"));
         }, 5000);
     }
     return $server;
 }
Example #15
0
 /**
  * @param int $buffer one of the self::BUFFER_* constants. Determines whether it will buffer the stdout and/or stderr data internally
  * @return Promise is updated with ["out", $data] or ["err", $data] for data received on stdout or stderr
  * That Promise will be resolved to a stdClass object with stdout, stderr (when $buffer is true), exit (holding exit code) and signal (only present when terminated via signal) properties
  */
 public function exec($buffer = self::BUFFER_NONE)
 {
     if ($this->proc) {
         throw new \RuntimeException("Process was already launched");
     }
     $fds = [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]];
     $cwd = isset($this->options["cwd"]) ? $this->options["cwd"] : NULL;
     $env = isset($this->options["env"]) ? $this->options["env"] : NULL;
     if (!($this->proc = @proc_open($this->cmd, $fds, $pipes, $cwd, $env, $this->options))) {
         return new Failure(new \RuntimeException("Failed executing command: {$this->cmd}"));
     }
     $this->writeBuf = "";
     $this->writeTotal = 0;
     $this->writeCur = 0;
     stream_set_blocking($pipes[0], false);
     stream_set_blocking($pipes[1], false);
     stream_set_blocking($pipes[2], false);
     $this->deferred = new Deferred();
     $result = new \stdClass();
     if ($buffer & self::BUFFER_STDOUT) {
         $result->stdout = "";
     }
     if ($buffer & self::BUFFER_STDERR) {
         $result->stderr = "";
     }
     $this->stdout = \Amp\onReadable($pipes[1], function ($watcher, $sock) use($result) {
         if ("" == ($data = @fread($sock, 8192))) {
             \Amp\cancel($watcher);
             \Amp\cancel($this->stdin);
             \Amp\immediately(function () use($result) {
                 $status = proc_get_status($this->proc);
                 assert($status["running"] === false);
                 if ($status["signaled"]) {
                     $result->signal = $status["termsig"];
                 }
                 $result->exit = $status["exitcode"];
                 $this->proc = NULL;
                 $this->deferred->succeed($result);
                 foreach ($this->writeDeferreds as $deferred) {
                     $deferred->fail(new \Exception("Write could not be completed, process finished"));
                 }
                 $this->writeDeferreds = [];
             });
         } else {
             isset($result->stdout) && ($result->stdout .= $data);
             $this->deferred->update(["out", $data]);
         }
     });
     $this->stderr = \Amp\onReadable($pipes[2], function ($watcher, $sock) use($result) {
         if ("" == ($data = @fread($sock, 8192))) {
             \Amp\cancel($watcher);
         } else {
             isset($result->stderr) && ($result->stderr .= $data);
             $this->deferred->update(["err", $data]);
         }
     });
     $this->stdin = \Amp\onWritable($pipes[0], function ($watcher, $sock) {
         $this->writeCur += @fwrite($sock, $this->writeBuf);
         if ($this->writeCur == $this->writeTotal) {
             \Amp\disable($watcher);
         }
         while (($next = key($this->writeDeferreds)) !== null && $next <= $this->writeCur) {
             $this->writeDeferreds[$next]->succeed($this->writeCur);
             unset($this->writeDeferreds[$next]);
         }
     }, ["enable" => false]);
     return $this->deferred->promise();
 }
Example #16
0
function __doConnect($uri, array $options)
{
    $contextOptions = [];
    if (\stripos($uri, "unix://") === 0 || \stripos($uri, "udg://") === 0) {
        list($scheme, $path) = explode("://", $uri, 2);
        $isUnixSock = true;
        $resolvedUri = "{$scheme}:///" . \ltrim($path, "/");
    } else {
        $isUnixSock = false;
        // TCP/UDP host names are always case-insensitive
        if (!($uriParts = @\parse_url(strtolower($uri)))) {
            throw new \DomainException("Invalid URI: {$uri}");
        }
        // $scheme, $host, $port, $path
        \extract($uriParts);
        $scheme = empty($scheme) ? "tcp" : $scheme;
        if (!($scheme === "tcp" || $scheme === "udp")) {
            throw new \DomainException("Invalid URI scheme ({$scheme}); tcp, udp, unix or udg scheme expected");
        }
        if (empty($host) || empty($port)) {
            throw new \DomainException("Invalid URI ({$uri}); host and port components required");
        }
        if (PHP_VERSION_ID < 50600 && $scheme === "tcp") {
            // Prior to PHP 5.6 the SNI_server_name only registers if assigned to the stream
            // context at the time the socket is first connected (NOT with stream_socket_enable_crypto()).
            // So we always add the necessary ctx option here along with our own custom SNI_nb_hack
            // key to communicate our intent to the CryptoBroker if it's subsequently used
            $contextOptions = ["ssl" => ["SNI_server_name" => $host, "SNI_nb_hack" => true]];
        }
        if ($inAddr = @\inet_pton($host)) {
            $isIpv6 = isset($inAddr[15]);
        } else {
            $records = (yield \Amp\Dns\resolve($host));
            list($host, $mode) = $records[0];
            $isIpv6 = $mode === \Amp\Dns\Record::AAAA;
        }
        $resolvedUri = $isIpv6 ? "[{$host}]:{$port}" : "{$host}:{$port}";
    }
    $flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT;
    $timeout = 42;
    // <--- timeout not applicable for async connects
    $bindTo = empty($options["bind_to"]) ? "" : (string) $options["bind_to"];
    if (!$isUnixSock && $bindTo) {
        $contextOptions["socket"]["bindto"] = $bindTo;
    }
    $ctx = \stream_context_create($contextOptions);
    if (!($socket = @\stream_socket_client($resolvedUri, $errno, $errstr, $timeout, $flags, $ctx))) {
        throw new ConnectException(\sprintf("Connection to %s failed: [Error #%d] %s", $uri, $errno, $errstr));
    }
    \stream_set_blocking($socket, false);
    $promisor = new \Amp\Deferred();
    $promise = $promisor->promise();
    $watcherId = \Amp\onWritable($socket, [$promisor, "succeed"]);
    $timeout = empty($options["timeout"]) ? 30000 : $options["timeout"];
    try {
        (yield $timeout > 0 ? \Amp\timeout($promise, $timeout) : $promise);
        \Amp\cancel($watcherId);
        (yield new \Amp\CoroutineResult($socket));
    } catch (\Amp\TimeoutException $e) {
        \Amp\cancel($watcherId);
        throw new ConnectException("Connection to {$uri} failed: timeout exceeded ({$timeout} ms)", 0, $e);
    }
}
Example #17
0
 public function send($socket)
 {
     $deferred = new Deferred();
     stream_set_blocking($socket, false);
     $data = $this->getRequest();
     \Amp\onWritable($socket, function ($writer, $socket) use($deferred, &$data) {
         if ($bytes = fwrite($socket, $data)) {
             if ($bytes < \strlen($data)) {
                 $data = substr($data, $bytes);
                 return;
             }
             $data = '';
             \Amp\onReadable($socket, function ($reader, $socket) use($deferred, &$data) {
                 $data .= $bytes = fgets($socket);
                 if ($bytes == '' || \strlen($bytes) > 32768) {
                     \Amp\cancel($reader);
                     $deferred->succeed(null);
                 } elseif (substr($data, -4) == "\r\n\r\n") {
                     \Amp\cancel($reader);
                     $deferred->succeed($this->parseResponse($data));
                 }
             });
         } else {
             $deferred->succeed(null);
         }
         \Amp\cancel($writer);
     });
     return $deferred->promise();
 }