/* * @(#)RetryModule.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; /** * This module handles request retries when a connection closes prematurely. * It is triggered by the RetryException thrown by the StreamDemultiplexor. * *

This module is somewhat unique in that it doesn't strictly limit itself * to the HTTPClientModule interface and its return values. That is, it * sends request directly using the HTTPConnection.sendRequest() method. This * is necessary because this module will not only resend its request but it * also resend all other requests in the chain. Also, it rethrows the * RetryException in Phase1 to restart the processing of the modules. * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär * @since V0.3 */ class RetryModule implements HTTPClientModule, GlobalConstants { // Constructors /** */ RetryModule() { } // Methods /** * Invoked by the HTTPClient. */ public int requestHandler(Request req, Response[] resp) { return REQ_CONTINUE; } /** * Invoked by the HTTPClient. */ public void responsePhase1Handler(Response resp, RoRequest roreq) throws IOException, ModuleException { try { resp.getStatusCode(); } catch (RetryException re) { Log.write(Log.MODS, "RtryM: Caught RetryException"); boolean got_lock = false; try { synchronized (re.first) { got_lock = true; // initialize idempotent sequence checking IdempotentSequence seq = new IdempotentSequence(); for (RetryException e=re.first; e!=null; e=e.next) seq.add(e.request); for (RetryException e=re.first; e!=null; e=e.next) { Log.write(Log.MODS, "RtryM: handling exception ", e); Request req = e.request; HTTPConnection con = req.getConnection(); /* Don't retry if either the sequence is not idempotent * (Sec 8.1.4 and 9.1.2), or we've already retried enough * times, or the headers have been read and parsed * already */ if (!seq.isIdempotent(req) || (con.ServProtVersKnown && con.ServerProtocolVersion >= HTTP_1_1 && req.num_retries > 0) || ((!con.ServProtVersKnown || con.ServerProtocolVersion <= HTTP_1_0) && req.num_retries > 4) || e.response.got_headers) { e.first = null; continue; } /** * if an output stream was used (i.e. we don't have the * data to resend) then delegate the responsibility for * resending to the application. */ if (req.getStream() != null) { if (HTTPConnection.deferStreamed) { req.getStream().reset(); e.response.setRetryRequest(true); } e.first = null; continue; } /* If we have an entity then setup either the entity-delay * or the Expect header */ if (req.getData() != null && e.conn_reset) { if (con.ServProtVersKnown && con.ServerProtocolVersion >= HTTP_1_1) addToken(req, "Expect", "100-continue"); else req.delay_entity = 5000L << req.num_retries; } /* If the next request in line has an entity and we're * talking to an HTTP/1.0 server then close the socket * after this request. This is so that the available() * call (to watch for an error response from the server) * will work correctly. */ if (e.next != null && e.next.request.getData() != null && (!con.ServProtVersKnown || con.ServerProtocolVersion < HTTP_1_1) && e.conn_reset) { addToken(req, "Connection", "close"); } /* If this an HTTP/1.1 server then don't pipeline retries. * The problem is that if the server for some reason * decides not to use persistent connections and it does * not do a correct shutdown of the connection, then the * response will be ReSeT. If we did pipeline then we * would keep falling into this trap indefinitely. * * Note that for HTTP/1.0 servers, if they don't support * keep-alives then the normal code will already handle * this accordingly and won't pipe over the same * connection. */ if (con.ServProtVersKnown && con.ServerProtocolVersion >= HTTP_1_1 && e.conn_reset) { req.dont_pipeline = true; } // The above is too risky - for moment let's be safe // and never pipeline retried request at all. req.dont_pipeline = true; // now resend the request Log.write(Log.MODS, "RtryM: Retrying request '" + req.getMethod() + " " + req.getRequestURI() + "'"); if (e.conn_reset) req.num_retries++; e.response.http_resp.set(req, con.sendRequest(req, e.response.timeout)); e.exception = null; e.first = null; } } } catch (NullPointerException npe) { if (got_lock) throw npe; } catch (ParseException pe) { throw new IOException(pe.getMessage()); } if (re.exception != null) throw re.exception; re.restart = true; throw re; } } /** * Invoked by the HTTPClient. */ public int responsePhase2Handler(Response resp, Request req) { // reset any stuff we might have set previously req.delay_entity = 0; req.dont_pipeline = false; req.num_retries = 0; return RSP_CONTINUE; } /** * Invoked by the HTTPClient. */ public void responsePhase3Handler(Response resp, RoRequest req) { } /** * Invoked by the HTTPClient. */ public void trailerHandler(Response resp, RoRequest req) { } /** * Add a token to the given header. If the header does not exist then * create it with the given token. * * @param req the request who's headers are to be modified * @param hdr the name of the header to add the token to (or to create) * @param tok the token to add * @exception ParseException if parsing the header fails */ private void addToken(Request req, String hdr, String tok) throws ParseException { int idx; NVPair[] hdrs = req.getHeaders(); for (idx=0; idx