目录
重定向属于客户端行为。服务器在收到客户端请求后,会通知客户端浏览器重新向另外一个 URL 发送请求,这称为请求重定向。它本质上是两次 HTTP 请求,对应两个 request 对象和两个 response 对象。
重定向的工作流程
重定向的工作流程如下:
- 用户在浏览器中输入 URL,请求访问服务器端的 Web 资源。
- 服务器端的 Web 资源返回一个状态码为 302 的响应信息,该响应的含义为:通知浏览器再次发送请求,访问另一个 Web 资源(在响应信息中提供了另一个资源的 URL)。
- 当浏览器接收到响应后,立即自动访问另一个指定的 Web 资源。
- 另一 Web 资源将请求处理完成后,由容器把响应信息返回给浏览器进行展示。
转发和重定向的区别
转发和重定向都能实现页面的跳转,但是两者也存在以下区别。
区别 | 转发 | 重定向 |
---|---|---|
浏览器地址栏 URL 是否发生改变 | 否 | 是 |
是否支持跨域跳转 | 否 | 是 |
请求与响应的次数 | 一次请求和一次响应 | 两次请求和两次响应 |
是否共享 request 对象和 response 对象 | 是 | 否 |
是否能通过 request 域对象传递数据 | 是 | 否 |
速度 | 相对要快 | 相对要慢 |
行为类型 | 服务器行为 | 客户端行为 |
response.sendRedirect()
HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。
返回值类型 | 方法 | 描述 |
---|---|---|
void | sendRedirect(String location) | 向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。 |
示例
下面我们通过一个案例加深对 response 对象和重定向的理解。
在 webapp 中,创建登录页面 login.html,代码如下。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/web/DoServlet" method="GET">
<table border="1" width="50%">
<tr>
<td colspan="2" align="center">
welcome to servlet !!!
</td>
</tr>
<tr>
<td>账号</td>
<td>
<input type="text" name="username"/>
</td>
</tr>
<tr>
<td>密码</td>
<td>
<input type="password" name="password"/>
</td>
</tr>
<tr>
<td>验证码</td>
<td><input type="text" name="code"/>
<img id="imgId" src="/web/CheckCodeServlet">
<a href="#" onclick="run()">看不清,换一张</a>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="提交"/>
</td>
</tr>
</table>
</form>
</body>
<script type="text/javascript">
// 看不清,换一张,时间戳
function run() {
// 获取图片
var image = document.getElementById("imgId");
image.src = "/web/CheckCodeServlet?" + new Date().getTime();
}
</script>
</html>
创建名称为 CheckCodeServlet 的 Servlet 类,代码如下。
package swadian.servlet;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@WebServlet("/CheckCodeServlet")
public class CheckCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int width = 120;
int height = 30;
// 在内存中生成图片
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 先获取画笔对象
Graphics2D g = (Graphics2D) image.getGraphics();
// 设置灰色
g.setColor(Color.GRAY);
// 画填充的矩形
g.fillRect(0, 0, width, height);
// 设置颜色
g.setColor(Color.BLUE);
// 画边框
g.drawRect(0, 0, width - 1, height - 1);
// 准备数据,随机获取4个字符
String words = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
// 设置颜色
g.setColor(Color.YELLOW);
// 设置字体
g.setFont(new Font("隶书", Font.BOLD, 20));
String code = "";
//构造存储字符的数组
char[] a = {};
//构造存储字符串的集合
List<String> list = new ArrayList<String>();
Random random = new Random();
int x = 20;
int y = 20;
for (int i = 0; i < 4; i++) {
// void rotate(double theta, double x, double y)
// theta 弧度
// hudu = jiaodu * Math.PI / 180;
// 获取正负30之间的角度
int jiaodu = random.nextInt(60) - 30;
double hudu = jiaodu * Math.PI / 180;
g.rotate(hudu, x, y);
// 获取下标
int index = random.nextInt(words.length());
// 返回指定下标位置的字符,随机获取下标
char ch = words.charAt(index);
//将字符存入字符数组中
char[] chc = {ch};
//使用字符数组构造字符串
String string = new String(chc);
//将构造好的字符串添加进list集合中
list.add(string);
// 写字符串
g.drawString("" + ch, x, y);
g.rotate(-hudu, x, y);
x += 20;
}
for (int i = 0; i < list.size(); i++) {
code += list.get(i);
}
//将验证码存入上下文中->合适的方式是放在会话域session中
getServletContext().setAttribute("code", code);
// 设置颜色
g.setColor(Color.GREEN);
int x1, x2, y1, y2;
// 画干扰线
for (int i = 0; i < 4; i++) {
x1 = random.nextInt(width);
y1 = random.nextInt(height);
x2 = random.nextInt(width);
y2 = random.nextInt(height);
g.drawLine(x1, y1, x2, y2);
}
// 输出到客户端
ImageIO.write(image, "jpg", response.getOutputStream());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
创建名称为 DoServlet 的 Servlet 类,代码如下
package swadian.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/DoServlet")
public class DoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置向页面输出内容格式
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
String username = request.getParameter("username");
// 获取密码
String password = request.getParameter("password");
//获取验证码
String code = request.getParameter("code");
//设置是否成功标识
Boolean IsSuccess = true;
//从上下文获取存储的验证码
String code1 = (String) getServletContext().getAttribute("code");
//账号密码为admin.且验证码(忽略大小写)输入正确,则跳转到登陆成功页面
if (!"".equals(code) && code != null && code.equalsIgnoreCase(code1) && "admin".equals(username) && "admin".equals(password)) {
response.sendRedirect("/web/Success");
//账号密码不为admin,设置错误信息
} else if (!"admin".equals(username) || !"admin".equals(password)) {
getServletContext().setAttribute("msg", "账号或密码不正确");
IsSuccess = false;
//验证码错误,设置错误信息
} else if ("".equals(code) || code == null || !code.equalsIgnoreCase(code1)) {
getServletContext().setAttribute("msg", "验证码输入错误");
IsSuccess = false;
}
if (!IsSuccess) {
//设置自动跳转的时间,存储在上下文中
getServletContext().setAttribute("time", 5);
//向request对象中设置属性requestAttr,在重定向之后取值->实际上取不到
request.setAttribute("requestAttr", "重定向中使用request域对象传递的数据");
response.sendRedirect("/web/RefreshServlet");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
创建名称为 RefreshServlet 的 Servlet 类,代码如下。
package swadian.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
@WebServlet("/RefreshServlet")
public class RefreshServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
Object requestAttr = request.getAttribute("requestAttr");
//获取存在上下文中的跳转时间
Integer times = (Integer) getServletContext().getAttribute("time");
//获取存在上下文中的错误信息
String msg = (String) getServletContext().getAttribute("msg");
/**
*
* 设置三个头信息,禁用浏览器缓存
* Cache-Control : no-cache
* Expires: -1 值是日期类型(setDateHeader())
* Pragma : no-cache
*/
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", -1);
//
response.setContentType("text/html;charset=UTF-8");
//设置提示
String title = msg + ",即将在" +
times + "秒钟后跳转到登录页";
//使用默认时区和语言环境获得一个日历
Calendar cale = Calendar.getInstance();
//将Calendar类型转换成Date类型
Date tasktime = cale.getTime();
//设置日期输出的格式
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//格式化输出
String nowTime = df.format(tasktime);
//只要倒计时时间没有到0,就直至输出下面内容
if (times != 0) {
response.getWriter().write("<html>\n" +
"<head><title >" + title + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">RefreshServlet 提醒您:</h1>" +
"<h1 align=\"center\" style=\"font-family:arial;color:red;\">" + title + "</h1>\n" +
"<h1 align=\"center\">当前时间 是:" + nowTime + "</h1>\n" +
"<h1 align=\"center\">重定向通过request传递的数据为:" + requestAttr + "</h1>\n");
//倒计时
times--;
//将倒计时的时间重新存入上下文中覆盖原来的值
getServletContext().setAttribute("time", times);
// 通过refresh头完成页面刷新——>达到循环的效果
response.setHeader("refresh", "1;url=/web/RefreshServlet");
} else {
//倒计时归零,则跳转到登陆页面
response.sendRedirect("/web/login.html");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
doGet(request, response);
}
}
创建名称为 SuccessServlet 的 Servlet 类,代码如下。
package swadian.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/Success")
public class SuccessServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应输出的格式
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("<h1>登录成功</h1>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat,在地址栏输入“http://localhost:8080/web/login.html”,访问登录页,如下图所示。
在登录页面输入账号、密码、验证码等信息(该实例中没有使用数据库,所以当账号和密码都为 admin 时验证成功,否则验证失败),这里我们输入错误的验证码点击提交按钮,结果如下图。倒计时完成后,自动跳转到登录页面。
输入正确信息,点击提交,结果如下图。