从微信公众号跳转微信小程序时,如果传入的参数过长会导致填写“小程序路径”选项无法填写,这时需要实现类似“短链接”的功能,让前端获取保存的信息、解析、重定向到目的页。
这个需求与短链接中心有很大的相似之处,除微信小程序之外的 Web 应用也可以使用,只要在生成短链接时传入是否需要重定向的参数即可。需要注意由于 HTTP 标准和浏览器的历史设计的原因,重定向使用的 HTTP 状态码需要酌情选择。
浏览器重定向的原理和由来
当用户使用浏览器访问网页时,网页重定向实现是由 HTTP Header 中的 Location 参数决定的,如果服务器返回的 HTTP Header 中带有 Location 参数,那么浏览器就会根据这个参数跳转到新的 URL 上,同时根据本次 HTTP 请求响应的状态码决定是否将本次的重定向缓存。
HTTP/1.0 引入了 HTTP 301 和 HTTP 302 的状态码,是最早使用的重定向状态码。其中 HTTP 301 属于永久重定向,浏览器收到这次重定向请求之后会将 Location 中的重定向 URL 缓存,只要用户不清理浏览器缓存,后续访问该 URL 时浏览器会直接跳转到最终地址,不向服务器发出请求。而 HTTP 302 属于临时重定向,浏览器第一次访问到相关 URL 后执行重定向转发,但在后续的访问中仍然会向服务器请求,得到响应后再决定是否执行跳转。
另外还有 HTTP 303 这个状态码,表示当前请求的 URL 存在着另一个请求结果,重定向后请求方法会变为 GET,且浏览器不会缓存下这次重定向的结果。在早期 Web 开发中常用的 form 表单提交,form 表单提交成功后重定向回来用于刷新页面。由于 Ajax 异步请求和前端工程化的原因,form 表单提交配合 303 状态码的使用在近些年几乎不常见了。
不过 HTTP 301、 HTTP 302 状态码的重定向在不同的浏览器中的效果却有所区别,不同的浏览器对重定向后的请求方法实现不一致,可能会出现原始使用 POST 请求,但在重定向跳转后却变成了 GET 请求的情况,具体取决于浏览器的代码实现,按设计之初的意图重定向跳转后的请求方式是不应该被改变的,否则这就让 HTTP 303 状态码失去了意义,而且在一些接口规定请求方式的特殊情况下还可能导致用户的请求被服务器返回错误信息。
为了应对浏览器错误的重定向跳转行为,在 1999 年 6 月 标准化的 HTTP/1.1 ( RFC 2616 )出现了 HTTP 307 状态码,用于标准化 HTTP 302 状态码下的临时重定向。在 2014 年 6 月时 RFC7238、RFC7538 标准为 HTTP/1.1 补充了 HTTP 308 状态码,用于标准化 HTTP 301 状态码下的永久重定向,这个标准规定了服务器响应 HTTP 308 状态码时浏览器重定向后的请求方法需要与原始的请求方法一致。由于 HTTP 308 出现的太晚,在较老的浏览器下可能不受支持,存在兼容性问题。
总结:HTTP 301、308 是永久重定向,跳转一次后浏览器会缓存重定向结果,但在部分浏览器下可能会将 HTTP 301 状态码的重定向在跳转后将请求方式转为 GET。而 HTTP 302、303、307是临时重定向,不缓存重定向结果,在部分浏览器下可能会将 HTTP 302、303 状态码的重定向在跳转后将请求方式转为 GET。使用 HTTP 301、302、303 时要注意尽量将重定向后的 URL 设置为支持 GET 请求方式访问。
Java 中关于重定向的实现
在 Spring Web 的工程中开发,可以直接使用 JDK 自带的 HttpServletResponse.sendRedirect()
方法用于快速实现重定向跳转。
1 |
|
如果你恰好好奇点开 HttpServletResponse.sendRedirect()
方法查看具体实现,会发现其代码如下(Oracle JDK 8):
1 | public void sendRedirect(String url) throws IOException { |
可以看到 HttpServletResponse 接口实现将 HTTP 状态码固定为了 HTTP 302,如果不考虑更改状态码为 HTTP/1.1 中用于规范的 HTTP 307、308,直接使用也无妨。如果想要修改最终响应的 HTTP 状态码,可以使用 response.setStatus(code)
或者在控制器中的方法上使用 @ResponseStatus
注解自定义响应的 HTTP 状态码。