Laravel 5 在 AJAX 密集型应用程序中丢失会话和 .env 配置值

Posted

技术标签:

【中文标题】Laravel 5 在 AJAX 密集型应用程序中丢失会话和 .env 配置值【英文标题】:Laravel 5 losing sessions and .env configuration values in AJAX-intensive applications 【发布时间】:2015-09-26 12:12:30 【问题描述】:

我正在使用 Laravel 5(具体来说,“laravel/framework”版本是“v5.0.27”),会话驱动程序 = 'file'。

我正在 Windows 7 64 位机器上开发。

我注意到有时(大约一周一次)我会意外地随机退出。有时甚至在我登录后立即发生这种情况。我已将日志消息添加到我的身份验证逻辑代码中,但未触发日志代码。 Laravel 表现得好像完全丢失了会话文件。

另一个更严重的问题是,有时在调试会话后(使用 xdebug 和 Netbeans),Laravel 开始丢失其他文件 - .env 设置、一些调试栏 JS 文件等。错误日志包含如下消息:

[2015-07-08 13:05:31] local.ERROR: exception 'ErrorException' with message 'mcrypt_encrypt(): Key of size 7 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported' in D:\myproject\vendor\laravel\framework\src\Illuminate\Encryption\Encrypter.php:81
[2015-07-08 13:05:31] local.ERROR: exception 'PDOException' with message 'SQLSTATE[HY000] [1044] Access denied for user ''@'localhost' to database 'forge'' in D:\myproject\vendor\laravel\framework\src\Illuminate\Database\Connectors\Connector.php:47

这清楚地表明 .env 文件未被 Laravel 读取,因此它使用默认设置:

'database'  => env('DB_DATABASE', 'forge'),
'key' => env('APP_KEY', 'somekey'),

文件丢失很少发生,可能一个月左右一次,而且总是在调试会话之后发生。我总是必须重新启动 Apache 才能让它再次工作。

为了对系统进行压力测试并可靠地重现问题,我在我的 Angular 控制器中使用了一个快速破解:

setInterval(function()
    $scope.getGridPagedDataAsync();
, 500);

这只是从 Angular 到 Laravel 的一个基本数据请求。

就是这样 - 现在我可以在 3 分钟或更短的时间内重现会话丢失和 .env 丢失。

我之前在同一台 PC 上使用相同的 Apache+PHP 开发了 AJAX 密集型 Web 应用程序,但没有 Laravel,没有 .env,而且我之前没有注意到此类问题。

通过代码调试时,我发现 Laravel 根本没有使用 PHP 内置会话,而是实现了自己的基于文件的会话。显然,它没有提供与默认 PHP 会话相同的可靠性,我不知道为什么。

当然,在现实生活场景中,我的应用不会是 AJAX 密集型的,但根据我的经验,在某些情况下,只需两个同时发生的 AJAX 请求就足以丢失会话。

我在 Laravel 上看到了一些有关各种会话问题的相关错误报告。不过,我还没有看到任何关于 dot-env 的信息,但它似乎遇到了同样的问题。

我的猜测是 Laravel 不使用文件锁定和等待,因此如果由于某种原因(可能被某些并行进程或 Apache 锁定)无法读取文件,那么 Laravel 就会放弃并返回它可以返回的任何内容。

有什么好的解决办法吗?也许它是特定于 Windows 的,并且问题会在 Linux 机器上消失?

很好奇,为什么 Laravel(或 Symfony)开发人员还没有修复他们的会话文件驱动程序。我知道锁定/等待会减慢速度,但至少有一些选项可以打开“可靠会话”会很棒。

同时我会尝试单步调试 Laravel 代码,看看我是否可以发明一些“快速而肮脏”的解决方案,但最好有一些可靠的“最佳实践”解决方案。

关于 .env 的更新

问题转为与锁定文件无关。我找到了 .env 问题的 Laravel 错误报告,这使我找到了 Dotenv 项目的链接报告,该报告反过来说这是一个核心 PHP 问题。令我不安的是,Dotenv 开发人员说 Dotenv 从未打算用于生产,但 Laravel 似乎依赖于 Dotenv。

在https://github.com/laravel/framework/pull/8187 中,似乎有一个解决方案应该朝一个方向工作,但一些评论者说,在他们的情况下,问题恰恰相反。有人叫 crynobone 给出了一个聪明的代码 sn-p 来尝试:

$value = array_get($_ENV, $key, getenv($key));

在 Dotenv 和 Laravel Githubs 上出现了另一个使用“makeMutable()”的建议,但评论者报告说这可能会破坏测试。

所以我尝试了 crynobone 的代码,但它对我不起作用。在调试时,我发现在我的情况下,当并发请求发生故障时,$key 既无法在 getenv() 中找到,也无法在 $_ENV 中找到,甚至在 $_SERVER 中也找不到。 唯一有效(快速而肮脏的实验)是添加:

静态::$cached[$name] = $value;

到 Dotenv 类,然后在 helpers.php env() 方法中我看到:

Dotenv::$cached[$key]

总是好的,即使 $_ENV 和 getenv 都没有提供任何东西。

虽然 Dotenv 不适合生产,但我不想更改我们的部署和配置工作流程。

接下来我必须调查会话问题...


附录

相关的 Laravel 错误报告(有些甚至来自第 4 版。而且似乎没有修复): https://github.com/laravel/framework/issues/4576

https://github.com/laravel/framework/issues/5416

https://github.com/laravel/framework/issues/8172

还有一篇旧文章阐明了正在发生的事情(至少与会话问题有关): http://thwartedefforts.org/2006/11/11/race-conditions-with-ajax-and-php-sessions/

【问题讨论】:

如果一次抛出 100 个非 ajax 请求会发生什么? @itachi:我刚刚运行了ab -n 10000 -c 100 http://mysite/,并且在 laravel.log 文件中,除了“超过 30 秒的最大执行时间”消息之外,我看到了很多 mcrypt_encryptSQLSTATE[HY000] 消息,这意味着对于许多并发的非 AJAX 请求,该 .env 文件也没有正确加载。 这些原来是两个不同的问题 - .env 实际上与锁定文件无关。我找到了 .env 问题的 Laravel 错误报告,这使我找到了 Dotenv 项目的链接报告,该报告反过来说这是一个核心 PHP 问题。令我不安的是,Dotenv 开发人员说 Dotenv 从未打算用于生产,但 Laravel 似乎将 Dotenv 紧密集成到所有环境配置中。所以,我想“解决方案”是为我们的 Laravel 应用程序禁用 Dotenv。 我正在使用 laravel5.1 开发 angularjs 应用程序,如果我连续发送许多请求..它会引发 401 异常。我也在使用 windows 和 Apache 服务器 @Muhammed Shihabuddeen,是的,这可能是由我正在谈论的会话问题引起的。看来,我设法解决了 Dotenv 问题(请参阅此处的最后一条评论:github.com/vlucas/phpdotenv/issues/76),明天我会看看是否可以对会话问题做任何事情。 【参考方案1】:

经过两天的密集调试,我有一些可能对其他人有用的解决方法:

这是我为 Dotenv 1.1.0 和 Laravel 5.0.27 修复 .env 问题的补丁: https://gist.github.com/progmars/db5b8e2331e8723dd637

这是我的解决方法补丁,可以减少会话问题的频率(或者完全修复它们,如果您没有在每次请求时都自己写入会话): https://gist.github.com/progmars/960b848170ff4ecd580a

我使用 Laravel 5.0.27 和 Dotenv 1.1.0 对它们进行了测试。

最近还为 Laravel 5.1.1 和 Dotenv 1.1.1 重新创建了补丁: https://gist.github.com/progmars/e750f46c8c12e21a10ea https://gist.github.com/progmars/76598c982179bc335ebb

确保添加

'metadata_update_threshold' => 1,

到您的 config/session.php 以使此补丁生效。

每次重新创建时,应将所有补丁应用到vendor 文件夹。

此外,您可能希望将会话补丁分开,因为您只更新了一次 session.php,但补丁的其他部分应在每次在部署前重新创建时应用到 vendor 文件夹。

请注意:“它适用于我的机器”。我真的希望 Laravel 和 Dotenv 的开发者能想出更好的东西,但同时我可以忍受这些修复。

【讨论】:

我也遇到了这些问题,并开始编写自己的脚本来读取 bootstrap/app.php 中的 .env 文件内容,我的所有问题都消失了,包括会话问题。我在这个问题上的主题:***.com/questions/37023633/… 在挣扎太多之后,我决定完全远离 dotenv。我还按照插件 + Laravel 的加载顺序坐了很多时间,并决定创建自己的包也可能会出现问题,所以我决定直接“硬编码”到 app.php @Wancieho :实际上,从 Laravel 5.2 开始,他们已经可以在生产环境中使用 config:cache 完全避免 Dotenv。这个选项在 L5.2 之前也可用,但在 Laravel 中仍然有一些使用 Dotenv 的代码。现在它消失了。但是,在本地开发并且没有缓存配置时,这并没有多大帮助 - 在这种情况下,仍然需要修复。【参考方案2】:

我个人认为使用 .env 配置 Laravel 是一个错误的决定。拥有包含 key:value 配置风格的 .php 文件要好得多。

但是,您遇到的问题不是 PHP 的问题,也不是 Apache 的问题 - 很可能是 Windows 问题。

其他一些东西:Apache 包含一个模块,允许将 PHP 二进制文件集成到 Apache 的进程或线程中,称为 mod_php - 问题在于 PHP 不仅速度慢,而且将另一个二进制文件集成到现有的一个非常棘手,可能会错过一些事情。在这种情况下,PHP 还必须使用线程安全构建。如果不是,那么奇怪的错误可能(并且将会)发生。

为了避免将一个程序复杂地集成到另一个程序中的问题,我们可以完全避免这种情况,我们可以通过 FastCGI 协议提供.php。这意味着网络服务器(Apache 或 nginx)将接受 HTTP 请求并将其传递给另一个“网络”服务器。在我们的例子中,这将是 PHP FastCGI 进程管理器或PHP-FPM

PHP-FPM 是服务.php 页面的首选方式——不仅因为它更快(比通过mod_php 集成要快得多),而且您可以轻松扩展您的HTTP 前端并让多台机器服务.php页面,让您可以轻松水平扩展 HTTP 前端。

然而,PHP-FPM 是一个叫做监督进程的东西,它依赖于进程控制。据我所知,Windows 不像 *nix 那样支持进程控制,因此 php-fpm 不适用于 Windows(如果我在这里错了,请纠正我)。

这一切对你意味着什么?这意味着您应该使用专为配合您想做的事情而设计的软件。 这是应该遵循的逻辑:

Web 服务器接受 HTTP 请求(Apache 或 Nginx) Web 服务器验证请求,解析原始 HTTP 请求,确定请求是否太大,如果在这种情况下一切正常,它将请求代理到 php-fpmphp-fpm 处理请求(在您的情况下它会启动 Laravel)并返回 Web 服务器向用户显示的 html

现在,这个过程虽然很棒,但也带来了一些问题,其中一个大问题是 PHP 如何处理会话。默认 PHP 会话是存储在服务器某处的文件。这意味着,如果您有 2 台物理机为您的php-fpm 服务,您将遇到会话问题。这就是 Laravel 做得很好的地方——它允许你使用基于 cookie 的加密会话。它有一些限制(你不能在这些会话中存储资源并且你有大小限制),但是一个正确构建的应用程序首先不会在会话中存储太多信息。当然,有多种处理会话的方法,但在我看来,加密的 cookie 是超级、超级易用且功能强大的。当使用这样的 cookie 时,携带会话信息的是客户端,任何包含解密密钥的机器都可以读取此会话,这意味着您可以轻松地将设置扩展到多个服务器 - 他们所要做的就是访问相同的解密密钥(它是.env 中的APP_KEY)。基本上,您需要将相同的 Laravel 安装复制到您希望为您的项目服务的机器上。

我将处理您在开发过程中遇到的问题的方式如下:

使用虚拟机(比如 Oracle Virtualbox) 安装 Ubuntu 14.04 将网络驱动器映射到您的 Windows 主机(使用 Samba) 使用您喜欢的方式编辑 PHP 文件,但它们将存储在映射驱动器上 在 VM 上启动 nginx 或 Apache 以及 php-fpm 以服务于您的项目

现在您通过此过程获得的好处是:您不会使用侦听端口 80 / 443 的程序污染您的 Windows 机器,当您完成工作后,您可以关闭虚拟机而不会丢失工作,您还可以轻松模拟您的网站在实际生产机器上的行为,并且您不会有诸如“它在我的开发机器上运行但在我的生产机器上不起作用”之类的惊喜,因为您将拥有相同的软件两个目的。

这些是我的意见,它们并不都是冷冰冰的事实,我在这里写的内容应该持保留态度。如果您认为我写的内容可能对您有所帮助,那么请尝试以这种方式解决问题。如果没有,好吧,没有难过的感觉,我祝你的项目好运。

【讨论】:

感谢您的深入指导,我们一定会尝试此 VM 设置。现在我最终修补 Dotenv 以在 getenv 为空时使用内存中的 key:value 数组作为后备,现在它似乎工作正常。 我可以肯定地确认,不使用 mod_php 并使用 FastCGI 似乎已经解决了这个问题。 @MarkW - 感谢您的确认,我很高兴它有所帮助。

以上是关于Laravel 5 在 AJAX 密集型应用程序中丢失会话和 .env 配置值的主要内容,如果未能解决你的问题,请参考以下文章

CSRF 令牌在带有 Ajax 请求的 Laravel 5.1 异常处理程序中不起作用

无法通过 jquery Ajax 在 laravel 5 中发布

laravel 5中多个异步ajax请求中的CSRF令牌不匹配错误?

如何在laravel 5.2中控制多个ajax请求

Laravel 5 异步 AJAX 请求导致会话问题

在 Ajax 调用中传递完整 URL 作为参数 - Laravel 5.1 路由