为静态资源设置单独的 Laravel 路由文件,且不使用 Session 和 Cookie
一早上醒来就看到 Slack 通知,这可不是什么好兆头
一夜之间,我的 Redis 实例完全填满了,我们的 Redis 做两件事:
- 会话存储
- 缓存一些数据位,没有什么实质内容
我用 TablePlus 看看在 Redis 能发现什么。由于 Laravel 使用随机散列作为缓存密钥的一部分,而且有效载荷是经过编码/加密的,因此很难说清楚到底发生了什么。
但是,我可以看到有2个 Redis 数据库(db0
和 db1
)。检查 config/databases.php
文件时,我发现确实为 Redis 定义了两个相应的数据库。
# File config/databases.php
return [
// Things ommitted here...
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];
default
连接使用 db0
而 cache
连接使用 db1
。 事实证明,会话存储在 db0
中,而我们在代码中缓存的内容存储在 db1
。 默认数据库 db0
的密钥比缓存数据库多得多。
应用程序创建太多会话.
如何创建会话?
每个 web 请求(针对在 routes/web.xml
中定义的路由)都会创建一个会话(或者使用一个现有的会话)。Web 应用程序在创建会话时返回 cookie。Web 浏览器存储这些 cookie,并在发出额外的 Web 请求时将 cookie 发送回来。这使得我们的 web 应用程序可以知道哪个会话对于给定的用户是有效的。
如果浏览器没有在每个请求上返回一个 cookie,那么用户将无法继续登录。
基于 API 的会话不是这样工作的。每个会话都被创建,然后再每个请求中销毁——没有涉及到 cookie。相反,客户端需要在每个 web 请求上发送它的身份验证信息(通常是某种令牌)。
Redis 为何爆炸?
那么, 是什么导致我们的 Redis 实例在会话中爆炸呢?
动态生成的资产嵌入到其他人的网站上,我们有两个这样的例子:
- 我们的应用程序生成一个
.js
文件,其他人在他们的网站上嵌入这个文件。 - 同样的,我们的应用程序还生成了
.svg
图片。
这些路由在我们的 routes/web.php
中定义:
Route::get('/embed.js');
Route::get('/{project}/share.js');
你看出问题了吗?消费者把这些信息放到他们自己的网站上。每次有人访问他们的网站,我们的应用程序就会收到一个 HTTP 请求,用于获取文件或图片,这就创建了一个会话。
这意味着我们的客户网络流量也在我们的网络应用程序中创建会话!
如何减少会话创建
解决方案是确保我们不为特定路线创建会话。 说起来简单,但是我们如何做到这一点呢?
事实证明,Cookies和 Sessions 的创建都是在 Laravel 的中间件中完成的。这很好,因为我们可以控制应用到每个路由的中间件。
为了确保某些路由不会创建 Sessions/返回 Cookies,我喜欢创建一个具有不同中间件的单独路由文件。
要做到这一点,我们需要做一些事情:
- 创建一个新的
routes/static.php
文件 (名称是任意的) - 将中间件添加到
app/Http/Kernel.php
- 更新
app/Providers/RouteServiceProvider.php
加载我们的新的路由文件,并应用新的中间件。
新的路由文件非常简单——我们创建一个新文件并将路由定义移动到里面:
# 文件 routes/static.php`
# 从 routes/web.php 复制他们
Route::get('/embed.js');
Route::get('/{project}/share.js');
然后我们可以更新 Kernel.php
文件来创建一个新的中间件。我们可以复制 web
中间件,移除处理 cookies 和会话的中间件:
# app/Http/Kernel.php 文件
.
.
.
/**
* 应用路由中间件组
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'static' => [
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
.
.
.
我们创建了一个新的中间件组,命名为 static
。它类似于 API 中间件,但是我们没有限流。
最后,我们需要注册新的路由文件,并应用我们新的 static
中间件组。 我们将通过更新 RouteServiceProvider.php
来做到这一点:
# File app/Providers/RouteServiceProvider.php
# 省略……
/**
* 定义路由模型绑定、模式过滤器等。
*
* @return void
*/
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
Route::middleware('static')
->namespace($this->namespace)
->group(base_path('routes/static.php'));
});
}
# 省略……
RouteServiceProvider
注册每个路由文件,并确定它们的中间件。 这就是 routes/web.php
中的内容如何获取分配给它的 web
中间件组。
这也是我们创建自己的路由文件的原因——我们希望避免使用 web 中间件组,并且能够在需要时向新路由文件添加路由。
结果
结果是我们的两个 “静态” 路由 (一个返回动态生成的资源——一个 JS 文件和 一个 SVG 图片 ) 不在创建 Session 和 Cookies。
这允许我们的 Redis 实例恢复。会话过期后,从 Redis 中删除了它们。由于客户流量不再在会话存储中创建会话,所以 Redis 实例再也不会填满!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
最好的方式,静态资源不经过后端