private function enableWriteWatcher(HttpTunnelStruct $struct) { if ($struct->writeWatcher === null) { $struct->writeWatcher = \Amp\onWritable($struct->socket, function () use($struct) { $this->doWrite($struct); }); } }
private function enableWriteWatcher() { if (empty($this->writeWatcher)) { $this->writeWatcher = \Amp\onWritable($this->socket, function () { $this->doWrite(); }); } }
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); }); }
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]); }
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); }); }
/** * @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(); }
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; }
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(); }
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; }
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]); }
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; }
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"])); }); }
/** * @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(); }
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; }
/** * @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(); }
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); } }
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(); }