RedirectionModule.java
上传用户:demmber
上传日期:2007-12-22
资源大小:717k
文件大小:14k
源码类别:

Java编程

开发平台:

Java

  1. /*
  2.  * @(#)RedirectionModule.java 0.3-3 06/05/2001
  3.  *
  4.  *  This file is part of the HTTPClient package
  5.  *  Copyright (C) 1996-2001 Ronald Tschal鋜
  6.  *
  7.  *  This library is free software; you can redistribute it and/or
  8.  *  modify it under the terms of the GNU Lesser General Public
  9.  *  License as published by the Free Software Foundation; either
  10.  *  version 2 of the License, or (at your option) any later version.
  11.  *
  12.  *  This library is distributed in the hope that it will be useful,
  13.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  *  Lesser General Public License for more details.
  16.  *
  17.  *  You should have received a copy of the GNU Lesser General Public
  18.  *  License along with this library; if not, write to the Free
  19.  *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  20.  *  MA 02111-1307, USA
  21.  *
  22.  *  For questions, suggestions, bug-reports, enhancement-requests etc.
  23.  *  I may be contacted at:
  24.  *
  25.  *  ronald@innovation.ch
  26.  *
  27.  *  The HTTPClient's home page is located at:
  28.  *
  29.  *  http://www.innovation.ch/java/HTTPClient/ 
  30.  *
  31.  */
  32. package HTTPClient;
  33. import java.net.ProtocolException;
  34. import java.io.IOException;
  35. import java.util.Hashtable;
  36. /**
  37.  * This module handles the redirection status codes 301, 302, 303, 305, 306
  38.  * and 307.
  39.  *
  40.  * @version 0.3-3  06/05/2001
  41.  * @author Ronald Tschal鋜
  42.  */
  43. class RedirectionModule implements HTTPClientModule
  44. {
  45.     /** a list of permanent redirections (301) */
  46.     private static Hashtable perm_redir_cntxt_list = new Hashtable();
  47.     /** a list of deferred redirections (used with Response.retryRequest()) */
  48.     private static Hashtable deferred_redir_list = new Hashtable();
  49.     /** the level of redirection */
  50.     private int level;
  51.     /** the url used in the last redirection */
  52.     private URI lastURI;
  53.     /** used for deferred redirection retries */
  54.     private boolean new_con;
  55.     /** used for deferred redirection retries */
  56.     private Request saved_req;
  57.     // Constructors
  58.     /**
  59.      * Start with level 0.
  60.      */
  61.     RedirectionModule()
  62.     {
  63. level     = 0;
  64. lastURI   = null;
  65. saved_req = null;
  66.     }
  67.     // Methods
  68.     /**
  69.      * Invoked by the HTTPClient.
  70.      */
  71.     public int requestHandler(Request req, Response[] resp)
  72.     {
  73. HTTPConnection con = req.getConnection();
  74. URI new_loc,
  75.     cur_loc;
  76. // check for retries
  77. HttpOutputStream out = req.getStream();
  78. if (out != null  &&  deferred_redir_list.get(out) != null)
  79. {
  80.     copyFrom((RedirectionModule) deferred_redir_list.remove(out));
  81.     req.copyFrom(saved_req);
  82.     if (new_con)
  83. return REQ_NEWCON_RST;
  84.     else
  85. return REQ_RESTART;
  86. }
  87. // handle permanent redirections
  88. try
  89. {
  90.     cur_loc = new URI(new URI(con.getProtocol(), con.getHost(), con.getPort(), null),
  91.       req.getRequestURI());
  92. }
  93. catch (ParseException pe)
  94. {
  95.     throw new Error("HTTPClient Internal Error: unexpected exception '"
  96.     + pe + "'");
  97. }
  98. // handle permanent redirections
  99. Hashtable perm_redir_list = Util.getList(perm_redir_cntxt_list,
  100.     req.getConnection().getContext());
  101. if ((new_loc = (URI) perm_redir_list.get(cur_loc)) != null)
  102. {
  103.     /* copy query if present in old url but not in new url. This
  104.      * isn't strictly conforming, but some scripts fail to properly
  105.      * propagate the query string to the Location header.
  106.      *
  107.      * Unfortunately it looks like we're fucked either way: some
  108.      * scripts fail if you don't propagate the query string, some
  109.      * fail if you do... God, don't you just love it when people
  110.      * can't read a spec? Anway, since we can't get it right for
  111.      * all scripts we opt to follow the spec.
  112.     String nres    = new_loc.getPathAndQuery(),
  113.    oquery  = Util.getQuery(req.getRequestURI()),
  114.    nquery  = Util.getQuery(nres);
  115.     if (nquery == null  &&  oquery != null)
  116. nres += "?" + oquery;
  117.      */
  118.     String nres = new_loc.getPathAndQuery();
  119.     req.setRequestURI(nres);
  120.     try
  121. { lastURI = new URI(new_loc, nres); }
  122.     catch (ParseException pe)
  123. { }
  124.     Log.write(Log.MODS, "RdirM: matched request in permanent " +
  125. "redirection list - redoing request to " +
  126. lastURI.toExternalForm());
  127.     if (!con.isCompatibleWith(new_loc))
  128.     {
  129. try
  130.     { con = new HTTPConnection(new_loc); }
  131. catch (Exception e)
  132. {
  133.     throw new Error("HTTPClient Internal Error: unexpected " +
  134.     "exception '" + e + "'");
  135. }
  136. con.setContext(req.getConnection().getContext());
  137. req.setConnection(con);
  138. return REQ_NEWCON_RST;
  139.     }
  140.     else
  141.     {
  142. return REQ_RESTART;
  143.     }
  144. }
  145. return REQ_CONTINUE;
  146.     }
  147.     /**
  148.      * Invoked by the HTTPClient.
  149.      */
  150.     public void responsePhase1Handler(Response resp, RoRequest req)
  151.     throws IOException
  152.     {
  153. int sts  = resp.getStatusCode();
  154. if (sts < 301  ||  sts > 307  ||  sts == 304)
  155. {
  156.     if (lastURI != null) // it's been redirected
  157. resp.setEffectiveURI(lastURI);
  158. }
  159.     }
  160.     /**
  161.      * Invoked by the HTTPClient.
  162.      */
  163.     public int responsePhase2Handler(Response resp, Request req)
  164.     throws IOException
  165.     {
  166. /* handle various response status codes until satisfied */
  167. int sts  = resp.getStatusCode();
  168. switch(sts)
  169. {
  170.     case 302: // General (temporary) Redirection (handle like 303)
  171. /* Note we only do this munging for POST and PUT. For GET it's
  172.  * not necessary; for HEAD we probably want to do another HEAD.
  173.  * For all others (i.e. methods from WebDAV, IPP, etc) it's
  174.  * somewhat unclear - servers supporting those should really
  175.  * return a 307 or 303, but some don't (guess who...), so we
  176.  * just don't touch those.
  177.  */
  178. if (req.getMethod().equals("POST")  ||
  179.     req.getMethod().equals("PUT"))
  180. {
  181.     Log.write(Log.MODS, "RdirM: Received status: " + sts +
  182. " " + resp.getReasonLine() +
  183. " - treating as 303");
  184.     sts = 303;
  185. }
  186.     case 301: // Moved Permanently
  187.     case 303: // See Other (use GET)
  188.     case 307: // Moved Temporarily (we mean it!)
  189. Log.write(Log.MODS, "RdirM: Handling status: " + sts +
  190.     " " + resp.getReasonLine());
  191. // the spec says automatic redirection may only be done if
  192. // the second request is a HEAD or GET.
  193. if (!req.getMethod().equals("GET")  &&
  194.     !req.getMethod().equals("HEAD")  &&
  195.     sts != 303)
  196. {
  197.     Log.write(Log.MODS, "RdirM: not redirected because " +
  198. "method is neither HEAD nor GET");
  199.     if (sts == 301  &&  resp.getHeader("Location") != null)
  200. update_perm_redir_list(req,
  201.     resLocHdr(resp.getHeader("Location"), req));
  202.     resp.setEffectiveURI(lastURI);
  203.     return RSP_CONTINUE;
  204. }
  205.     case 305: // Use Proxy
  206.     case 306: // Switch Proxy
  207. if (sts == 305  ||  sts == 306)
  208.     Log.write(Log.MODS, "RdirM: Handling status: " + sts +
  209.         " " + resp.getReasonLine());
  210. // Don't accept 305 from a proxy
  211. if (sts == 305  &&  req.getConnection().getProxyHost() != null)
  212. {
  213.     Log.write(Log.MODS, "RdirM: 305 ignored because " +
  214. "a proxy is already in use");
  215.     resp.setEffectiveURI(lastURI);
  216.     return RSP_CONTINUE;
  217. }
  218. /* the level is a primitive way of preventing infinite
  219.  * redirections. RFC-2068 set the max to 5, but RFC-2616
  220.  * has loosened this. Since some sites (notably M$) need
  221.  * more levels, this is now set to the (arbitrary) value
  222.  * of 15 (god only knows why they need to do even 5
  223.  * redirections...).
  224.  */
  225. if (level >= 15  ||  resp.getHeader("Location") == null)
  226. {
  227.     if (level >= 15)
  228. Log.write(Log.MODS, "RdirM: not redirected because "+
  229.     "of too many levels of redirection");
  230.     else
  231. Log.write(Log.MODS, "RdirM: not redirected because "+
  232.     "no Location header was present");
  233.     resp.setEffectiveURI(lastURI);
  234.     return RSP_CONTINUE;
  235. }
  236. level++;
  237. URI loc = resLocHdr(resp.getHeader("Location"), req);
  238. HTTPConnection mvd;
  239. String nres;
  240. new_con = false;
  241. if (sts == 305)
  242. {
  243.     mvd = new HTTPConnection(req.getConnection().getProtocol(),
  244.      req.getConnection().getHost(),
  245.      req.getConnection().getPort());
  246.     mvd.setCurrentProxy(loc.getHost(), loc.getPort());
  247.     mvd.setContext(req.getConnection().getContext());
  248.     new_con = true;
  249.     nres = req.getRequestURI();
  250.     /* There was some discussion about this, and especially
  251.      * Foteos Macrides (Lynx) said a 305 should also imply
  252.      * a change to GET (for security reasons) - see the thread
  253.      * starting at
  254.      * http://www.ics.uci.edu/pub/ietf/http/hypermail/1997q4/0351.html
  255.      * However, this is not in the latest draft, but since I
  256.      * agree with Foteos we do it anyway...
  257.      */
  258.     req.setMethod("GET");
  259.     req.setData(null);
  260.     req.setStream(null);
  261. }
  262. else if (sts == 306)
  263. {
  264.     // We'll have to wait for Josh to create a new spec here.
  265.     return RSP_CONTINUE;
  266. }
  267. else
  268. {
  269.     if (req.getConnection().isCompatibleWith(loc))
  270.     {
  271. mvd  = req.getConnection();
  272. nres = loc.getPathAndQuery();
  273.     }
  274.     else
  275.     {
  276. try
  277. {
  278.     mvd  = new HTTPConnection(loc);
  279.     nres = loc.getPathAndQuery();
  280. }
  281. catch (Exception e)
  282. {
  283.     if (req.getConnection().getProxyHost() == null  ||
  284. !loc.getScheme().equalsIgnoreCase("ftp"))
  285. return RSP_CONTINUE;
  286.     // We're using a proxy and the protocol is ftp -
  287.     // maybe the proxy will also proxy ftp...
  288.     mvd  = new HTTPConnection("http",
  289.     req.getConnection().getProxyHost(),
  290.     req.getConnection().getProxyPort());
  291.     mvd.setCurrentProxy(null, 0);
  292.     nres = loc.toExternalForm();
  293. }
  294. mvd.setContext(req.getConnection().getContext());
  295. new_con = true;
  296.     }
  297.     /* copy query if present in old url but not in new url.
  298.      * This isn't strictly conforming, but some scripts fail
  299.      * to propagate the query properly to the Location
  300.      * header.
  301.      *
  302.      * See comment on line 126.
  303.     String oquery  = Util.getQuery(req.getRequestURI()),
  304.    nquery  = Util.getQuery(nres);
  305.     if (nquery == null  &&  oquery != null)
  306. nres += "?" + oquery;
  307.      */
  308.     if (sts == 303)
  309.     {
  310. // 303 means "use GET"
  311. if (!req.getMethod().equals("HEAD"))
  312.     req.setMethod("GET");
  313. req.setData(null);
  314. req.setStream(null);
  315.     }
  316.     else
  317.     {
  318. // If they used an output stream then they'll have
  319. // to do the resend themselves
  320. if (req.getStream() != null)
  321. {
  322.     if (!HTTPConnection.deferStreamed)
  323.     {
  324. Log.write(Log.MODS, "RdirM: status " + sts +
  325.     " not handled - request " +
  326.     "has an output stream");
  327. return RSP_CONTINUE;
  328.     }
  329.     saved_req = (Request) req.clone();
  330.     deferred_redir_list.put(req.getStream(), this);
  331.     req.getStream().reset();
  332.     resp.setRetryRequest(true);
  333. }
  334. if (sts == 301)
  335. {
  336.     // update permanent redirection list
  337.     try
  338.     {
  339. update_perm_redir_list(req, new URI(loc, nres));
  340.     }
  341.     catch (ParseException pe)
  342.     {
  343. throw new Error("HTTPClient Internal Error: " +
  344. "unexpected exception '" + pe +
  345. "'");
  346.     }
  347. }
  348.     }
  349.     // Adjust Referer, if present
  350.     NVPair[] hdrs = req.getHeaders();
  351.     for (int idx=0; idx<hdrs.length; idx++)
  352. if (hdrs[idx].getName().equalsIgnoreCase("Referer"))
  353. {
  354.     HTTPConnection con = req.getConnection();
  355.     hdrs[idx] =
  356. new NVPair("Referer", con+req.getRequestURI());
  357.     break;
  358. }
  359. }
  360. req.setConnection(mvd);
  361. req.setRequestURI(nres);
  362. try { resp.getInputStream().close(); }
  363. catch (IOException ioe) { }
  364. if (sts != 305  &&  sts != 306)
  365. {
  366.     try
  367. { lastURI = new URI(loc, nres); }
  368.     catch (ParseException pe)
  369. { /* ??? */ }
  370.     Log.write(Log.MODS, "RdirM: request redirected to " +
  371. lastURI.toExternalForm() +
  372. " using method " + req.getMethod());
  373. }
  374. else
  375. {
  376.     Log.write(Log.MODS, "RdirM: resending request using " +
  377. "proxy " + mvd.getProxyHost() +
  378. ":" + mvd.getProxyPort());
  379. }
  380. if (req.getStream() != null)
  381.     return RSP_CONTINUE;
  382. else if (new_con)
  383.     return RSP_NEWCON_REQ;
  384. else
  385.     return RSP_REQUEST;
  386.     default:
  387. return RSP_CONTINUE;
  388. }
  389.     }
  390.     /**
  391.      * Invoked by the HTTPClient.
  392.      */
  393.     public void responsePhase3Handler(Response resp, RoRequest req)
  394.     {
  395.     }
  396.     /**
  397.      * Invoked by the HTTPClient.
  398.      */
  399.     public void trailerHandler(Response resp, RoRequest req)
  400.     {
  401.     }
  402.     /**
  403.      * Update the permanent redirection list.
  404.      *
  405.      * @param the original request
  406.      * @param the new location
  407.      */
  408.     private static void update_perm_redir_list(RoRequest req, URI new_loc)
  409.     {
  410. HTTPConnection con = req.getConnection();
  411. URI cur_loc = null;
  412. try
  413. {
  414.     cur_loc = new URI(new URI(con.getProtocol(), con.getHost(), con.getPort(), null),
  415.       req.getRequestURI());
  416. }
  417. catch (ParseException pe)
  418.     { }
  419. if (!cur_loc.equals(new_loc))
  420. {
  421.     Hashtable perm_redir_list =
  422. Util.getList(perm_redir_cntxt_list, con.getContext());
  423.     perm_redir_list.put(cur_loc, new_loc);
  424. }
  425.     }
  426.     /**
  427.      * The Location header field must be an absolute URI, but too many broken
  428.      * servers use relative URIs. So, we always resolve relative to the
  429.      * full request URI.
  430.      *
  431.      * @param  loc the Location header field
  432.      * @param  req the Request to resolve relative URI's relative to
  433.      * @return an absolute URI corresponding to the Location header field
  434.      * @exception ProtocolException if the Location header field is completely
  435.      *                            unparseable
  436.      */
  437.     private URI resLocHdr(String loc, RoRequest req)  throws ProtocolException
  438.     {
  439. try
  440. {
  441.     URI base = new URI(req.getConnection().getProtocol(),
  442.        req.getConnection().getHost(),
  443.        req.getConnection().getPort(), null);
  444.     base = new URI(base, req.getRequestURI());
  445.     URI res = new URI(base, loc);
  446.     if (res.getHost() == null)
  447. throw new ProtocolException("Malformed URL in Location header: `" + loc +
  448.     "' - missing host field");
  449.     return res;
  450. }
  451. catch (ParseException pe)
  452. {
  453.     throw new ProtocolException("Malformed URL in Location header: `" + loc +
  454. "' - exception was: " + pe.getMessage());
  455. }
  456.     }
  457.     private void copyFrom(RedirectionModule other)
  458.     {
  459. this.level     = other.level;
  460. this.lastURI   = other.lastURI;
  461. this.saved_req = other.saved_req;
  462.     }
  463. }