Aerys\Vhost::setCrypto PHP Method

setCrypto() public method

Define TLS encryption settings for this host
public setCrypto ( array $tls ) : void
$tls array An array mapping TLS stream context values
return void
    public function setCrypto(array $tls)
    {
        if (!extension_loaded('openssl')) {
            throw new \LogicException("Cannot assign crypto settings in host `{$this}`; ext/openssl required");
        }
        $certPath = $tls['local_cert'];
        $certBase = basename($certPath);
        if (!($rawCert = @file_get_contents($certPath))) {
            throw new \RuntimeException("TLS certificate path `{$certPath}` could not be read in host `{$this}`");
        }
        if (!($cert = @openssl_x509_read($rawCert))) {
            throw new \RuntimeException("`{$certBase}` does not appear to be a valid X.509 certificate in host `{$this}`");
        }
        if (!isset($tls['local_pk']) && !preg_match("#-----BEGIN( [A-Z]+)? PRIVATE KEY-----#", $rawCert)) {
            throw new \RuntimeException("TLS certificate `{$certBase}` appears to be missing the private key in host " . "`{$this}`; encrypted hosts must concatenate their private key into the same " . "file with the public key and any intermediate CA certs or use the local_pk option.");
        }
        if (!($cert = openssl_x509_parse($cert))) {
            throw new \RuntimeException("Failed parsing X.509 certificate `{$certBase}` in host `{$this}`");
        }
        $names = $this->parseNamesFromTlsCertArray($cert);
        if (!in_array($this->name, $names)) {
            trigger_error("TLS certificate `{$certBase}` has no CN or SAN name match for host `{$this}`; " . "web browsers will not trust the validity of your certificate :(", E_USER_WARNING);
        }
        if (time() > $cert['validTo_time_t']) {
            date_default_timezone_set(@date_default_timezone_get());
            $expiration = date('Y-m-d', $cert['validTo_time_t']);
            trigger_error("TLS certificate `{$certBase}` for host `{$this}` expired {$expiration}; web " . "browsers will not trust the validity of your certificate :(", E_USER_WARNING);
        }
        if (isset($tls['crypto_method'])) {
            $tls = $this->normalizeTlsCryptoMethod($tls);
        }
        $tls = array_merge($this->tlsDefaults, $tls);
        $tls = array_filter($tls, function ($value) {
            return isset($value);
        });
        $this->tlsContextArr = $tls;
    }

Usage Example

Example #1
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 PsrLogger $logger
     * @param \Aerys\Console $console
     * @return \Generator
     */
    public function boot(PsrLogger $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 (array_key_exists("debug", $options)) {
            throw new \DomainException('AERYS_OPTIONS constant contains "debug" key; "debug" option is read-only and only settable to true via the -d command line option');
        }
        $options["debug"] = $console->isArgDefined("debug");
        $options["configPath"] = $configFile;
        return $this->init($logger, $options);
    }
    /**
     * Initializes the server directly without config file inclusion
     *
     * @param PsrLogger $logger
     * @param array $options Aerys options array
     * @return Server
     */
    public function init(PsrLogger $logger, array $options = []) : Server
    {
        if (!array_key_exists("debug", $options)) {
            $options["debug"] = false;
        }
        $options = $this->generateOptionsObjFromArray($options);
        $vhosts = new VhostContainer(new Http1Driver());
        $ticker = new Ticker($logger);
        $server = new Server($options, $vhosts, $logger, $ticker);
        $bootLoader = static 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 = [];
            $monitors = [];
            foreach ($actions as $key => $action) {
                if ($action instanceof Bootable) {
                    $action = $bootLoader($action);
                } elseif (is_array($action) && $action[0] instanceof Bootable) {
                    $bootLoader($action[0]);
                }
                if ($action instanceof Middleware) {
                    $middlewares[] = [$action, "do"];
                } elseif (is_array($action) && $action[0] instanceof Middleware) {
                    $middlewares[] = [$action[0], "do"];
                }
                if ($action instanceof Monitor) {
                    $monitors[get_class($action)][] = $action;
                } elseif (is_array($action) && $action[0] instanceof Monitor) {
                    $monitors[get_class($action[0])][] = $action[0];
                }
                if (is_callable($action)) {
                    $applications[] = $action;
                }
            }
            if (empty($applications)) {
                $application = static 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, PsrLogger $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, $monitors, $hostExport["httpdriver"]);
            if ($crypto = $hostExport["crypto"]) {
                $vhost->setCrypto($crypto);
            }
            return $vhost;
        } catch (\Throwable $previousException) {
            throw new \DomainException("Failed building Vhost instance", $code = 0, $previousException);
        }
    }
}