001: /*
002: * @(#)RetryModule.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.IOException;
032:
033: /**
034: * This module handles request retries when a connection closes prematurely.
035: * It is triggered by the RetryException thrown by the StreamDemultiplexor.
036: *
037: * <P>This module is somewhat unique in that it doesn't strictly limit itself
038: * to the HTTPClientModule interface and its return values. That is, it
039: * sends request directly using the HTTPConnection.sendRequest() method. This
040: * is necessary because this module will not only resend its request but it
041: * also resend all other requests in the chain. Also, it rethrows the
042: * RetryException in Phase1 to restart the processing of the modules.
043: *
044: * @version 0.3-2 18/06/1999
045: * @author Ronald Tschalär
046: * @since V0.3
047: */
048:
049: class RetryModule implements HTTPClientModule, GlobalConstants {
050: // Constructors
051:
052: /**
053: */
054: RetryModule() {
055: }
056:
057: // Methods
058:
059: /**
060: * Invoked by the HTTPClient.
061: */
062: public int requestHandler(Request req, Response[] resp) {
063: return REQ_CONTINUE;
064: }
065:
066: /**
067: * Invoked by the HTTPClient.
068: */
069: public void responsePhase1Handler(Response resp, RoRequest roreq)
070: throws IOException, ModuleException {
071: try {
072: resp.getStatusCode();
073: } catch (RetryException re) {
074: if (DebugMods)
075: System.err.println("RtryM: Caught RetryException");
076:
077: boolean got_lock = false;
078:
079: try {
080: synchronized (re.first) {
081: got_lock = true;
082:
083: // initialize idempotent sequence checking
084: IdempotentSequence seq = new IdempotentSequence();
085: for (RetryException e = re.first; e != null; e = e.next)
086: seq.add(e.request);
087:
088: for (RetryException e = re.first; e != null; e = e.next) {
089: Request req = e.request;
090: HTTPConnection con = req.getConnection();
091:
092: /* Don't retry if either the sequence is not idempotent
093: * (Sec 8.1.4 and 9.1.2), or we've already retried enough
094: * times, or the headers have been read and parsed
095: * already
096: */
097: if (!seq.isIdempotent(req)
098: || (con.ServProtVersKnown
099: && con.ServerProtocolVersion >= HTTP_1_1 && req.num_retries > 0)
100: || ((!con.ServProtVersKnown || con.ServerProtocolVersion <= HTTP_1_0) && req.num_retries > 4)
101: || e.response.got_headers
102: || req.getStream() != null) {
103: e.first = null;
104: continue;
105: }
106:
107: /* If we have an entity then setup either the entity-delay
108: * or the Expect header
109: */
110: if (req.getData() != null && e.conn_reset) {
111: if (con.ServProtVersKnown
112: && con.ServerProtocolVersion >= HTTP_1_1)
113: addToken(req, "Expect", "100-continue");
114: else
115: req.delay_entity = 5000L << req.num_retries;
116: }
117:
118: /* If the next request in line has an entity and we're
119: * talking to an HTTP/1.0 server then close the socket
120: * after this request. This is so that the available()
121: * call (to watch for an error response from the server)
122: * will work correctly.
123: */
124: if (e.next != null
125: && e.next.request.getData() != null
126: && (!con.ServProtVersKnown || con.ServerProtocolVersion < HTTP_1_1)
127: && e.conn_reset) {
128: addToken(req, "Connection", "close");
129: }
130:
131: /* If this an HTTP/1.1 server then don't pipeline retries.
132: * The problem is that if the server for some reason
133: * decides not to use persistent connections and it does
134: * not do a correct shutdown of the connection, then the
135: * response will be ReSeT. If we did pipeline then we
136: * would keep falling into this trap indefinitely.
137: *
138: * Note that for HTTP/1.0 servers, if they don't support
139: * keep-alives then the normal code will already handle
140: * this accordingly and won't pipe over the same
141: * connection.
142: */
143: if (con.ServProtVersKnown
144: && con.ServerProtocolVersion >= HTTP_1_1
145: && e.conn_reset) {
146: req.dont_pipeline = true;
147: }
148: // The above is too risky - for moment let's be safe
149: // and never pipeline retried request at all.
150: req.dont_pipeline = true;
151:
152: // now resend the request
153:
154: if (DebugDemux)
155: System.err
156: .println("RtryM: Retrying request '"
157: + req.getMethod()
158: + " "
159: + req.getRequestURI() + "'");
160:
161: if (e.conn_reset)
162: req.num_retries++;
163: e.response.http_resp.set(req, con.sendRequest(
164: req, e.response.timeout));
165: e.exception = null;
166: e.first = null;
167: }
168: }
169: } catch (NullPointerException npe) {
170: if (got_lock)
171: throw npe;
172: } catch (ParseException pe) {
173: throw new IOException(pe.getMessage());
174: }
175:
176: if (re.exception != null)
177: throw re.exception;
178:
179: re.restart = true;
180: throw re;
181: }
182: }
183:
184: /**
185: * Invoked by the HTTPClient.
186: */
187: public int responsePhase2Handler(Response resp, Request req) {
188: // reset any stuff we might have set previously
189: req.delay_entity = 0;
190: req.dont_pipeline = false;
191: req.num_retries = 0;
192:
193: return RSP_CONTINUE;
194: }
195:
196: /**
197: * Invoked by the HTTPClient.
198: */
199: public void responsePhase3Handler(Response resp, RoRequest req) {
200: }
201:
202: /**
203: * Invoked by the HTTPClient.
204: */
205: public void trailerHandler(Response resp, RoRequest req) {
206: }
207:
208: /**
209: * Add a token to the given header. If the header does not exist then
210: * create it with the given token.
211: *
212: * @param req the request who's headers are to be modified
213: * @param hdr the name of the header to add the token to (or to create)
214: * @param tok the token to add
215: * @exception ParseException if parsing the header fails
216: */
217: private void addToken(Request req, String hdr, String tok)
218: throws ParseException {
219: int idx;
220: NVPair[] hdrs = req.getHeaders();
221: for (idx = 0; idx < hdrs.length; idx++) {
222: if (hdrs[idx].getName().equalsIgnoreCase(hdr))
223: break;
224: }
225:
226: if (idx == hdrs.length) // no such header, so add one
227: {
228: hdrs = Util.resizeArray(hdrs, idx + 1);
229: hdrs[idx] = new NVPair(hdr, tok);
230: req.setHeaders(hdrs);
231: } else // header exists, so add token
232: {
233: if (!Util.hasToken(hdrs[idx].getValue(), tok))
234: hdrs[idx] = new NVPair(hdr, hdrs[idx].getValue() + ", "
235: + tok);
236: }
237: }
238: }
|