11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel で https でサイトを表示しているのに、route() ヘルパーが http 始まりの URL を返す

Last updated at Posted at 2022-10-05

問題

サイト自体は https でアクセス出来ていて、.env の APP_URL にはちゃんと https 始まりの URL を設定している。
この状態で

route('admin.home') // ---> http://localhost/admin
config('app.url') // ---> https://localhost

と、なぜか route() ヘルパーのときだけ http になってしまい、Mixed Content が発生して正常に動作しなくなる。

config:cache とか Laravel の cache 機能は使っていない。
一応以下コマンドでクリアしてみても結果は変わらない。

$ php artisan cache:clear && php artisan view:clear && php artisan config:clear

route ヘルパーの処理を追って、何が原因で http 始まりの URL を返しているか調査

route() ヘルパーはどんなことをしていて、どこの情報を参照しているのか?を地道に追っていく。

vendor/laravel/framework/src/Illuminate/Foundation/helpers.php

    function route($name, $parameters = [], $absolute = true)
    {
        return app('url')->route($name, $parameters, $absolute);
    }

app('url') は Illuminate/Routing/UrlGenerator を返す。

vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php

    public function route($name, $parameters = [], $absolute = true)
    {
        if (! is_null($route = $this->routes->getByName($name))) {
            return $this->toRoute($route, $parameters, $absolute);
        }

        throw new RouteNotFoundException("Route [{$name}] not defined.");
    }

    public function toRoute($route, $parameters, $absolute)
    {
        $parameters = collect(Arr::wrap($parameters))->map(function ($value, $key) use ($route) {
            $value = $value instanceof UrlRoutable && $route->bindingFieldFor($key)
                    ? $value->{$route->bindingFieldFor($key)}
                    : $value;

            return function_exists('enum_exists') && $value instanceof BackedEnum
                ? $value->value
                : $value;
        })->all();

        return $this->routeUrl()->to(
            $route, $this->formatParameters($parameters), $absolute
        );
    }

$this->routeUrl() は Illuminate/Routing/RouteUrlGenerator を返す。

vendor/laravel/framework/src/Illuminate/Routing/RouteUrlGenerator.php

    public function to($route, $parameters = [], $absolute = false)
    {
        $domain = $this->getRouteDomain($route, $parameters);

        // First we will construct the entire URI including the root and query string. Once it
        // has been constructed, we'll make sure we don't have any missing parameters or we
        // will need to throw the exception to let the developers know one was not given.
        $uri = $this->addQueryString($this->url->format(
            $root = $this->replaceRootParameters($route, $domain, $parameters),
            $this->replaceRouteParameters($route->uri(), $parameters),
            $route
        ), $parameters);

        if (preg_match_all('/{(.*?)}/', $uri, $matchedMissingParameters)) {
            throw UrlGenerationException::forMissingParameters($route, $matchedMissingParameters[1]);
        }

        // Once we have ensured that there are no missing parameters in the URI we will encode
        // the URI and prepare it for returning to the developer. If the URI is supposed to
        // be absolute, we will return it as-is. Otherwise we will remove the URL's root.
        $uri = strtr(rawurlencode($uri), $this->dontEncode);

        if (! $absolute) {
            $uri = preg_replace('#^(//|[^/?])+#', '', $uri);

            if ($base = $this->request->getBaseUrl()) {
                $uri = preg_replace('#^'.$base.'#i', '', $uri);
            }

            return '/'.ltrim($uri, '/');
        }

        return $uri;
    }

$this->url は Illuminate/Routing/UrlGenerator を返す。

vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php

dump() でインスタンスの情報を確認したときに、ここで初めて http で始まっているアプリケーション URL を見つけた。
こいつが原因ぽい。

^ Illuminate\Routing\UrlGenerator {#198 ▼
   : 
  #cachedRoot: "http://localhost"

以下で cachedRoot が設定される。

    public function formatRoot($scheme, $root = null)
    {
        if (is_null($root)) {
            if (is_null($this->cachedRoot)) {
                $this->cachedRoot = $this->forcedRoot ?: $this->request->root();
            }

            $root = $this->cachedRoot;
        }

        $start = str_starts_with($root, 'http://') ? 'http://' : 'https://';

        return preg_replace('~'.$start.'~', $scheme, $root, 1);
    }

$this->request->root() を追う。

$this->forcedRoot // --> null
$this->request->root() // --> http://localhost/admin

$this->request->root() は Illuminate\Http\Request を返す。

vendor/laravel/framework/src/Illuminate/Http/Request.php

    public function root()
    {
        return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/');
    }

$this->getSchemeAndHttpHost() が http 始まりの URL だった。
親クラスの Symfony の Request クラス Symfony\Component\HttpFoundation\Request で定義されている。

vendor/symfony/http-foundation/Request.php

    public function getSchemeAndHttpHost(): string
    {
        return $this->getScheme().'://'.$this->getHttpHost();
    }

$this->getScheme() が http を返す。

    public function getScheme(): string
    {
        return $this->isSecure() ? 'https' : 'http';
    }

    public function isSecure(): bool
    {
        if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_X_FORWARDED_PROTO)) {
            return \in_array(strtolower($proto[0]), ['https', 'on', 'ssl', '1'], true);
        }

        $https = $this->server->get('HTTPS');

        return !empty($https) && 'off' !== strtolower($https);
    }

isSecure の後半の return 側。

$https = $this->server->get('HTTPS');

の値を最終的に返却している。

var_dump($_SERVER['HTTPS'] ?? 'undefined'); // --> undefined

$_SERVER['HTTPS'] が設定されていない。なんで??

$_SERVER['HTTPS'] については こちら

'HTTPS'
スクリプトが HTTPS プロトコルを通じて実行されている場合に 空でない値が設定されます。

ちなみにこの現象が発生しているのは お名前.com のレンサバ。
ほかのレンサバや VPS, AWS とかでは $_SERVER['HTTPS'] はきっちり on などを返す。

レンサバ側の大本の設定がなにか悪さをしていそう。
$_SERVER['HTTPS'] が設定されない原因は問い合わせたところ、
「設定されないからこっちで対応して」
といったシンプルな回答だった。

ググったらわかったけど、ロードバランサーを介して https -> http にリバースプロキシしている環境だとあるあるの事象みたい。

対応

どっちでも好きな方で。

A 案. .htaccess などで対応

  • .htaccess
    # $_SERVER['HTTPS'] が設定されない環境向け
    # ロードバランサーを介して https -> http にリバースプロキシしている環境とか
    SetEnvIf X-Forwarded-Proto https HTTPS=on

B 案. Laravel プログラム側で対応

  • app/Providers/AppServiceProvider.php
    public function boot()
    {
        // レンサバの設定と相性が悪く、https でアクセスしているにも関わらず $_SERVER['HTTPS] が設定されず、
        // Laravel, Symfony 側の処理で http 始まりの URL で処理されてしまう。
        // config('app.url') が https 始まりか?http 始まりか? をもとに強制的に scheme を設定する
        \Illuminate\Support\Facades\URL::forceScheme(\Str::startsWith(config('app.url'), 'https') ? 'https' : 'http');
11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?