/**
  * Test if the VHost has successfully been initialized.
  *
  * @return void
  */
 public function testConstructor()
 {
     $vhost = new Vhost($name = 'foo.bar', $appBase = '/foo.bar', $aliases = array('www.foo.bar', 'test.foo.bar'));
     $this->assertSame($name, $vhost->getName());
     $this->assertSame($appBase, $vhost->getAppBase());
     $this->assertSame($aliases, $vhost->getAliases());
 }
예제 #2
0
 private function preventCryptoSocketConflict(Vhost $new)
 {
     foreach ($this->vhosts as $old) {
         // If both hosts are encrypted there is no conflict
         if ($new->IsEncrypted() && $old->isEncrypted()) {
             continue;
         }
         if ($new->matchesAddress($old->getAddress()) && $old->getPort() == $new->getPort()) {
             throw new \LogicException(sprintf("Cannot register encrypted host `%s`; unencrypted " . "host `%s` registered on conflicting port `%s`", $new->IsEncrypted() ? $new->getId() : $old->getId(), $new->IsEncrypted() ? $old->getId() : $new->getId(), $new->getAddress() . ":" . $new->getPort()));
         }
     }
 }
예제 #3
0
 private function addHttpDriver(Vhost $vhost)
 {
     $driver = $vhost->getHttpDriver() ?? $this->defaultHttpDriver;
     foreach ($vhost->getInterfaces() as list($address, $port)) {
         $generic = $this->httpDrivers[$port][\strlen(inet_pton($address)) === 4 ? "0.0.0.0" : "::"] ?? $driver;
         if (($this->httpDrivers[$port][$address] ?? $generic) !== $driver) {
             throw new \LogicException("Cannot use two different HttpDriver instances on an equivalent address-port pair");
         }
         if ($address == "0.0.0.0" || $address == "::") {
             foreach ($this->httpDrivers[$port] ?? [] as $oldAddr => $oldDriver) {
                 if ($oldDriver !== $driver && (\strlen(inet_pton($address)) === 4) == ($address == "0.0.0.0")) {
                     throw new \LogicException("Cannot use two different HttpDriver instances on an equivalent address-port pair");
                 }
             }
         }
         $this->httpDrivers[$port][$address] = $driver;
     }
     $hash = spl_object_hash($driver);
     if ($this->setupArgs && $this->setupHttpDrivers[$hash] ?? false) {
         $driver->setup(...$this->setupArgs);
         $this->setupHttpDrivers[$hash] = true;
     }
 }
예제 #4
0
 private function preventCryptoSocketConflict(Vhost $new)
 {
     foreach ($this->vhosts as $old) {
         // If both hosts are encrypted or both unencrypted there is no conflict
         if ($new->isEncrypted() == $old->isEncrypted()) {
             continue;
         }
         foreach ($old->getInterfaces() as list($address, $port)) {
             if (in_array($port, $new->getPorts($address))) {
                 throw new \LogicException(sprintf("Cannot register encrypted host `%s`; unencrypted " . "host `%s` registered on conflicting port `%s`", $new->IsEncrypted() ? $new->getName() : $old->getName() ?: "*", $new->IsEncrypted() ? $old->getName() : $new->getName() ?: "*", "{$address}:{$port}"));
             }
         }
     }
 }
예제 #5
0
class Bootstrapper
{
    private $hostAggregator;
    public function __construct(callable $hostAggregator = null)
    {
        $this->hostAggregator = $hostAggregator ?: ['\\Aerys\\Host', "getDefinitions"];
    }
    /**
     * Bootstrap a server from command line options
     *
     * @param \Aerys\Logger $logger
     * @param \Aerys\Console $console
     * @return \Generator
     */
    public function boot(Logger $logger, Console $console) : \Generator
    {
        $configFile = self::selectConfigFile((string) $console->getArg("config"));
        $logger->info("Using config file found at {$configFile}");
        // may return Promise or Generator for async I/O inside config file
        $returnValue = (include $configFile);
        if (!$returnValue) {
            throw new \DomainException("Config file inclusion failure: {$configFile}");
        }
        if (is_callable($returnValue)) {
            $returnValue = \call_user_func($returnValue);
        }
        if ($returnValue instanceof \Generator) {
            yield from $returnValue;
        } elseif ($returnValue instanceof Promise) {
            (yield $returnValue);
        }
        if (!defined("AERYS_OPTIONS")) {
            $options = [];
        } elseif (is_array(AERYS_OPTIONS)) {
            $options = AERYS_OPTIONS;
        } else {
            throw new \DomainException("Invalid AERYS_OPTIONS constant: expected array, got " . gettype(AERYS_OPTIONS));
        }
        if ($console->isArgDefined("debug")) {
            $options["debug"] = true;
        }
        $options["configPath"] = $configFile;
        $options = $this->generateOptionsObjFromArray($options);
        $vhosts = new VhostContainer();
        $ticker = new Ticker($logger);
        $server = new Server($options, $vhosts, $logger, $ticker, [new Http1Driver(), new Http2Driver()]);
        $bootLoader = function (Bootable $bootable) use($server, $logger) {
            $booted = $bootable->boot($server, $logger);
            if ($booted !== null && !$booted instanceof Middleware && !is_callable($booted)) {
                throw new \InvalidArgumentException("Any return value of " . get_class($bootable) . '::boot() must return an instance of Aerys\\Middleware and/or be callable, got ' . gettype($booted) . ".");
            }
            return $booted ?? $bootable;
        };
        $hosts = \call_user_func($this->hostAggregator) ?: [new Host()];
        foreach ($hosts as $host) {
            $vhost = self::buildVhost($host, $bootLoader);
            $vhosts->use($vhost);
        }
        return $server;
    }
    public static function selectConfigFile(string $configFile) : string
    {
        if ($configFile == "") {
            throw new \DomainException("No config file found, specify one via the -c switch on command line");
        }
        return realpath(is_dir($configFile) ? rtrim($configFile, "/") . "/config.php" : $configFile);
    }
    private function generateOptionsObjFromArray(array $optionsArray) : Options
    {
        try {
            $optionsObj = new Options();
            foreach ($optionsArray as $key => $value) {
                $optionsObj->{$key} = $value;
            }
            return $optionsObj->debug ? $optionsObj : $this->generatePublicOptionsStruct($optionsObj);
        } catch (\Throwable $e) {
            throw new \DomainException("Failed assigning options from config file", 0, $e);
        }
    }
    private function generatePublicOptionsStruct(Options $options) : Options
    {
        $code = "return new class extends \\Aerys\\Options {\n";
        foreach ((new \ReflectionClass($options))->getProperties() as $property) {
            $name = $property->getName();
            if ($name[0] !== "_") {
                $code .= "\tpublic \${$name};\n";
            }
        }
        $code .= "};\n";
        $publicOptions = eval($code);
        foreach ($publicOptions as $option => $value) {
            $publicOptions->{$option} = $options->{$option};
        }
        return $publicOptions;
    }
    private static function buildVhost(Host $host, callable $bootLoader) : Vhost
    {
        try {
            $hostExport = $host->export();
            $interfaces = $hostExport["interfaces"];
            $name = $hostExport["name"];
            $actions = $hostExport["actions"];
            $middlewares = [];
            $applications = [];
            foreach ($actions as $key => $action) {
                if ($action instanceof Bootable) {
                    $action = $bootLoader($action);
                }
                if ($action instanceof Middleware) {
                    $middlewares[] = [$action, "do"];
                } elseif (is_array($action) && $action[0] instanceof Middleware) {
                    $middlewares[] = [$action[0], "do"];
                }
                if (is_callable($action)) {
                    $applications[] = $action;
                }
            }
            if (empty($applications)) {
                $application = function (Request $request, Response $response) {
                    $response->end("<html><body><h1>It works!</h1></body></html>");
                };
            } elseif (count($applications) === 1) {
                $application = current($applications);
            } else {
                // Observe the Server in our stateful multi-responder so if a shutdown triggers
                // while we're iterating over our coroutines we can send a 503 response. This
                // obviates the need for applications to pay attention to server state themselves.
                $application = $bootLoader(new class($applications) implements Bootable, ServerObserver
                {
                    private $applications;
                    private $isStopping = false;
                    public function __construct(array $applications)
                    {
                        $this->applications = $applications;
                    }
                    public function boot(Server $server, Logger $logger)
                    {
                        $server->attach($this);
                    }
                    public function update(Server $server) : Promise
                    {
                        if ($server->state() === Server::STOPPING) {
                            $this->isStopping = true;
                        }
                        return new Success();
                    }
                    public function __invoke(Request $request, Response $response)
                    {
                        foreach ($this->applications as $action) {
                            $out = $action($request, $response);
                            if ($out instanceof \Generator) {
                                yield from $out;
                            }
                            if ($response->state() & Response::STARTED) {
                                return;
                            }
                            if ($this->isStopping) {
                                $response->setStatus(HTTP_STATUS["SERVICE_UNAVAILABLE"]);
                                $response->setReason("Server shutting down");
                                $response->setHeader("Aerys-Generic-Response", "enable");
                                $response->end();
                                return;
                            }
                        }
                    }
                    public function __debugInfo()
                    {
                        return ["applications" => $this->applications];
                    }
                });
            }
            $vhost = new Vhost($name, $interfaces, $application, $middlewares);
            if ($crypto = $hostExport["crypto"]) {
                $vhost->setCrypto($crypto);
            }
            return $vhost;
        } catch (\Throwable $previousException) {
            throw new \DomainException("Failed building Vhost instance", $code = 0, $previousException);
        }
    }
}
예제 #6
0
 private function buildVhost(Host $host, callable $bootLoader) : Vhost
 {
     try {
         $hostExport = $host->export();
         $address = $hostExport["address"];
         $port = $hostExport["port"];
         $name = $hostExport["name"];
         $actions = $hostExport["actions"];
         list($application, $middlewares) = $this->bootApplication($actions, $bootLoader);
         $vhost = new Vhost($name, $address, $port, $application, $middlewares);
         if ($crypto = $hostExport["crypto"]) {
             $vhost->setCrypto($crypto);
         }
         return $vhost;
     } catch (\Throwable $previousException) {
         throw new \DomainException("Failed building Vhost instance", $code = 0, $previousException);
     }
 }