Apache Mod Rewrite异常:POST请求先被301转为GET再执行307重写导致POST数据丢失API失效
最近遇到了一个头疼的Apache Mod Rewrite问题:所有POST请求在执行我自定义的重写规则前,居然被自动转成了GET请求,导致POST数据丢失,API直接罢工,连iOS客户端都崩溃了。折腾了两天才找到根源,分享出来给大家避避坑。
问题现象
我通过POST请求调用API地址:https://s1.****.com//I2amZMjAK6q8nmkuBY3TEK1jkqgZyQ4S/1.3/getclientid,结果发现POST数据完全丢失,API返回错误。查看服务器日志后发现,请求先被301重定向成了GET,之后才执行我配置的307重写规则,最终导致整个流程异常。
当前使用的.htaccess配置
我的.htaccess文件内容如下:
RewriteEngine On RewriteBase / RewriteCond %{ENV:REDIRECT_END} =1 RewriteRule ^ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule "^/?([\w]+)/([0-9\.]+)/assets/(.*)" "/assets/$1/$3" [L,E=END:1] RewriteRule ^verifycode/([a-zA-Z0-9_-]+)/?$ vercode.php?code=$1 [L] RewriteRule "^/?([\w]+)/([0-9\.]+)/(.*)$" "/clients/jsonrest.php/$1/$2/$3" [R=307]
日志分析
我开启了LogLevel alert rewrite:trace8查看重写日志,发现自定义的Rewrite规则匹配完全正常,但查看access.log时,记录却暴露了问题:
*.*.119.49 - - [09/Sep/2021:07:03:53 +0000] "POST /I2amZMjAK6q8nmkuBY3TEK1jkqgZyQ4S/1.3/getclientid HTTP/1.1" 301 283 "-" "BDSSport/1.06.07 (com.bizdev-strategy.container; build:2397; iOS 12.5.4) Alamofire/5.1.0" *.*.119.49 - - [09/Sep/2021:07:03:54 +0000] "GET /I2amZMjAK6q8nmkuBY3TEK1jkqgZyQ4S/1.3/getclientid HTTP/1.1" 307 306 "-" "BDSSport/1.06.07 (com.bizdev-strategy.container; build:2397; iOS 12.5.4) Alamofire/5.1.0" *.*.119.49 - - [09/Sep/2021:07:03:54 +0000] "GET /clients/jsonrest.php/I2amZMjAK6q8nmkuBY3TEK1jkqgZyQ4S/1.3/getclientid HTTP/1.1" 200 161 "-" "BDSSport/1.06.07 (com.bizdev-strategy.container; build:2397; iOS 12.5.4) Alamofire/5.1.0"
可以看到,最初的POST请求直接返回了301状态码,被客户端自动转为GET请求,之后才触发了我配置的307重定向,但此时POST数据已经丢失了。
排查过程
我花了整整一天多时间,反复排查重写日志、API代码和iOS客户端代码,甚至怀疑是Apache版本的问题(我的服务器用的是Apache/2.4.6,另一台测试服务器相同配置却完全正常)。最后才发现,问题居然出在HTTP转HTTPS的重定向配置上:iPhone应用里调用的是http://而非https://,而服务器上的HTTP转HTTPS重定向用了默认的301状态码,这会导致客户端自动将POST请求转为GET请求。
原因解释
Apache默认的HTTP转HTTPS重定向(比如用mod_alias的Redirect指令,或者未指定状态码的mod_rewrite规则)会返回301永久重定向,而根据HTTP规范,301重定向会让客户端将POST请求转为GET请求。这就导致我的自定义307重写规则(本来是保留POST方法的)完全没机会发挥作用,因为请求已经被提前转成GET了。
解决方法
把HTTP转HTTPS的重定向规则改成307临时重定向或者308永久重定向,这两个状态码都会保留原始请求方法(POST)。示例配置如下:
RewriteEngine On # 检查是否为HTTPS请求 RewriteCond %{HTTPS} off # 重定向到HTTPS,用307保留请求方法 RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=307]
如果需要永久重定向(比如确定不再支持HTTP),可以把R=307改成R=308,同样会保留POST方法。
总结
这次问题完全是人为失误导致的:客户端调用了HTTP地址,而服务器的HTTP转HTTPS重定向配置不正确,用了会改变请求方法的301状态码。折腾了两天才找到根源,希望我的经历能帮大家节省时间——遇到POST被转GET的情况,先检查是否有HTTP/HTTPS之间的重定向,别像我一样绕了大弯!
内容的提问来源于stack exchange,提问作者thefrankie

