宝塔 配置 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

宝塔 配置 Nginx 配置导致接口的重复请求问题分析与解决

经过和 claude 24 小时深入交流,发现问题的根源在于 Nginx 配置中的 error_page 指令:
这些指令导致了以下问题:

  1. 错误页面重定向:当 Nginx 遇到 404 或 502 错误时,会进行内部重定向到指定的错误页面。
  2. POST 请求处理:对于 POST 请求,Nginx 在处理错误页面重定向时会将其转换为 GET 请求,这是因为 HTTP 规范建议重定向后不应自动重新发送 POST 数据。
  3. 双重请求:结果就是原始的 POST 请求被记录,随后是一个转换后的 GET 请求到错误页面。

因为我的 /api/login 接口本事因为配置原因会报错,导致 Nginx 遇到 404 或 502 错误时,会进行内部重定向到指定的错误页面,对于 POST 请求,Nginx 在处理错误页面重定向时会将其转换为 GET 请求,这就是为什么浏览器访问会导致重复提交问题,
这是就是宝塔的默认配置坑

有几种方法可以解决这个问题:

  1. 移除 error_page 指令: 最直接的方法是注释掉或删除这些指令:
    # error_page 404 /404.html;  
    # error_page 502 /502.html;
  1. 使用 = 修饰符: 如果确实需要自定义错误页面,可以使用 = 修饰符来防止 Nginx 进行内部重定向:
    error_page  404 =404 /404.html;  
    error_page  502 =502 /502.html;
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 1

哈哈哈哈,你这绕的比我远

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!