站長資訊網(wǎng)
        最全最豐富的資訊網(wǎng)站

        Tomcat 7 升級到 Tomcat 8 歷程

        總述

            JDK都要出12了,而我們項目使用的jdk卻仍然還停留在JDK1.6。為了追尋技術(shù)的發(fā)展的腳步,我這邊準備將項目升級到JDK1.8。而作為一個web項目,我們的容器使用的是Tomcat。看了下Tomcat版本與JDK版本之間的兼容關(guān)系http://tomcat.apache.org/whichversion.html以及網(wǎng)上所傳的各種JDK1.8和Tomcat7不兼容的問題, 我決定將Tomcat升級到8。我這里本地驗證采用的tomcat版本是8.5.38 https://tomcat.apache.org/download-80.cgi。

        問題一:請求js文件報404錯誤

            其實這個問題嚴格來講不是升級到Tomcat8出現(xiàn)的問題,而是升級到Tomcat9出現(xiàn)的問題。正好我開始嘗試的是Tomcat9,無法解決這個問題才降到Tomcat8。所以這里一并記錄下來。

            這個問題在從Tomcat6升級到Tomcat7之后也會存在,原因如下,在項目代碼中對js的請求路徑中包含了{、}等特殊符號:

        <script type="text/javascript" src="${ctx}/js/common/include_css.js?{'ctx':'${ctx}','easyui':'easyui'}"></script>  

            前臺會發(fā)現(xiàn)加載js的時候報了404的錯誤,后臺報錯信息如下:

        Invalid character found in the request target.The valid characters are defined in RFC 7230 and RFC3986

            出現(xiàn)這個問題的原因是因為Tomcat升級之后對安全進行了升級,其中就有對請求中的特殊字符進行校驗,具體校驗規(guī)則參照下面的代碼:

        (InternalInputBuffer、InternalAprInputBuffer、InternalNioInputBuffer)

        /**   * Read the request line. This function is meant to be used during the   * HTTP request header parsing. Do NOT attempt to read the request body   * using it.   *   * @throws IOException If an exception occurs during the underlying socket   * read operations, or if the given buffer is not big enough to accommodate   * the whole line.   */  @Override  public boolean parseRequestLine(boolean useAvailableDataOnly)        throws IOException {        int start = 0;        //      // Skipping blank lines      //        byte chr = 0;      do {            // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }          // Set the start time once we start reading data (even if it is          // just skipping blank lines)          if (request.getStartTime() < 0) {              request.setStartTime(System.currentTimeMillis());          }          chr = buf[pos++];      } while ((chr == Constants.CR) || (chr == Constants.LF));        pos--;        // Mark the current buffer position      start = pos;        //      // Reading the method name      // Method name is a token      //        boolean space = false;        while (!space) {            // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }            // Spec says method name is a token followed by a single SP but          // also be tolerant of multiple SP and/or HT.          if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {              space = true;              request.method().setBytes(buf, start, pos - start);          } else if (!HttpParser.isToken(buf[pos])) {              throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));          }            pos++;        }        // Spec says single SP but also be tolerant of multiple SP and/or HT      while (space) {          // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }          if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {              pos++;          } else {              space = false;          }      }        // Mark the current buffer position      start = pos;      int end = 0;      int questionPos = -1;        //      // Reading the URI      //        boolean eol = false;        while (!space) {            // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }            // Spec says single SP but it also says be tolerant of HT          if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {              space = true;              end = pos;          } else if ((buf[pos] == Constants.CR)                     || (buf[pos] == Constants.LF)) {              // HTTP/0.9 style request              eol = true;              space = true;              end = pos;          } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {              questionPos = pos;          } else if (HttpParser.isNotRequestTarget(buf[pos])) {              throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));          }            pos++;        }        request.unparsedURI().setBytes(buf, start, end - start);      if (questionPos >= 0) {          request.queryString().setBytes(buf, questionPos + 1,                                         end - questionPos - 1);          request.requestURI().setBytes(buf, start, questionPos - start);      } else {          request.requestURI().setBytes(buf, start, end - start);      }        // Spec says single SP but also says be tolerant of multiple SP and/or HT      while (space) {          // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }          if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {              pos++;          } else {              space = false;          }      }        // Mark the current buffer position      start = pos;      end = 0;        //      // Reading the protocol      // Protocol is always "HTTP/" DIGIT "." DIGIT      //      while (!eol) {            // Read new bytes if needed          if (pos >= lastValid) {              if (!fill())                  throw new EOFException(sm.getString("iib.eof.error"));          }            if (buf[pos] == Constants.CR) {              end = pos;          } else if (buf[pos] == Constants.LF) {              if (end == 0)                  end = pos;              eol = true;          } else if (!HttpParser.isHttpProtocol(buf[pos])) {              // 關(guān)鍵點在這一句,如果校驗不通過,則會報參數(shù)異常              throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));          }            pos++;        }        if ((end - start) > 0) {          request.protocol().setBytes(buf, start, end - start);      } else {          request.protocol().setString("");      }        return true;    }

        我們進一步跟進HttpParser中的方法:

        public static boolean isNotRequestTarget(int c) {      // Fast for valid request target characters, slower for some incorrect      // ones      try {          // 關(guān)鍵在于這個數(shù)組          return IS_NOT_REQUEST_TARGET[c];      } catch (ArrayIndexOutOfBoundsException ex) {          return true;      }  }      // Combination of multiple rules from RFC7230 and RFC 3986. Must be  // ASCII, no controls plus a few additional characters excluded  if (IS_CONTROL[i] || i > 127 ||          i == ' ' || i == '"' || i == '#' || i == '<' || i == '>' || i == '\' ||          i == '^' || i == '`'  || i == '{' || i == '|' || i == '}') {      // 可以看到只有在REQUEST_TARGET_ALLOW數(shù)組中的值才不會設置成true,所以我們需要追蹤REQUEST_TARGET_ALLOW數(shù)組的賦值      if (!REQUEST_TARGET_ALLOW[i]) {          IS_NOT_REQUEST_TARGET[i] = true;      }  }    String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");  if (prop != null) {      for (int i = 0; i < prop.length(); i++) {          char c = prop.charAt(i);          // 可以看到在配置文件中配置了tomcat.util.http.parser.HttpParser.requestTargetAllow并且包含{、}、|的時候,REQUEST_TARGET_ALLOW數(shù)組中的值才會為true          if (c == '{' || c == '}' || c == '|') {              REQUEST_TARGET_ALLOW[c] = true;          } else {              log.warn(sm.getString("httpparser.invalidRequestTargetCharacter",                      Character.valueOf(c)));          }      }  }

            解決辦法: 其實通過源碼分析不難得到解決辦法

        在Tomcat的catalina.properties文件中添加以下語句:

        tomcat.util.http.parser.HttpParser.requestTargetAllow={}|

        當然需要注意的是,這個后門在Tomcat8.5以后就無法使用的,Tomcat9之后的解決辦法暫時未找到,可能只有對URL進行編碼了。

        問題二:Cookie設置報錯

            這個問題就是在升級到Tomcat8.5以上的時候會出現(xiàn)的,具體原因是Tomcat8.5采用的Cookie處理類是:

        Rfc6265CookieProcessor,而在之前使用的處理類是LegacyCookieProcessor。該處理類對domai進行了校驗:

        private void validateDomain(String domain) {      int i = 0;      int prev = -1;      int cur = -1;      char[] chars = domain.toCharArray();      while (i < chars.length) {          prev = cur;          cur = chars[i];          if (!domainValid.get(cur)) {              throw new IllegalArgumentException(sm.getString(                      "rfc6265CookieProcessor.invalidDomain", domain));          }          // labels must start with a letter or number          if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {              throw new IllegalArgumentException(sm.getString(                      "rfc6265CookieProcessor.invalidDomain", domain));          }          // labels must end with a letter or number          if (prev == '-' && cur == '.') {              throw new IllegalArgumentException(sm.getString(                      "rfc6265CookieProcessor.invalidDomain", domain));          }          i++;      }      // domain must end with a label      if (cur == '.' || cur == '-') {          throw new IllegalArgumentException(sm.getString(                  "rfc6265CookieProcessor.invalidDomain", domain));      }  }

        新的Cookie規(guī)范對domain有以下要求

        1、必須是1-9、a-z、A-Z、. 、- (注意是-不是_)這幾個字符組成
        2、必須是數(shù)字或字母開頭 (所以以前的cookie的設置為.XX.com 的機制要改為 XX.com 即可)
        3、必須是數(shù)字或字母結(jié)尾

        原來的代碼設置domain時如下:

        cookie.setDomain(".aaa.com");

        這就導致設置domain的時候不符合新的規(guī)范,直接報錯如下:

        java.lang.IllegalArgumentException: An invalid domain [.aaa.com] was specified for this cookie          at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateDomain(Rfc6265CookieProcessor.java:181)          at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:123)          at org.apache.catalina.connector.Response.generateCookieString(Response.java:989)          at org.apache.catalina.connector.Response.addCookie(Response.java:937)          at org.apache.catalina.connector.ResponseFacade.addCookie(ResponseFacade.java:386)

            解決辦法(以下3中任意一種皆可)

        1. 修改原來代碼為:

          cookie.setDomain("aaa.com");
        2. 如果是Spring-boot環(huán)境,直接替換默認的Cookie處理類:

          @Configuration  @ConditionalOnExpression("${tomcat.useLegacyCookieProcessor:false}")  public class LegacyCookieProcessorConfiguration {      @Bean      EmbeddedServletContainerCustomizer embeddedServletContainerCustomizerLegacyCookieProcessor() {          return new EmbeddedServletContainerCustomizer() {              @Override              public void customize(ConfigurableEmbeddedServletContainer factory) {                  if (factory instanceof TomcatEmbeddedServletContainerFactory) {                      TomcatEmbeddedServletContainerFactory tomcatFactory =                              (TomcatEmbeddedServletContainerFactory) factory;                      tomcatFactory.addContextCustomizers(new TomcatContextCustomizer() {                          @Override                          public void customize(Context context) {                              context.setCookieProcessor(new LegacyCookieProcessor());                          }                      });                  }              }          };      }  }
        3. 在Tomcat的context.xml中增加如下配置,指定Cookie的處理類:

          <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" /> 

        更多Tomcat相關(guān)教程見以下內(nèi)容

        CentOS 6.6下安裝配置Tomcat環(huán)境  http://www.0106606.com/Linux/2015-08/122234.htm
        RedHat Linux 5.5安裝JDK+Tomcat并部署Java項目  http://www.0106606.com/Linux/2015-02/113528.htm
        Tomcat權(quán)威指南(第二版)(中英高清PDF版+帶書簽)  http://www.0106606.com/Linux/2015-02/113062.htm
        Tomcat 安全配置與性能優(yōu)化 http://www.0106606.com/Linux/2015-02/113060.htm
        Linux下使用Xshell查看Tomcat實時日志中文亂碼解決方案 http://www.0106606.com/Linux/2015-01/112395.htm
        CentOS 64-bit下安裝JDK和Tomcat并設置Tomcat開機啟動操作步驟 http://www.0106606.com/Linux/2015-01/111485.htm
        Ubuntu 16.04下安裝Tomcat 8.5.9  http://www.0106606.com/Linux/2017-06/144809.htm
        Tomcat中session的管理機制  http://www.0106606.com/Linux/2016-09/135072.htm

        贊(0)
        分享到: 更多 (0)
        網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號
        主站蜘蛛池模板: xxx国产精品视频| 国产精品 日韩欧美| 777久久精品一区二区三区无码 | 精品日韩在线视频一区二区三区| 国产精品毛片无遮挡| 亚洲一日韩欧美中文字幕欧美日韩在线精品一区二 | 久久国产欧美日韩精品| 亚洲国产精品成人| 精品久久久久久国产三级| 最新亚洲精品国自产在线观看| 99久久久精品| 精品国精品无码自拍自在线| 亚洲国产精品一区二区第一页| 久久久91人妻无码精品蜜桃HD| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 亚洲国产精品无码专区| 下载天堂国产AV成人无码精品网站| 惠民福利中文字幕人妻无码乱精品 | 久久亚洲日韩精品一区二区三区 | 精品乱码一区二区三区四区| 亚洲午夜福利精品久久| 欧美亚洲日本久久精品| 久久精品国产亚洲5555| 国产在线精品福利大全| 国产成人高清精品免费观看| 久久精品国产99国产电影网| 国产精品久久久久国产A级| 精品国产第一国产综合精品 | 国产中老年妇女精品| 久久99国产综合精品| 精品视频一区二区三三区四区 | 97精品国产高清自在线看超| 欧美一区二区精品久久| 久久精品国产只有精品2020| 久久久国产精品福利免费| 精品免费视在线观看| 精品久久久久久| 亚洲精品自产拍在线观看动漫| 亚洲综合一区二区精品导航 | 99热门精品一区二区三区无码| 18国产精品白浆在线观看免费|