001: /*
002: * @(#)HTTPResponse.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: import java.io.EOFException;
033: import java.io.InterruptedIOException;
034: import java.io.InputStream;
035: import java.io.ByteArrayInputStream;
036: import java.net.URL;
037: import java.util.Date;
038: import java.util.Enumeration;
039:
040: /**
041: * This defines the http-response class returned by the requests. It's
042: * basically a wrapper around the Response class which first lets all
043: * the modules handle the response before finally giving the info to
044: * the user.
045: *
046: * @version 0.3-2 18/06/1999
047: * @author Ronald Tschalär
048: * @since 0.3
049: */
050:
051: public class HTTPResponse implements GlobalConstants,
052: HTTPClientModuleConstants {
053: /** the list of modules */
054: private HTTPClientModule[] modules;
055:
056: /** the timeout for reads */
057: private int timeout;
058:
059: /** the request */
060: private Request request = null;
061:
062: /** the current response */
063: private Response response = null;
064:
065: /** the HttpOutputStream to synchronize on */
066: private HttpOutputStream out_stream = null;
067:
068: /** our input stream from the stream demux */
069: private InputStream inp_stream;
070:
071: /** the status code returned. */
072: private int StatusCode;
073:
074: /** the reason line associated with the status code. */
075: private String ReasonLine;
076:
077: /** the HTTP version of the response. */
078: private String Version;
079:
080: /** the original URI used. */
081: private URI OriginalURI = null;
082:
083: /** the final URI of the document. */
084: private URI EffectiveURI = null;
085:
086: /** any headers which were received and do not fit in the above list. */
087: private CIHashtable Headers = null;
088:
089: /** any trailers which were received and do not fit in the above list. */
090: private CIHashtable Trailers = null;
091:
092: /** the ContentLength of the data. */
093: private int ContentLength = -1;
094:
095: /** the data (body) returned. */
096: private byte[] Data = null;
097:
098: /** signals if we have got and parsed the headers yet? */
099: private boolean initialized = false;
100:
101: /** signals if we have got the trailers yet? */
102: private boolean got_trailers = false;
103:
104: /** marks this response as aborted (stop() in HTTPConnection) */
105: private boolean aborted = false;
106:
107: /** the method used in the request */
108: private String method = null;
109:
110: // Constructors
111:
112: /**
113: * Creates a new HTTPResponse.
114: *
115: * @param modules the list of modules handling this response
116: * @param timeout the timeout to be used on stream read()'s
117: */
118: HTTPResponse(HTTPClientModule[] modules, int timeout, Request orig) {
119: this .modules = modules;
120: this .timeout = timeout;
121: try {
122: this .OriginalURI = new URI(orig.getConnection()
123: .getProtocol(), orig.getConnection().getHost(),
124: orig.getConnection().getPort(), orig
125: .getRequestURI());
126: } catch (ParseException pe) {
127: }
128: this .method = orig.getMethod();
129: }
130:
131: /**
132: * @param req the request
133: * @param resp the response
134: */
135: void set(Request req, Response resp) {
136: this .request = req;
137: this .response = resp;
138: resp.http_resp = this ;
139: resp.timeout = timeout;
140: this .aborted = resp.final_resp;
141: }
142:
143: /**
144: * @param req the request
145: * @param resp the response
146: */
147: void set(Request req, HttpOutputStream out_stream) {
148: this .request = req;
149: this .out_stream = out_stream;
150: }
151:
152: // Methods
153:
154: /**
155: * Give the status code for this request. These are grouped as follows:
156: * <UL>
157: * <LI> 1xx - Informational (new in HTTP/1.1)
158: * <LI> 2xx - Success
159: * <LI> 3xx - Redirection
160: * <LI> 4xx - Client Error
161: * <LI> 5xx - Server Error
162: * </UL>
163: *
164: * @exception IOException if any exception occurs on the socket.
165: * @exception ModuleException if any module encounters an exception.
166: */
167: public final int getStatusCode() throws IOException,
168: ModuleException {
169: if (!initialized)
170: handleResponse();
171: return StatusCode;
172: }
173:
174: /**
175: * Give the reason line associated with the status code.
176: *
177: * @exception IOException If any exception occurs on the socket.
178: * @exception ModuleException if any module encounters an exception.
179: */
180: public final String getReasonLine() throws IOException,
181: ModuleException {
182: if (!initialized)
183: handleResponse();
184: return ReasonLine;
185: }
186:
187: /**
188: * Get the HTTP version used for the response.
189: *
190: * @exception IOException If any exception occurs on the socket.
191: * @exception ModuleException if any module encounters an exception.
192: */
193: public final String getVersion() throws IOException,
194: ModuleException {
195: if (!initialized)
196: handleResponse();
197: return Version;
198: }
199:
200: /**
201: * Get the name and type of server.
202: *
203: * @deprecated This method is a remnant of V0.1; use
204: * <code>getHeader("Server")</code> instead.
205: * @see #getHeader(java.lang.String)
206: * @exception IOException If any exception occurs on the socket.
207: * @exception ModuleException if any module encounters an exception.
208: */
209: public final String getServer() throws IOException, ModuleException {
210: if (!initialized)
211: handleResponse();
212: return getHeader("Server");
213: }
214:
215: /**
216: * Get the original URI used in the request.
217: *
218: * @return the URI used in primary request
219: */
220: public final URI getOriginalURI() {
221: return OriginalURI;
222: }
223:
224: /**
225: * Get the final URL of the document. This is set if the original
226: * request was deferred via the "moved" (301, 302, or 303) return
227: * status.
228: *
229: * @return the effective URL, or null if no redirection occured
230: * @exception IOException If any exception occurs on the socket.
231: * @exception ModuleException if any module encounters an exception.
232: * @deprecated use getEffectiveURI() instead
233: * @see #getEffectiveURI
234: */
235: public final URL getEffectiveURL() throws IOException,
236: ModuleException {
237: if (!initialized)
238: handleResponse();
239: if (EffectiveURI != null)
240: return EffectiveURI.toURL();
241: return null;
242: }
243:
244: /**
245: * Get the final URI of the document. If the request was redirected
246: * via the "moved" (301, 302, 303, or 307) return status this returns
247: * the URI used in the last redirection; otherwise it returns the
248: * original URI.
249: *
250: * @return the effective URI
251: * @exception IOException If any exception occurs on the socket.
252: * @exception ModuleException if any module encounters an exception.
253: */
254: public final URI getEffectiveURI() throws IOException,
255: ModuleException {
256: if (!initialized)
257: handleResponse();
258: if (EffectiveURI != null)
259: return EffectiveURI;
260: return OriginalURI;
261: }
262:
263: /**
264: * Retrieves the value for a given header.
265: *
266: * @param hdr the header name.
267: * @return the value for the header, or null if non-existent.
268: * @exception IOException If any exception occurs on the socket.
269: * @exception ModuleException if any module encounters an exception.
270: */
271: public String getHeader(String hdr) throws IOException,
272: ModuleException {
273: if (!initialized)
274: handleResponse();
275: return (String) Headers.get(hdr.trim());
276: }
277:
278: /**
279: * Retrieves the value for a given header. The value is parsed as an
280: * int.
281: *
282: * @param hdr the header name.
283: * @return the value for the header if the header exists
284: * @exception NumberFormatException if the header's value is not a number
285: * or if the header does not exist.
286: * @exception IOException if any exception occurs on the socket.
287: * @exception ModuleException if any module encounters an exception.
288: */
289: public int getHeaderAsInt(String hdr) throws IOException,
290: ModuleException, NumberFormatException {
291: return Integer.parseInt(getHeader(hdr));
292: }
293:
294: /**
295: * Retrieves the value for a given header. The value is parsed as a
296: * date; if this fails it is parsed as a long representing the number
297: * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
298: * exception is thrown.
299: * <br>Note: When sending dates use Util.httpDate().
300: *
301: * @param hdr the header name.
302: * @return the value for the header, or null if non-existent.
303: * @exception IllegalArgumentException if the header's value is neither a
304: * legal date nor a number.
305: * @exception IOException if any exception occurs on the socket.
306: * @exception ModuleException if any module encounters an exception.
307: */
308: public Date getHeaderAsDate(String hdr) throws IOException,
309: IllegalArgumentException, ModuleException {
310: String raw_date = getHeader(hdr);
311: if (raw_date == null)
312: return null;
313:
314: // asctime() format is missing an explicit GMT specifier
315: if (raw_date.toUpperCase().indexOf("GMT") == -1)
316: raw_date += " GMT";
317:
318: Date date;
319:
320: try {
321: date = new Date(raw_date);
322: } catch (IllegalArgumentException iae) {
323: // some servers erroneously send a number, so let's try that
324: long time;
325: try {
326: time = Long.parseLong(raw_date);
327: } catch (NumberFormatException nfe) {
328: throw iae;
329: } // give up
330: if (time < 0)
331: time = 0;
332: date = new Date(time * 1000L);
333: }
334:
335: return date;
336: }
337:
338: /**
339: * Returns an enumeration of all the headers available via getHeader().
340: *
341: * @exception IOException If any exception occurs on the socket.
342: * @exception ModuleException if any module encounters an exception.
343: */
344: public Enumeration listHeaders() throws IOException,
345: ModuleException {
346: if (!initialized)
347: handleResponse();
348: return Headers.keys();
349: }
350:
351: /**
352: * Retrieves the value for a given trailer. This should not be invoked
353: * until all response data has been read. If invoked before it will
354: * call <code>getData()</code> to force the data to be read.
355: *
356: * @param trailer the trailer name.
357: * @return the value for the trailer, or null if non-existent.
358: * @exception IOException If any exception occurs on the socket.
359: * @exception ModuleException if any module encounters an exception.
360: * @see #getData()
361: */
362: public String getTrailer(String trailer) throws IOException,
363: ModuleException {
364: if (!got_trailers)
365: getTrailers();
366: return (String) Trailers.get(trailer.trim());
367: }
368:
369: /**
370: * Retrieves the value for a given tailer. The value is parsed as an
371: * int.
372: *
373: * @param trailer the tailer name.
374: * @return the value for the trailer if the trailer exists
375: * @exception NumberFormatException if the trailer's value is not a number
376: * or if the trailer does not exist.
377: * @exception IOException if any exception occurs on the socket.
378: * @exception ModuleException if any module encounters an exception.
379: */
380: public int getTrailerAsInt(String trailer) throws IOException,
381: ModuleException, NumberFormatException {
382: return Integer.parseInt(getTrailer(trailer));
383: }
384:
385: /**
386: * Retrieves the value for a given trailer. The value is parsed as a
387: * date; if this fails it is parsed as a long representing the number
388: * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
389: * IllegalArgumentException is thrown.
390: * <br>Note: When sending dates use Util.httpDate().
391: *
392: * @param trailer the trailer name.
393: * @return the value for the trailer, or null if non-existent.
394: * @exception IllegalArgumentException if the trailer's value is neither a
395: * legal date nor a number.
396: * @exception IOException if any exception occurs on the socket.
397: * @exception ModuleException if any module encounters an exception.
398: */
399: public Date getTrailerAsDate(String trailer) throws IOException,
400: IllegalArgumentException, ModuleException {
401: String raw_date = getTrailer(trailer);
402: if (raw_date == null)
403: return null;
404:
405: // asctime() format is missing an explicit GMT specifier
406: if (raw_date.toUpperCase().indexOf("GMT") == -1)
407: raw_date += " GMT";
408:
409: Date date;
410:
411: try {
412: date = new Date(raw_date);
413: } catch (IllegalArgumentException iae) {
414: // some servers erroneously send a number, so let's try that
415: long time;
416: try {
417: time = Long.parseLong(raw_date);
418: } catch (NumberFormatException nfe) {
419: throw iae;
420: } // give up
421: if (time < 0)
422: time = 0;
423: date = new Date(time * 1000L);
424: }
425:
426: return date;
427: }
428:
429: /**
430: * Returns an enumeration of all the trailers available via getTrailer().
431: *
432: * @exception IOException If any exception occurs on the socket.
433: * @exception ModuleException if any module encounters an exception.
434: */
435: public Enumeration listTrailers() throws IOException,
436: ModuleException {
437: if (!got_trailers)
438: getTrailers();
439: return Trailers.keys();
440: }
441:
442: /**
443: * Reads all the response data into a byte array. Note that this method
444: * won't return until <em>all</em> the data has been received (so for
445: * instance don't invoke this method if the server is doing a server
446: * push). If <code>getInputStream()</code> had been previously invoked
447: * then this method only returns any unread data remaining on the stream
448: * and then closes it.
449: *
450: * <P>Note to the unwarry: code like
451: * <code>System.out.println("The data: " + resp.getData())</code>
452: * will probably not do what you want - use
453: * <code>System.out.println("The data: " + new String(resp.getData()))</code>
454: * instead.
455: *
456: * @see #getInputStream()
457: * @return an array containing the data (body) returned. If no data
458: * was returned then it's set to a zero-length array.
459: * @exception IOException If any io exception occured while reading
460: * the data
461: * @exception ModuleException if any module encounters an exception.
462: */
463: public synchronized byte[] getData() throws IOException,
464: ModuleException {
465: if (!initialized)
466: handleResponse();
467:
468: if (Data == null) {
469: try {
470: readResponseData(inp_stream);
471: } catch (InterruptedIOException ie) // don't intercept
472: {
473: throw ie;
474: } catch (IOException ioe) {
475: if (DebugResp) {
476: System.err.println("HResp: (\"" + method + " "
477: + OriginalURI.getPath() + "\")");
478: System.err.print(" ");
479: ioe.printStackTrace();
480: }
481: try {
482: inp_stream.close();
483: } catch (Exception e) {
484: }
485: throw ioe;
486: }
487:
488: inp_stream.close();
489: }
490:
491: return Data;
492: }
493:
494: /**
495: * Gets an input stream from which the returned data can be read. Note
496: * that if <code>getData()</code> had been previously invoked it will
497: * actually return a ByteArrayInputStream created from that data.
498: *
499: * @see #getData()
500: * @return the InputStream.
501: * @exception IOException If any exception occurs on the socket.
502: * @exception ModuleException if any module encounters an exception.
503: */
504: public synchronized InputStream getInputStream()
505: throws IOException, ModuleException {
506: if (!initialized)
507: handleResponse();
508:
509: if (Data == null)
510: return inp_stream;
511: else {
512: getData(); // ensure complete data is read
513: return new ByteArrayInputStream(Data);
514: }
515: }
516:
517: /**
518: * produces a full list of headers and their values, one per line.
519: *
520: * @return a string containing the headers
521: */
522: public String toString() {
523: if (!initialized) {
524: try {
525: handleResponse();
526: } catch (Exception e) {
527: if (DebugResp && !(e instanceof InterruptedIOException)) {
528: System.err.println("HResp: (\"" + method + " "
529: + OriginalURI.getPath() + "\")");
530: System.err.print(" ");
531: e.printStackTrace();
532: }
533: return "Failed to read headers: " + e;
534: }
535: }
536:
537: String nl = System.getProperty("line.separator", "\n");
538:
539: StringBuffer str = new StringBuffer(Version);
540: str.append(' ');
541: str.append(StatusCode);
542: str.append(' ');
543: str.append(ReasonLine);
544: str.append(nl);
545:
546: if (EffectiveURI != null) {
547: str.append("Effective-URI: ");
548: str.append(EffectiveURI);
549: str.append(nl);
550: }
551:
552: Enumeration hdr_list = Headers.keys();
553: while (hdr_list.hasMoreElements()) {
554: String hdr = (String) hdr_list.nextElement();
555: str.append(hdr);
556: str.append(": ");
557: str.append(Headers.get(hdr));
558: str.append(nl);
559: }
560:
561: return str.toString();
562: }
563:
564: // Helper Methods
565:
566: HTTPClientModule[] getModules() {
567: return modules;
568: }
569:
570: /**
571: * Processes a Response. This is done by calling the response handler
572: * in each module. When all is done, the various fields of this instance
573: * are intialized from the last Response.
574: *
575: * @exception IOException if any handler throws an IOException.
576: * @exception ModuleException if any module encounters an exception.
577: * @return true if a new request was generated. This is used for internal
578: * subrequests only
579: */
580: synchronized boolean handleResponse() throws IOException,
581: ModuleException {
582: if (initialized)
583: return false;
584:
585: /* first get the response if necessary */
586:
587: if (out_stream != null) {
588: response = out_stream.getResponse();
589: response.http_resp = this ;
590: out_stream = null;
591: }
592:
593: /* go through modules and handle them */
594:
595: doModules: while (true) {
596:
597: Phase1: for (int idx = 0; idx < modules.length && !aborted; idx++) {
598: try {
599: modules[idx].responsePhase1Handler(response,
600: request);
601: } catch (RetryException re) {
602: if (re.restart)
603: continue doModules;
604: else
605: throw re;
606: }
607: }
608:
609: Phase2: for (int idx = 0; idx < modules.length && !aborted; idx++) {
610: int sts = modules[idx].responsePhase2Handler(response,
611: request);
612: switch (sts) {
613: case RSP_CONTINUE: // continue processing
614: break;
615:
616: case RSP_RESTART: // restart response processing
617: idx = -1;
618: continue doModules;
619:
620: case RSP_SHORTCIRC: // stop processing and return
621: break doModules;
622:
623: case RSP_REQUEST: // go to phase 1
624: case RSP_NEWCON_REQ: // process the request using a new con
625: response.getInputStream().close();
626: if (handle_trailers)
627: invokeTrailerHandlers(true);
628: if (request.internal_subrequest)
629: return true;
630: request.getConnection().handleRequest(request,
631: this , response, true);
632: if (initialized)
633: break doModules;
634:
635: idx = -1;
636: continue doModules;
637:
638: case RSP_SEND: // send the request immediately
639: case RSP_NEWCON_SND: // send the request using a new con
640: response.getInputStream().close();
641: if (handle_trailers)
642: invokeTrailerHandlers(true);
643: if (request.internal_subrequest)
644: return true;
645: request.getConnection().handleRequest(request,
646: this , response, false);
647: idx = -1;
648: continue doModules;
649:
650: default: // not valid
651: throw new Error(
652: "HTTPClient Internal Error: invalid status"
653: + " " + sts
654: + " returned by module "
655: + modules[idx].getClass().getName());
656: }
657: }
658:
659: Phase3: for (int idx = 0; idx < modules.length && !aborted; idx++) {
660: modules[idx].responsePhase3Handler(response, request);
661: }
662:
663: break doModules;
664: }
665:
666: /* force a read on the response in case none of the modules did */
667: response.getStatusCode();
668:
669: /* all done, so copy data */
670: if (!request.internal_subrequest)
671: init(response);
672:
673: if (handle_trailers)
674: invokeTrailerHandlers(false);
675:
676: return false;
677: }
678:
679: /**
680: * Copies the relevant fields from Response and marks this as initialized.
681: *
682: * @param resp the Response class to copy from
683: */
684: void init(Response resp) {
685: if (initialized)
686: return;
687:
688: this .StatusCode = resp.StatusCode;
689: this .ReasonLine = resp.ReasonLine;
690: this .Version = resp.Version;
691: this .EffectiveURI = resp.EffectiveURI;
692: this .ContentLength = resp.ContentLength;
693: this .Headers = resp.Headers;
694: this .inp_stream = resp.inp_stream;
695: this .Data = resp.Data;
696: initialized = true;
697: }
698:
699: private boolean handle_trailers = false;
700: private boolean trailers_handled = false;
701:
702: /**
703: * This is invoked by the RespInputStream when it is close()'d. It
704: * just invokes the trailer handler in each module.
705: *
706: * @param force invoke the handlers even if not initialized yet?
707: * @exception IOException if thrown by any module
708: * @exception ModuleException if thrown by any module
709: */
710: void invokeTrailerHandlers(boolean force) throws IOException,
711: ModuleException {
712: if (trailers_handled)
713: return;
714:
715: if (!force && !initialized) {
716: handle_trailers = true;
717: return;
718: }
719:
720: for (int idx = 0; idx < modules.length && !aborted; idx++) {
721: modules[idx].trailerHandler(response, request);
722: }
723:
724: trailers_handled = true;
725: }
726:
727: /**
728: * Mark this request as having been aborted. It's invoked by
729: * HTTPConnection.stop().
730: */
731: void markAborted() {
732: aborted = true;
733: }
734:
735: /**
736: * Gets any trailers from the response if we haven't already done so.
737: */
738: private synchronized void getTrailers() throws IOException,
739: ModuleException {
740: if (got_trailers)
741: return;
742: if (!initialized)
743: handleResponse();
744:
745: response.getTrailer("Any");
746: Trailers = response.Trailers;
747: got_trailers = true;
748:
749: invokeTrailerHandlers(false);
750: }
751:
752: /**
753: * Reads the response data received. Does not return until either
754: * Content-Length bytes have been read or EOF is reached.
755: *
756: * @inp the input stream from which to read the data
757: * @exception IOException if any read on the input stream fails
758: */
759: private void readResponseData(InputStream inp) throws IOException,
760: ModuleException {
761: if (ContentLength == 0)
762: return;
763:
764: if (Data == null)
765: Data = new byte[0];
766:
767: // read response data
768:
769: int off = Data.length;
770:
771: try {
772: // check Content-length header in case CE-Module removed it
773: if (getHeader("Content-Length") != null) {
774: int rcvd = 0;
775: Data = new byte[ContentLength];
776:
777: do {
778: off += rcvd;
779: rcvd = inp.read(Data, off, ContentLength - off);
780: } while (rcvd != -1 && off + rcvd < ContentLength);
781:
782: /* Don't do this!
783: * If we do, then getData() won't work after a getInputStream()
784: * because we'll never get all the expected data. Instead, let
785: * the underlying RespInputStream throw the EOF.
786: if (rcvd == -1) // premature EOF
787: {
788: throw new EOFException("Encountered premature EOF while " +
789: "reading headers: received " + off +
790: " bytes instead of the expected " +
791: ContentLength + " bytes");
792: }
793: */
794: } else {
795: int inc = 1000, rcvd = 0;
796:
797: do {
798: off += rcvd;
799: Data = Util.resizeArray(Data, off + inc);
800: } while ((rcvd = inp.read(Data, off, inc)) != -1);
801:
802: Data = Util.resizeArray(Data, off);
803: }
804: } catch (IOException ioe) {
805: Data = Util.resizeArray(Data, off);
806: throw ioe;
807: } finally {
808: try {
809: inp.close();
810: } catch (IOException ioe) {
811: }
812: }
813: }
814:
815: int getTimeout() {
816: return timeout;
817: }
818: }
|