[Servlet]Response.sendRedirect() 三两事

[Servlet]Response.sendRedirect() 三两事


sendRedirect
void sendRedirect(java.lang.String location)
                  throws java.io.IOException
Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. The buffer will be replaced with the data set by this method. Calling this method sets the status code to SC_FOUND 302 (Found). This method can accept relative URLs;the servlet container must convert the relative URL to an absolute URL before sending the response to the client. If the location is relative without a leading '/' the container interprets it as relative to the current request URI. If the location is relative with a leading '/' the container interprets it as relative to the servlet container root.

If the response has already been committed, this method throws an IllegalStateException. After using this method, the response should be considered to be committed and should not be written to.

Parameters:
location - the redirect location URL
Throws:
java.io.IOException - If an input or output exception occurs
IllegalStateException - If the response was committed or if a partial URL is given and cannot be converted into a valid URL

然后我们再来来看 spring mvc "org.springframework.web.servlet.view.RedirectView" 实践的内容

   1:      /
   2:       * Send a redirect back to the HTTP client
   3:       * @param request current HTTP request (allows for reacting to request method)
   4:       * @param response current HTTP response (for sending response headers)
   5:       * @param targetUrl the target URL to redirect to
   6:       * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
   7:       * @throws IOException if thrown by response methods
   8:       */
   9:      protected void sendRedirect(
  10:              HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible)
  11:              throws IOException {
  12:   
  13:          if (http10Compatible) {
  14:              // Always send status code 302.
  15:              response.sendRedirect(response.encodeRedirectURL(targetUrl));
  16:          }
  17:          else {
  18:              HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
  19:              response.setStatus(statusCode.value());
  20:              response.setHeader("Location", response.encodeRedirectURL(targetUrl));
  21:          }
  22:      }

撇开"Status Code" 不同外(注一) , 其实 "Location" 也是不同的.

因为当你呼 response.sendRedirect( 相对路径 ), servlet container 会将它组合成绝对路径(如: http://www.example.com.tw/test/index.jsp) 放在响应标头.Location , 再交给浏览器处理.

image

而如果是用response.setHeader("Location", 相对路径 ), 在响应标头.Location 则是相对路径, 再交给浏览器处理.

image

感觉上好像是无差异的过程, 但如果发生在较复杂的分散式系统架构, 如: 前面有个SSL Web Server (apache) 走HTTPS, 但后面的AP Server 则走HTTP,

这时如果 plug-in 的 ap module 没有聪明到帮你Replace 响应标头 Location 到真实世界的 https://www.exapmle.com.tw/test/index.jsp 而是 http:// ...

就会出现迷途的羊了 ...

注一:

前: HTTP/1.0 302 Moved Temporarily   

后: HTTP/1.1 303 See Other

ps. 这题来自 Spring MVC “redirect:” prefix always redirects to http — how do I make it stay on https?

update 2011/12/24 上述推论, 其实证据还是太弱了, 还是把Tomcat 的底给打开来抓吧

   1:      /
   2:       * Send a temporary redirect to the specified redirect location URL.
   3:       *
   4:       * @param location Location URL to redirect to
   5:       *
   6:       * @exception IllegalStateException if this response has
   7:       *  already been committed
   8:       * @exception IOException if an input/output error occurs
   9:       */
  10:      @Override
  11:      public void sendRedirect(String location) 
  12:          throws IOException {
  13:   
  14:          if (isCommitted())
  15:              throw new IllegalStateException
  16:                  (sm.getString("coyoteResponse.sendRedirect.ise"));
  17:   
  18:          // Ignore any call from an included servlet
  19:          if (included)
  20:              return; 
  21:   
  22:          // Clear any data content that has been buffered
  23:          resetBuffer();
  24:   
  25:          // Generate a temporary redirect to the specified location
  26:          try {
  27:              String absolute = toAbsolute(location);
  28:              setStatus(SC_FOUND);
  29:              setHeader("Location", absolute);
  30:          } catch (IllegalArgumentException e) {
  31:              setStatus(SC_NOT_FOUND);
  32:          }
  33:   
  34:          // Cause the response to be finished (from the application perspective)
  35:          setSuspended(true);
  36:   
  37:      }

第27 行超可疑的, 再向下看

   1:          boolean leadingSlash = location.startsWith("/");
   2:   
   3:          if (leadingSlash || !hasScheme(location)) {
   4:   
   5:              redirectURLCC.recycle();
   6:   
   7:              String scheme = request.getScheme();
   8:              String name = request.getServerName();
   9:              int port = request.getServerPort();
  10:   
  11:              try {
  12:                  redirectURLCC.append(scheme, 0, scheme.length());
  13:                  redirectURLCC.append("://", 0, 3);
  14:                  redirectURLCC.append(name, 0, name.length());
  15:                  if ((scheme.equals("http") && port != 80)
  16:                      || (scheme.equals("https") && port != 443)) {
  17:                      redirectURLCC.append(':');
  18:                      String portS = port + "";
  19:                      redirectURLCC.append(portS, 0, portS.length());
  20:                  }

果然在第5~20行 被重置了 ... 这时第7~9行就是羊迷路的重点了, 如果被proxy rewrite过, 此时得到的可能被变动, 由其是 scheme( https to http), 这种问题在Google 上还真多解法,

1) 如果前头有开80 port 则强制转向443 , 所以在前头的web or proxy server就会rewrite 到 443 port.

可以参考

http://www.cyberciti.biz/tips/howto-apache-force-https-secure-connections.html
http://wiki.apache.org/httpd/RewriteSSL

2) 如果前头没开80 port者, 可以在后头设定第二组Connector 来 override HttpServletRequest 的scheme (http to https)

参考

http://forum.springsource.org/archive/index.php/t-108494.html

ps. 没有环境可测其真实性.

3) 更绝的做法是, 自己硬改 (Open source 不就是爱来这招, spring 更让人有许多发展空间 :D)

参考

http://forum.springsource.org/archive/index.php/t-38887.html

结语: 因为没有时间去建立这么多的环境来测试, 如果有人有兴趣就试试看吧, 记得告诉我结果, 感愠

update 2011/12/26 ASP.NET 会不会有同样问题呢?

刚试了一下,  答案:不会, 它不会转成绝对路径.

update 2011/12/27 玩了一个小把戏

edit ssl.conf
 
apache >= 2.2.4
 
 
  
    Header edit Location "^http://example.com.tw" "https://example.com.tw"