好吧,这算是我的奇思妙想之一
问题描述
代码片段如下:
@RequestMapping("logout")//表示user/logout.do(有后缀)
public String logout(HttpServletRequest request,HttpServletResponse response) {
//删除当前的session
request.getSession().invalidate();//销毁当前session
//获取响应字符输出流
PrintWriter out = null;
try {
out = response.getWriter();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(out!=null) {
out.print("<script type='text/javascript'>");
out.print("alert('退出成功!');");
out.print("</script>");
out.flush();//提交响应流,已提交的响应无法被修改,默认提交请求头200
}
return "redirect:showLogin.do";
}
- 预期的作用是:先在页面弹出提示框,然后重定向到showLogin.do页面
- 实际的作用是:报错: 提交响应后无法调用sendRedirect()。
报错
- java.lang.IllegalStateException: 提交响应后无法调用sendRedirect()。
解决方案1
- 将重定向变为转发就可以正常使用了
return "showLogin.do";
原理分析
- 你如果用了response.getWriter().flush()方法,容器的实现会认为你要输出的内容无法确定大小(因为你已经先flush先输出一部分内容了),所以就采用了chunked编码方式输出。
那么就没有Content-Length: xxx, 而是多了一个 Transfer-Encoding: chunked,表示分段输出。 - 如果你不用flush,那么返回的http头里是包含 Content-Length: xxx 这样的,表示返回的全文长度。
- 如图
out.flush()意味着什么?
- flush表示响应已提交,默认提交请求头状态码200,原因如下:
out.flush();
//尝试使用reset重置状态码
response.reset();//报错:IllegalStateException: 已经提交响应后无法调用reset()
- 当一个响应被提交时,这意味着至少头部已经被发送到客户端.
- 当响应已经提交时,您无法设置/更改标头,因为为时已晚.
那么如何终止分块编码【Transfer-Encoding: chunked】?
- 一般服务器响应消息都会在HTTP响应头部加上Content-Length,这样客户端就知道要接收多少数据。
但是,分块编码无法在响应头部设置Content-Length,因为传输的数据不知道会有多少个。
所以,也就不知道数据会在何时终止 - 在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
- 最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
当包的大小为0时,当客户端收到一个包大小为0的chunk包时,表示包传输完了。
再次尝试【结果以失败告终】
指定响应码302
out.print("<script type='text/javascript'>");
out.print("alert('退出成功1!');");
out.print("</script>");
response.setStatus(302);//临时定向响应码
out.flush();//提交响应流,已提交的响应无法被修改
- 使用上述代码,可以实现提交数据,并将当前状态码改为302
- 但是,没有办法指定重定向的地址
指定响应码302,指定重定向的地址
out.print("<script type='text/javascript'>");
out.print("alert('退出成功1!');");
out.print("</script>");
response.setStatus(302);//临时定向响应码
response.setHeader("Location", "showLogin.do");//showLogin.do 代表重定向的地址
out.flush();//提交响应流,已提交的响应无法被修改
- 使用上述代码,可以实现当前状态码改为302,并且重定向成功
- 但是数据不会打印出来,而且还显示分块编码
指定响应码302,指定重定向的地址,指定浏览器接受的数据大小
out.print("<script type='text/javascript'>");
out.print("alert('退出成功1!');");
out.print("</script>");
response.setStatus(302);//临时定向响应码
response.setHeader("Location", "showLogin.do");//showLogin.do 代表重定向的地址
response.setTrailerFields(null);//没啥效果可以忽略
//setContentLength,表示响应后,浏览器接受的数据大小
//设置0,结束分块编码,提交后,不处理任何数据
response.setContentLength(0);
out.flush();//提交响应流,已提交的响应无法被修改
- 使用上述代码,可以实现当前状态码改为302,并且重定向成功
- 但是数据不会打印出来,不再显示分块编码
个人总结
- 重定向的过程需要进行两次请求响应
- 第一次响应回来的时候,如果指定了状态码是302且Location重定向的地址,则会直接跳转到指定的Location地址,不会处理第一次响应的信息。
- 第一次响应回来的时候,如果指定了状态码是302,但没有指定重定向地址,会按照状态码200的特点进行处理,处理响应的信息,但是处理完之后,会滞留在当前的页面,不会跳转。
- 相同路径,提交过一次响应头之后,不允许再次提交
即,当一个响应被提交时,这意味着至少头部已经被发送到客户端.
当响应已经提交时,您无法设置/更改标头,因为为时已晚. - 建议在使用转发的时候进行,因为转发的状态码是200,和flush提交后的默认状态码一致
不死心的垂死挣扎
2022-10-07放弃挣扎1
URL realUrl = new URL("http://localhost:8080/testSpringMVC6/user/logout.do");
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.setConnectTimeout(30 * 1000);
// 建立实际的连接
connection.connect();//
String location = connection.getHeaderField("Location");
System.out.println(location);