宝塔 配置 Nginx 配置导致接口的重复请求问题分析与解决
请几天给客户在 宝塔上面配置一个 laravel 项目,使用浏览器访问提交登录接口发现laravel 报错:
POST /api/login HTTP/1.1
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Content-Length: 42
Content-Type: application/json
Cookie: XSRF-TOKEN=eyjoiIn0%3D; laravel_session=eyJpdiI6IlRMRUNpIiwidGFnIjoiIn0%3D
Host: xxxxxx.com
Origin: xxxxxx.com
Proxy-Connection: keep-alive
Referer: xxxxxx.com
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Mobile Safari/537.36 Edg/127.0.0.0
X-Requested-With: XMLHttpRequest
"message": "The GET method is not supported for route api/login. Supported methods: POST.",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException",
"file": "/www/wwwroot/example-apple/vendor/laravel/framework/src/Illuminate/Routing/AbstractRouteCollection.php",
看到这问题因为是 路由配置问题,于是打开路由文件:
Route::middleware(UnauthorizedMiddleware::class)->group(function (){
...
Route::post('/api/login', 'App\Http\Controllers\AuthController@login');
});
发现路由定义是存在的,
使用 curl 模拟post 请求:
curl -X POST https://xxx.com/api/login
{"code":500,"message":"\u8d26\u53f7\u6216\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a"}
接口并没有报 MethodNotAllowedHttpException 异常,怀疑是nginx 和 php 通信问题
在 Laravel 的 public/index.php
文件的开头添加以下代码:
...
file_put_contents('/tmp/request_debug.log',
"Time: " . date('Y-m-d H:i:s') . "\n" .
"Method: " . $_SERVER['REQUEST_METHOD'] . "\n" .
"URI: " . $_SERVER['REQUEST_URI'] . "\n" .
"Content Type: " . ($_SERVER['CONTENT_TYPE'] ?? 'Not Set') . "\n" .
"Content Length: " . ($_SERVER['CONTENT_LENGTH'] ?? 'Not Set') . "\n" .
"Raw POST Data: " . file_get_contents('php://input') . "\n\n",
FILE_APPEND
);
再次使用浏览器提交接口,查看 日志文件:
Time: 2024-07-29 23:29:40
Method: POST
URI: /api/login
Content Type: application/json
Content Length: 41
Raw POST Data: {"accountName":"1111","password":"23243"}
Time: 2024-07-29 23:29:41
Method: GET
URI: /api/login
Content Type: application/json
Content Length: 41
Raw POST Data: {"accountName":"1111","password":"23243"}
从输出看到 浏览器导致的重复请求并且将 post 方法重定向为 get 方法,并且查看 laravel 的日志文件也没有什么输出
这可能是 nginx 配置问题,下面是nginx 配置:
server
{
listen 80;
server_name xxxxxx.com;
index index.php;
root /www/wwwroot/example-apple/public;
#CERT-APPLY-CHECK--START
# 用于SSL证书申请时的文件验证相关配置 -- 请勿删除
include /www/server/panel/vhost/nginx/well-known/xxxxxx.com.conf;
# CERT-APPLY-CHECK--END
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
error_page 404/404.html;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-83.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/xxxxxx.com.conf;
#REWRITE-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
# 一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/xxxxxx.com.log;
error_log /www/wwwlogs/xxxxxx.com.error.log;
}
这是宝塔在创建目录后生成的站点配置文件,我看也没有什么问题啊,还是丢给 claude kankan
经过和 claude 24 小时深入交流,发现问题的根源在于 Nginx 配置中的 error_page
指令:
这些指令导致了以下问题:
- 错误页面重定向:当 Nginx 遇到 404 或 502 错误时,会进行内部重定向到指定的错误页面。
- POST 请求处理:对于 POST 请求,Nginx 在处理错误页面重定向时会将其转换为 GET 请求,这是因为 HTTP 规范建议重定向后不应自动重新发送 POST 数据。
- 双重请求:结果就是原始的 POST 请求被记录,随后是一个转换后的 GET 请求到错误页面。
因为我的 /api/login 接口本事因为配置原因会报错,导致 Nginx 遇到 404 或 502 错误时,会进行内部重定向到指定的错误页面,对于 POST 请求,Nginx 在处理错误页面重定向时会将其转换为 GET 请求,这就是为什么浏览器访问会导致重复提交问题,
这是就是宝塔的默认配置坑
有几种方法可以解决这个问题:
- 移除 error_page 指令: 最直接的方法是注释掉或删除这些指令:
# error_page 404 /404.html; # error_page 502 /502.html;
- 使用
=
修饰符: 如果确实需要自定义错误页面,可以使用=
修饰符来防止 Nginx 进行内部重定向:error_page 404 =404 /404.html; error_page 502 =502 /502.html;
本作品采用《CC 协议》,转载必须注明作者和本文链接
哈哈哈哈,你这绕的比我远