重定向与转发
开发环境
本文升级了 IntelliJ IDEA 的版本,本文依然不需要 Tomcat 服务器
- IntelliJ IDEA 2023.2.3 (Ultimate Edition)
- JDK 11
- Gradle 7.5.1
- Kotlin 1.8.21
Tomcat 10.1.13
重定向
临时重定向
重定向是指当客户端发起一个请求时,服务器将返回一个重定向指令,指示客户端请求地址发生改变,需要使用新的地址再发起请求。
我们再编写一个 RedirectServlet
,当请求地址是 /redirect
时重定向到 /servlet
:
@WebServlet(name = "redirect", urlPatterns = ["/redirect"])
class RedirectServlet : HttpServlet() {
override fun init() {
println("RedirectServlet init")
}
override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val name = req.getParameter("name") ?: null
val location = "/servlet${ if (name != null) "?name=${name}" else "" }"
resp.sendRedirect(location)
}
}
如果客户端发起 GET /redirect
请求,RedirectServlet
将会处理此请求。由于 RedirectServlet
内部向客户端发送了重定向响应,因此,客户端将会收到如下响应:
HTTP/1.1 302 Found
Location: /servlet
当客户端收到 302
响应后,它会立刻根据 Location
字段的值重新发起一个 GET /servlet
请求,这个过程就是重定向:
查看浏览器的网络请求,可以看到 两次 HTTP 请求:
并且浏览器的地址栏自动更新为 /servlet
。
永久重定向
其实,有两种重定向的方式。上面我们使用的是临时重定向,由服务器返回 302
响应码,另一种是永久重定向,由服务器返回 301
响应码。下面修改下 RedirectServlet
的代码,使其返回 301
永久重定向:
override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
val name = req.getParameter("name") ?: null
val location = "/servlet${ if (name != null) "?name=${name}" else "" }"
- resp.sendRedirect(location)
+ resp.status = HttpServletResponse.SC_MOVED_PERMANENTLY // 301
+ resp.setHeader("Location", location)
}
其中 HttpServletResponse.SC_MOVED_PERMANENTLY
是 301
响应码,手动注入响应头中的 Location
字段的值。
如果使用永久重定向,浏览器会缓存 /redirect
到 /servlet
这个重定向映射关系,下次再请求 /redirect
时,浏览器就直接发起 /servlet
请求。
第一次请求 /redirect
:
可以看到浏览器收到了 301
响应码并再次发起 /servlet
请求。
第二次请求 /redirect
:
可以看到浏览器请求 /redirect
时是从缓存中取得的响应,说明浏览器的确缓存了永久重定向的映射关系。/servlet
请求的 Initiator
栏是 redirect
,说明 /servlet
请求由 /redirect
发起/触发。
当禁用缓存后,发起第三次请求 /redirect
:
当我们禁用缓存后,浏览器请求 /redirect
时不会从缓存中取,还是向服务器发起请求,此时与第一次请求别无二致。
临时还是永久?
至于如何选择重定向的类型,这主要取决于实际的业务需要。例如:
- 双十一搞一个抽奖活动,将原请求地址临时重定向至活动界面。
- 功能模块升级后使用了新的请求地址,为了不影响用户使用,将原请求地址永久重定向至新的请求地址。
转发
转发是指服务器内部转发。当一个 Servlet 处理请求时,它可以决定自己不再处理,而是转给另一个 Servlet 处理。
我们可以再编写一个 ForwardServlet
:
@WebServlet(name = "forward", urlPatterns = ["/forward"])
class ForwardServlet : HttpServlet() {
override fun init() {
println("ForwardServlet init")
}
override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
// 转给能处理 /servlet 请求地址的 Servlet
req.getRequestDispatcher("/servlet").forward(req, resp)
}
}
当客户端请求 /forward
时,ForwardServlet
将请求转给 HelloServlet
,后续处理实际上由 HelloServlet
完成,这个过程就是转发:
查看浏览器的网络请求,只看到 一次 HTTP 请求:
并且浏览器的地址栏依然是 /forward
。
总结
重定向与转发的区别在于:
方式 | 执行对象 |
---|---|
重定向 | 客户端 |
转发 | 服务端 |
例如:
- 当功能模块升级后,请求地址发生了变化,服务器可以将原来的请求地址重定向至新的请求地址,从而避免客户端请求原地址找不到资源,提升用户体验。
- 当功能模块升级后,请求地址不允许变化,服务器可以将请求转发给新的 Servlet 处理,客户端还是使用原请求地址,保证接口的稳定性和封装性。
重定向和转发在实际场景里都非常有用。