`rewrite_rules` 刷新后自定义端点 502 又变 404:我卡在 Nginx `try_files` 与 WordPress 重写的"夹心层"里两小时

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 45 浏览 0 回复

上周给插件加了个前端提交页,走自定义重写规则:/submit-idea/ 映射到插件模板。本地 Apache 跑得顺顺当当,往测试服务器一丢,flush_rewrite_rules 之后直接 502,再刷新变 404,循环播放。

先贴我当时自以为稳的代码:

// 注册端点
add_action( 'init', function() {
    add_rewrite_endpoint( 'submit-idea', EP_ROOT );
    add_rewrite_rule( '^submit-idea/?$', 'index.php?submit_idea=1', 'top' );
} );

// 查询变量白名单
add_filter( 'query_vars', function( $vars ) {
    $vars[] = 'submit_idea';
    return $vars;
} );

// 模板劫持
add_action( 'template_redirect', function() {
    if ( get_query_var( 'submit_idea' ) ) {
        include plugin_dir_path( __FILE__ ) . 'templates/submit-idea.php';
        exit;
    }
} );

Apache 下 template_redirect 正常触发,Nginx 下直接进虚空。排查路径如下,每一步都是泪:

第一层:伪静态规则"假传圣旨"

测试环境 Nginx 配置是从某面板一键拉的,location / 里写的是:

try_files $uri $uri/ /index.php?$args;

看起来标准,但问题在 $args 不带原始查询串的重组。更坑的是,面板为了"兼容",偷偷在 index.php 后面又套了一层 if (!-e $request_filename) 的 legacy 规则,导致 WordPress 的 $_SERVER['REQUEST_URI'] 被改写,parse_request 阶段匹配不到 submit-idea

验证方法:在插件里临时 error_log( $_SERVER['REQUEST_URI'] );,发现请求 /submit-idea/ 时 URI 变成了 /index.php,原始路径丢了。

修正后的 Nginx 片段:

location / {
    try_files $uri $uri/ /index.php$is_args$args;
}

注意 $is_args$args? 的区别,前者在没参数时不挂空问号,避免 $_SERVER['QUERY_STRING'] 出现脏数据。

第二层:flush_rewrite_rules 的"延时生效"陷阱

我代码里在 init 钩子直接调了 flush_rewrite_rules( true ),每次请求都写数据库。Nginx 环境下这玩意会引发 race:规则还没落库,下一个请求已经进来,wp_rewrite 用的还是内存里的旧规则数组。

更隐蔽的是,如果服务器开了 object cache(Redis),rewrite_rules option 可能被缓存层拦截,数据库里明明更新了,WP_Rewrite::wp_rewrite_rules() 读到的却是缓存版本。

我现在改成"懒刷新"模式,只在激活/升级时触发:

register_activation_hook( __FILE__, function() {
    // 先注册规则,再刷新
    my_plugin_add_rewrite_rules();
    flush_rewrite_rules( true );
} );

register_deactivation_hook( __FILE__, function() {
    flush_rewrite_rules( true );
} );

// 日常 init 只注册,不刷新
add_action( 'init', 'my_plugin_add_rewrite_rules', 10, 0 );

第三层:EP_ROOTadd_rewrite_rule 的"叠床架屋"

回头看我的代码,同时用了 add_rewrite_endpointadd_rewrite_rule,这俩在 $wp_rewrite->endpoints$wp_rewrite->extra_rules_top 里各占一条。Apache mod_rewrite 对冗余规则容忍度高,Nginx 的 try_files fallback 路径短,先匹配到 endpoint 的 submit_idea=(注意等号后面空值),get_query_var 返回空字符串,template_redirect 里的判断 if ( get_query_var( 'submit_idea' ) ) 被短路。

修正:二选一,我保留了 add_rewrite_rule,干掉 endpoint。或者如果必须用 endpoint,判断条件改成 if ( '' !== get_query_var( 'submit_idea' ) ),但这样语义别扭。

第四层:template_redirect 的触发时机盲区

即使前面都对了,Nginx + PHP-FPM 环境下,如果某个上游插件在 parse_requestsend_headers 阶段调了 wp_die()exittemplate_redirect 会被跳过。我最后加了个兜底,在 wp 钩子也挂一份检查:

add_action( 'wp', function() {
    if ( get_query_var( 'submit_idea' ) && ! did_action( 'template_redirect' ) ) {
        // 日志告警,说明有东西提前截断了流程
        do_action( 'my_plugin_template_redirect_missed' );
        include plugin_dir_path( __FILE__ ) . 'templates/submit-idea.php';
        exit;
    }
} );

总结 checklist,给同样踩坑的:

  1. Nginx try_files 必须带 $is_args$args,检查是否有 legacy if 规则干扰
  2. flush_rewrite_rules 绝不放在高频钩子,激活时一次刷新,日常靠 init 注册
  3. object cache 环境下,刷新后手动踢缓存或加版本号强制失效
  4. endpoint 和 rewrite_rule 别混用,查询变量判断要区分 false / '' / 0
  5. 核心钩子被截断时,wp 是最后的"救生舱"

最后吐槽:Apache 的容错像海绵,Nginx 的严格像钢板,插件跨服务器部署时,重写规则这趴最好写进安装检测脚本,自动报环境差异。

评论0
回复 · 0
还没有回复
微信客服 微信客服