一次Tomcat无法正确返回重定向302状态码,始终返回200的错误

【啰嗦几句】

    起初,人们都毫不在意Tomcat返回的是什么状态码,因为正确的状态码一定是200,偶然会有些404、500,开发的道路走多了,也就习惯了平坦,直到那个“转发”的需求到来。

    注意!本文仅仅是一次因配置错误导致的状态码问题叙述,因为事发蹊跷,不是常见的问题解决方案,建议网友仅做备查,优先搜索其他专业文章。

 【起因】

     在项目中,我设计了一套类似SpringMVC的架构,支持FORWARD转发和REDIRECT重定向(没错,我就是这么认为的),由于需求里面一直没有重定向的场景,所以没有试过重定向功能,直到这次设计了一个PDF浏览接口,为了让浏览器能简化路径,方便下载时的文件名显示,所以尝试用重定向来解决。

    重定向的意思就是,浏览器第一次访问的接口通过识别后,因地址路径需要变化,而通知浏览器重新向另外的地址发出请求。本文是针对解决具体问题的,不是教程,暂且不再具体讲解。

【现象】

    Java重定向的代码真的不难,就一句:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //...省略...
		response.sendRedirect(view.getUrl());  //这里view.getUrl仅仅是一串地址,可以是http开头
	}

   也就是说,只需要这一句返回给客户端浏览器,浏览器自动会去跳转到另外一个地址。   熟悉的网友一定知道这个sendRedirect的本质是给浏览器回复一个302的状态码,同时加一个Header参数Location的地址,我也这么认为的,但是结果浏览器端或者PostMan端始终显示200状态码,实测如下图,本应是302的状态码,收到的却是200码。

【解决过程】

    1、第一反应是代码错了,于是网上一堆找,硬头皮啃了些外文资料,偶尔有人也遇到这个但是回复的无非就是上面那个sendRedirect。

    2、有点意思了,是不是自己写的框架有问题,我的框架用到listener、filter等等配置,据查资料可知,tomcat的执行路径应该是listener---->Filter---->Servlet(DoGet、DoPost),并在执行后反过来一步步返回给浏览器,由于前面response.sendRedirect是在Servlet中执行,有可能是执行之后,response内部的状态码被Filter改变,于是检查了Filter的代码(省略无关代码):

public class HTMLFilter implements Filter{
	String NO_LOGIN_NO = "用户未登录";
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		    throws IOException, ServletException{
		
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		HttpSession session = httpRequest.getSession(false);//这里用false,当不存在session时不会新建
		// 得到用户请求的URI
		String request_uri = httpRequest.getRequestURI();
		// 得到web应用程序的上下文路径
		String ctxPath = httpRequest.getContextPath();
		// 去除上下文路径,得到剩余部分的路径
		String uri = request_uri.substring(ctxPath.length());
		//System.out.println("访问uri="+uri);
		if (uri.equalsIgnoreCase("/index.jsp") || uri.equalsIgnoreCase("/DownloadServlet"))   
		{
        	chain.doFilter(request, response);
			//httpResponse.setStatus(302);
			//httpResponse.sendRedirect("www.taobao.com");
			
        	
		}
		
	
		
	}

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    	
    }

    @Override
    public void destroy() {
    }
}

为了减少排查难度,删除了不少业务代码,其实本质只有一句:

chain.doFilter(request, response);

这一句本意应该是执行某些过滤条件,我上面这样写相当于没有过滤,基本就照原对象传递给Servlet。于是又对Filter在web.xml文件中的配置进行优化删减,问题如旧。

   3、承认自己水平不够吧,可能自己写的框架真的有问题,想想老老实实写个最简单的Servlet的Helloworld吧,于是真动手写了一个,也是一句话response.sendRedirect("http://www.baidu.com");还是不行。

   4、难道是web.xml的配置问题吗?改来改去,甚至怀疑是不是<web-app></web-app>版本问题,把默认的3.1改为4,都没有解决。

   5、是不是java有问题啊?我承认搞不定了,丢给一个年轻同事,让他在他那里试试response.sendRedirect到底能不能正常重定向,结果他在他的环境下测试后说可以,唉,我这老脸算是挂不住了,装作若有所思地让他把他的模块代码给我......哦不!整个tomcat打包给我!(不然,万一再没解决还能厚脸皮向年轻同事再要一次代码?)

   6、经历多个波折,至少现在有了方向,预计是Tomcat的环境配置问题,于是先从server.xml着手,很好,新同事的配置多数是默认参数,降低排查难度。我从端口、SSL、项目参数一个个改起,最后终于在最后一个节点看到一句不一样的话:

标准配置大概如此:

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

而我的配置:

<Host name="localhost"  appBase="webapps"
            unpackWARs="false" autoDeploy="true" errorReportValveClass="com.xxxxx.errorPageFilter.CustomErrorServlet">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
		<!-- 此处是替换tomcat默认的错误类,从而达到替换页面的效果 -->
		<Valve className="com.xxxxx.errorPageFilter.CustomErrorServlet" showReport="false" showServerInfo="false" />
</Host>

区别有2个参数,一个是unpackWARs的值,一个是多了一个CustomErrorServlet的错误拦截类,于是分别对这2个参数进行修改和删除,终于看到浏览器跳转了。

【原因分析】

   其实问题就出在这个错误拦截类,这个是有一次我发现如果浏览器故意访问一个不存在的项目,Tomcat会返回404等默认界面,会出现Tomcat的版本号等,是一个小小安全隐患,于是让另一位年轻同事写个识别插件,也就是这个com.xxxxx.errorPageFilter.CustomErrorServlet,这个本质是一个自定义错误提示页面,无伤大雅,所以被忽视了:

public class CustomErrorServlet extends ErrorReportValve {
	 @Override
	 protected void report(Request request, Response response, Throwable t) {
		 response.setContentType("text/html");
		 response.setCharacterEncoding("utf-8");
		 response.setStatus(200);
		 PrintWriter writer;
			try {
				writer = response.getReporter();
				  if (writer != null) {
					    writer.write("页面不存在,你想干啥?");
					  }
			} catch (IOException e) {
				e.printStackTrace();
			}
	}

}

    问题就出在这个类的这一句:

response.setStatus(200);

   为了不显示默认错误页,当时采用了这么暴力的响应状态码?所以啊,浏览器的200状态码就是这么来的。

    原来,Tomcat或者说Java把response.sendRedirect重定向对应的302码认为是错误码,也执行了这个错误类插件,确实是知识盲区啊,所以只好让同事重新改一下这个类,忽略掉400之前的所有状态码(符合“事随人走的潜规则”,他开发的,他解决(◔‸◔))。

   至此,这个令人蹊跷又尴尬的问题才解决了,算算全部花费的时间,差不多2天多。希望帮助也有同样困惑的网友们,收工。

  本博客原创内容,转载请注明本博客信息,谢谢!

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值