HerokuではSSLの処理をロードバランサーで行っているので、Dyno上のLaravelアプリケーションはデフォルト設定ではhttpで動いていると判定されアセットのURLがhttpになってしまします。
そのまま表示すると以下のようにブラウザの警告が出てしまいます。
Herokuのロードバランサーまわりについては、公式ドキュメントのHTTP Routingで説明されています。
Laravelで、ロードバランサー経由のhttpsリクエストを判定するには、公式ドキュメント「HTTP Requests」の「Configuring Trusted Proxies」の「Trusting All Proxies」に記述されている通りApp\Http\Middlewar\TrustProxies
クラスのproxies
の値にロードバランサーのIPアドレスをセットします。
Herokuの場合、そもそもロードバランサーのIPアドレスは判らないのですし、Web DynoにはグローバルIPアドレスは割あたってない(はず)ので、すべてのプロキシからのリクエストを信頼する「*」をセットします。
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Fideloper\Proxy\TrustProxies as Middleware;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies = '*'; // <== これ
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}
実装としてはこれだけなのですが、少し仕組みを解説してみます。
ミドルウェアApp\Http\Middlewar\TrustProxies
は、Fideloper\Proxy\TrustProxies
を継承しています。
$proxies
に「*」をセットした場合は、setTrustedProxyIpAddressesToTheCallingIp
メソッドが呼ばれますが、このメソッドではリクエストのサーバー環境変数REMOTE_ADDR
をそのまま信頼されるアドレスとして保存します。
// https://github.com/fideloper/TrustedProxy/blob/master/src/TrustProxies.php
protected function setTrustedProxyIpAddresses(Request $request)
{
$trustedIps = $this->proxies ?: $this->config->get('trustedproxy.proxies');
// Trust any IP address that calls us
// `**` for backwards compatibility, but is deprecated
if ($trustedIps === '*' || $trustedIps === '**') {
return $this->setTrustedProxyIpAddressesToTheCallingIp($request);
}
// Support IPs addresses separated by comma
$trustedIps = is_string($trustedIps) ? array_map('trim', explode(',', $trustedIps)) : $trustedIps;
// Only trust specific IP addresses
if (is_array($trustedIps)) {
return $this->setTrustedProxyIpAddressesToSpecificIps($request, $trustedIps);
}
}
// 中略
private function setTrustedProxyIpAddressesToTheCallingIp(Request $request)
{
$request->setTrustedProxies([$request->server->get('REMOTE_ADDR')], $this->getTrustedHeaderNames());
}
続いてhttpsかどうかの判定ですが、LaravelのIlluminate\Http\Request
クラスのsecure
メソッドに実装されています。
secure
メソッドは、親クラスのSymfony\Component\HttpFoundation\Request
クラスのisSecure
メソッドを呼んでおり、まずisFromTrustedProxy
メソッドで信頼済みのプロキシであることを確認しています。このとき、上記の通り「*」の場合はミドルウェアTrustProxies
でリクエストが来たアドレスを信頼済みプロキシとしてセットしているので必ずtrueが返ります。
次にX-Forwarded-Proto
ヘッダーの値をチェックして、httpsぽい値がセットされていればtrueを返します。
Herokuのロードバランサーは、httpsからどうかの情報をX-Forwarded-Proto
ヘッダーに追加してバックエンドのWebアプリケーション サーバーに渡しているためtrueと判定されます。確認したところ値は「https」でした。
(他のロードバランサーでも一般的にX-Forwarded-Proto
を付与されるので、この設定はHeroku以外でも利用可能です)
// https://github.com/symfony/http-foundation/blob/master/Request.php#L1121
public function isSecure()
{
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);
}
あとは、Viewのヘルパー関数asset
からIlluminate\Routing\UrlGenerator
のasset
メソッドが呼ばれると’Symfony\Component\HttpFoundation\Requestクラスの
getSchemeメソッドが呼ばれ、その中で上記の
isSecure`メソッドが呼び出されるという流れになります。