自定义路由文件
发布于 作者 Chris Fidao
有一天早上,我醒来看到 Slack 通知。这可不是什么好兆头。
一夜之间,我的 Redis 实例完全满了。我们使用 Redis 做两件事
- 会话存储
- 缓存一些数据,没什么实质性的
我使用 TablePlus 查看了 Redis 中的内容。有点难以判断发生了什么,因为 Laravel 使用随机哈希作为缓存键的一部分,并且有效负载是编码/加密的。
但是我可以看到有两个 Redis 数据库(db0
和 db1
)。检查 config/databases.php
文件后,我发现确实为 Redis 定义了两个相应的数据库。
# File config/databases.phpreturn [ // 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.php
中定义的路由)都会创建一个会话(或使用现有的会话)。Web 应用程序在创建会话时会返回一个 Cookie。Web 浏览器会存储这些 Cookie,并在进行额外的 Web 请求时将 Cookie 发送回服务器。这使我们的 Web 应用程序能够知道哪个会话对给定用户有效。
如果浏览器在每次请求时不返回 Cookie,那么用户将无法保持登录状态。
基于 API 的会话的工作方式不同。每个会话都会在每个 Web 请求中创建,然后销毁 - 不涉及 Cookie。相反,客户端需要在每次 Web 请求时发送其身份验证信息(通常是某种令牌)。
是什么导致 Redis 爆炸?
那么,是什么导致我们的 Redis 实例因会话而爆炸?
动态生成的资产,其他人将其嵌入到自己的网站上。我们有两个这样的情况
- 我们的应用程序生成了一个
.js
文件,其他人将其嵌入到他们的网站上 - 我们的应用程序还为相同的目的生成了
.svg
图像
这些路由是在我们的 routes/web.php
文件中定义的
Route::get('/embed.js');Route::get('/{project}/share.js');
你看到问题了吗?客户将这些内容放在他们自己的网站上。每当有人访问他们的网站时,都会向我们的应用程序发出 HTTP 请求以获取嵌入代码或 SVG,**并且这会创建一个会话**。
这意味着我们的客户的网络流量也会在我们的 Web 应用程序中创建会话!
如何减少会话创建
**解决方法是确保我们不会为某些路由创建会话。** 这么说很简单,但是我们该怎么做呢?
事实证明,Cookie 和会话的创建是在 Laravel 的中间件中完成的。这很好,因为我们可以控制为每个路由应用哪些中间件。
为了确保某些路由不会创建会话/返回 Cookie,我喜欢创建一个单独的路由文件,该文件具有不同的中间件堆栈。
为此,我们需要做几件事
- 创建一个新的
routes/static.php
文件(名称是任意的) - 在
app/Http/Kernel.php
中添加一个中间件堆栈 - 更新
app/Providers/RouteServiceProvider.php
以加载我们的新路由文件,并应用我们的新中间件堆栈
新路由文件很简单 - 我们创建一个新文件并将路由定义移动到其中
# File routes/static.php` # Move these from routes/web.phpRoute::get('/embed.js');Route::get('/{project}/share.js');
然后我们可以更新 Kernel.php
文件以创建一个新的中间件堆栈。我们可以复制 web
中间件堆栈并删除处理 Cookie 和会话的中间件
# File app/Http/Kernel.php # Items omitted here /** * The application's route middleware groups. * * @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,+ ], ]; # Items omitted here
我们创建了一个名为 static
的新中间件组。它类似于 API 中间件,但我们没有节流。
最后,我们需要注册新路由文件,并应用我们的新 static
中间件组。我们将通过更新 RouteServiceProvider.php
来完成此操作
# File app/Providers/RouteServiceProvider.php # Items omitted here /** * Define your route model bindings, pattern filters, etc. * * @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'));+ }); } # Items omitted here
RouteServiveProvider
注册每个路由文件,并确定其中间件。这就是 routes/web.php
中的每个内容如何获得分配给它的 web
中间件组。
这也是我们创建自己的路由文件的原因 - 我们想避免 web
中间件组,并且能够在需要时将路由添加到新的路由文件中。
结果
结果是,我们的两个“静态”路由(返回动态生成的资产 - JS 文件和 SVG)不再创建会话,也不再返回 Cookie。
这使我们的 Redis 实例得以恢复。随着会话的过期,它们从 Redis 中删除。由于我们的客户流量不再在我们的会话存储中创建会话,因此 Redis 实例再也没有满过!