JavaWeb项目间隔刷新出现412

年前实训的时候写了个Javaweb项目,遇到的一些问题,现在总结一下方便以后查看。

登录成功后跳转到后台管理的页面,点了一下刷新发现提示我需要重新提交表单,因为这个Servlet是登录页面post登录信息跳转的,出现这个也很正常。

点击继续后发现页面显示412,后台控制台却没有报错。多刷新几次后,发现它是刷新间隔会报412,也就是一次成功进入后台,一次报412。

百度412的具体内容,在 HTTP 协议中,响应状态码 412 Precondition Failed(先决条件失败)表示客户端错误,意味着对于目标资源的访问请求被拒绝。这通常发生于采用除 GET 和 HEAD 之外的方法进行条件请求时,由首部字段 If-Unmodified-Since 或 If-None-Match 规定的先决条件不成立的情况下。这时候,请求的操作——通常是上传或修改文件——无法执行,从而返回该错误状态码。

不太看得懂。

于是我研究了一下两次有什么区别。

(解释原因的部分很长,只想直接解决的可以拉到最后)

 

跟踪了网页请求,发现在成功进入后台的时候,服务器的响应头中会返回

ETag:W/"216-1545291952524"

Last-Modified:Thu, 20 Dec 2018 07:45:52 GMT

这两条数据。

而在第二次刷新的时候,这两条数据会在请求消息头中被传给服务器

If-Modified-Since:Thu, 20 Dec 2018 07:45:52 GMT

If-None-Match:W/"216-1545291952524"

 

然后我研究了一下ETag,Last-Modified的具体含义

ETag

数据签名
典型做法:对资源内容进行hash计算
配合If-Match或者Id-Non-Match使用
对比资源的签名判断是否使用缓存

Last-Modified

上次修改时间
 配合If-Modified-Since或者If-Unmodified-Since使用
 对比上次修改时间验证资源是否需要更新

在浏览器和服务端设置都允许缓存策略的前提下(服务端响应头的Cache-Control不为no-store,即禁止任何缓存),如果某个请求的响应头设置了Last-Modified或Etag:

第一次请求:浏览器会记住响应头的Last-Modified和Etag;
第二次及以后请求:浏览器会携带保存的Last-Modified和Etag分别作为If-Modified-Since和If-None-Match放入请求头中携带过去,以此到服务端验证此次请求的资源是否过期或更新;服务端进行判断,若过期或更新,则返回新的资源,否则返回空即可,节省服务端消耗。
 

这是apach对于静态资源(含js,图片,css等)部分的缓存,用于加速并减轻后台实际web服务器的压力。

最先有的是last_modified,它的工作方式就是上述介绍的,但缺点是只能精确到秒级别。也就是说当你在一秒中修改资源两次,而客户端拿到的是第一次修改,那之后就算客户端第二次再次请求也不会拿到最新的资源。

而etag的出现正是为了解决last_modified的秒级问题,于http 1.1被提出。

tomcat对于静态数据作了缓存。

接着我们分析tomcat对于这部分静态缓存的判断处理,这部分逻辑是写在DefaultServlet类中,

我们可以在doGet方法中进入ServiceContext方法中找到以下源码: 

 // Check if the conditions specified in the optional If headers are
        // satisfied.
        if (cacheEntry.context == null) {

            // Checking If headers
            boolean included =
                (request.getAttribute(Globals.INCLUDE_CONTEXT_PATH_ATTR) != null);
            if (!included
                && !checkIfHeaders(request, response, cacheEntry.attributes)) {  //这句判断是否需要返回整个资源请求
                return;
            }

        }

上面源码的  if (!included
                && !checkIfHeaders(request, response, cacheEntry.attributes))

用于判断是否需要返回整个资源,如果indcluded与checkIfHeaders方法返回的都是false,这时就直接返回,说明资源未修改,或者是缓存不支持的请求方式。

我们接着查看checkIfHeaders方法:

 /**
     * Check if the conditions specified in the optional If headers are
     * satisfied.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param resourceAttributes The resource information
     * @return boolean true if the resource meets all the specified conditions,
     * and false if any of the conditions is not satisfied, in which case
     * request processing is stopped
     */
    protected boolean checkIfHeaders(HttpServletRequest request,
                                     HttpServletResponse response,
                                     ResourceAttributes resourceAttributes)
        throws IOException {

        return checkIfMatch(request, response, resourceAttributes)
            && checkIfModifiedSince(request, response, resourceAttributes)
            && checkIfNoneMatch(request, response, resourceAttributes)
            && checkIfUnmodifiedSince(request, response, resourceAttributes);

    }

可以看到tomcat只有当这四个属性全部返回true(也就是说全部认为资源已经改变)才会返回true,这样最终会将整个资源(最新修改过的)返回客户端。

在这里,我们从上面实际过程当中看到,浏览器第二次请求资源时在http请求header中放了

If-Modified-Since:Thu, 20 Dec 2018 07:45:52 GMT

If-None-Match:W/"216-1545291952524"

这两个属性。

因此我们查看

  && checkIfModifiedSince(request, response, resourceAttributes)
            && checkIfNoneMatch(request, response, resourceAttributes)

这两个方法

checkIfModifiedSince源码如下:

  /**
     * Check if the if-modified-since condition is satisfied.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param resourceInfo File object
     * @return boolean true if the resource meets the specified condition,
     * and false if the condition is not satisfied, in which case request
     * processing is stopped
     */
    protected boolean checkIfModifiedSince(HttpServletRequest request,
            HttpServletResponse response,
            ResourceAttributes resourceAttributes) {
        try {
            long headerValue = request.getDateHeader("If-Modified-Since");
            long lastModified = resourceAttributes.getLastModified();
            if (headerValue != -1) {

                // If an If-None-Match header has been specified, if modified since
                // is ignored.
                if ((request.getHeader("If-None-Match") == null)
                    && (lastModified < headerValue + 1000)) {
                    // The entity has not been modified since the date
                    // specified by the client. This is not an error case.
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    response.setHeader("ETag", resourceAttributes.getETag());

                    return false;
                }
            }
        } catch (IllegalArgumentException illegalArgument) {
            return true;
        }
        return true;

    }

源码中可以看到:

   if ((request.getHeader("If-None-Match") == null)
                    && (lastModified < headerValue + 1000)) {

这句话表明只有在客户端浏览器发送的请求头中不包含If-None-Match,IfModifiedSince才会生效。

我们接着看checkIfNoneMatch,源码如下:

/**
     * Check if the if-none-match condition is satisfied.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param resourceInfo File object
     * @return boolean true if the resource meets the specified condition,
     * and false if the condition is not satisfied, in which case request
     * processing is stopped
     */
    protected boolean checkIfNoneMatch(HttpServletRequest request,
                                     HttpServletResponse response,
                                     ResourceAttributes resourceAttributes)
        throws IOException {

        String eTag = resourceAttributes.getETag();
        String headerValue = request.getHeader("If-None-Match");
        if (headerValue != null) {

            boolean conditionSatisfied = false;

            if (!headerValue.equals("*")) {

                StringTokenizer commaTokenizer =
                    new StringTokenizer(headerValue, ",");

                while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
                    String currentToken = commaTokenizer.nextToken();
                    if (currentToken.trim().equals(eTag))
                        conditionSatisfied = true;
                }

            } else {
                conditionSatisfied = true;
            }

            if (conditionSatisfied) {

                // For GET and HEAD, we should respond with
                // 304 Not Modified.
                // For every other method, 412 Precondition Failed is sent
                // back.
                if ( ("GET".equals(request.getMethod()))
                     || ("HEAD".equals(request.getMethod())) ) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    response.setHeader("ETag", eTag);

                    return false;
                }
                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
                return false;
            }
        }
        return true;

    }

这里:

  String eTag = resourceAttributes.getETag();
        String headerValue = request.getHeader("If-None-Match");

这两句比较简单,就是分别从服务器缓存和http请求头中中取出etag。

接着判断这两个etag如果相等,则conditionSatisfied为true,会执行到以下语句: 

 if (conditionSatisfied) {

                // For GET and HEAD, we should respond with
                // 304 Not Modified.
                // For every other method, 412 Precondition Failed is sent
                // back.
                if ( ("GET".equals(request.getMethod()))
                     || ("HEAD".equals(request.getMethod())) ) {
                    response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    response.setHeader("ETag", eTag);

                    return false;
                }
                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
                return false;
    }

这段语句中可以发现,如果资源未改变的情况下,并且请求方式为GET或者HEAD时,会返回304状态码。否则返回一个412状态码,同样不会返回资源内容。

如果上述最终

if ((request.getHeader("If-None-Match") == null)
                    && (lastModified < headerValue + 1000))

条件不成立,即资源更新了或者是第一次请求,这里会读取当前请求资源文件,并最终放入http响应中。
==================================================================================

至此,终于知道为啥会报412了,tomcat直接设定,控制台自然也不会报错。错误的原因找到了,那应该怎么解决这个问题呢?

方法一:

清缓存,这样第二次访问的时候就不会有If-Modified-Since和If-None-Match被传过去了。

用户自然不会手动清除缓存,所以我们在后台加禁止浏览器缓存。

代码:

response.setHeader( "Cache-Control" , "no-store" );   
response.setDateHeader( "Expires" , 0); 
response.setHeader( "Pragma" , "no-cache" );

不过静止用户缓存这个方法大大增加了服务器的压力,所以可以选择第二种方法。

方法二:

在web.xml中添加error-page标签,按照错误号指定跳转。出现错误号为412则跳转到login.jsp(也可以改成别的页面,随自己的想法)

我顺便还把别的没捕捉的异常也一块处理了,反正有错就重新登录。

代码:

  <error-page>
    <error-code>412</error-code>
    <location>/login.jsp</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/login.jsp</location>
  </error-page>

现在我只知道这两种方法,如果还有别的办法可能会更新吧。

当然根据错误的原因,还可以将提交方式修改为get或者head,不过我这个登录跳转还是老老实实不考虑这个问题了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值