跳至主要內容

重定向与转发

guodongAndroid大约 4 分钟

开发环境

本文升级了 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 请求,这个过程就是重定向:

redirect

查看浏览器的网络请求,可以看到 两次 HTTP 请求:

redirect network

并且浏览器的地址栏自动更新为 /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_PERMANENTLY301 响应码,手动注入响应头中的 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 完成,这个过程就是转发:

forward

查看浏览器的网络请求,只看到 一次 HTTP 请求:

forward network

并且浏览器的地址栏依然是 /forward

总结

重定向与转发的区别在于:

方式执行对象
重定向客户端
转发服务端

例如:

  • 当功能模块升级后,请求地址发生了变化,服务器可以将原来的请求地址重定向至新的请求地址,从而避免客户端请求原地址找不到资源,提升用户体验。
  • 当功能模块升级后,请求地址不允许变化,服务器可以将请求转发给新的 Servlet 处理,客户端还是使用原请求地址,保证接口的稳定性和封装性。

重定向和转发在实际场景里都非常有用。