1 /*
2 * @(#)HTTPResponse.java 0.3-3 06/05/2001
3 *
4 * This file is part of the HTTPClient package
5 * Copyright (C) 1996-2001 Ronald Tschalär
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 * This file contains modifications for use with "The Grinder"
32 * (http://grinder.sourceforge.net) under the terms of the LGPL. They
33 * are marked below with the comment "GRINDER MODIFICATION".
34 *
35 */
36
37 package HTTPClient;
38
39 import java.io.IOException;
40 import java.io.InterruptedIOException;
41 import java.io.InputStream;
42 import java.io.ByteArrayInputStream;
43 import java.net.URL;
44 import java.util.Date;
45 import java.util.Enumeration;
46
47
48
49 /**
50 * This defines the http-response class returned by the requests. It's
51 * basically a wrapper around the Response class which first lets all
52 * the modules handle the response before finally giving the info to
53 * the user.
54 *
55 * @version 0.3-3 06/05/2001
56 * @author Ronald Tschalär
57 * @since 0.3
58 */
59 public class HTTPResponse implements HTTPClientModuleConstants
60 {
61 /** the list of modules */
62 private HTTPClientModule[] modules;
63
64 /** the timeout for reads */
65 private int timeout;
66
67 /** the request */
68 private Request request = null;
69
70 /** the current response */
71 Response response = null;
72
73 /** the HttpOutputStream to synchronize on */
74 private HttpOutputStream out_stream = null;
75
76 /** our input stream from the stream demux */
77 private InputStream inp_stream;
78
79 /** the status code returned. */
80 private int StatusCode;
81
82 /** the reason line associated with the status code. */
83 private String ReasonLine;
84
85 /** the HTTP version of the response. */
86 private String Version;
87
88 /** the original URI used. */
89 private URI OriginalURI = null;
90
91 /** the final URI of the document. */
92 private URI EffectiveURI = null;
93
94 /** any headers which were received and do not fit in the above list. */
95 private CIHashtable Headers = null;
96
97 /** any trailers which were received and do not fit in the above list. */
98 private CIHashtable Trailers = null;
99
100 /** the ContentLength of the data. */
101 private int ContentLength = -1;
102
103 /** the data (body) returned. */
104 private byte[] Data = null;
105
106 /** signals if we have got and parsed the headers yet? */
107 private boolean initialized = false;
108
109 /** signals if we have got the trailers yet? */
110 private boolean got_trailers = false;
111
112 /** marks this response as aborted (stop() in HTTPConnection) */
113 private boolean aborted = false;
114
115 /** should the request be retried by the application? */
116 private boolean retry = false;
117
118 /** the method used in the request */
119 private String method = null;
120
121 /** ++GRINDER MODIFICATION **/
122 /** The time to first byte */
123 private long ttfb;
124 /** --GRINDER MODIFICATION **/
125
126 // Constructors
127
128 /**
129 * Creates a new HTTPResponse.
130 *
131 * @param modules the list of modules handling this response
132 * @param timeout the timeout to be used on stream read()'s
133 */
134 HTTPResponse(HTTPClientModule[] modules, int timeout, Request orig)
135 {
136 this.modules = modules;
137 this.timeout = timeout;
138 try
139 {
140 int qp = orig.getRequestURI().indexOf('?');
141 this.OriginalURI = new URI(orig.getConnection().getProtocol(),
142 null,
143 orig.getConnection().getHost(),
144 orig.getConnection().getPort(),
145 qp < 0 ? orig.getRequestURI() :
146 orig.getRequestURI().substring(0, qp),
147 qp < 0 ? null :
148 orig.getRequestURI().substring(qp+1),
149 null);
150 }
151 catch (ParseException pe)
152 { }
153 this.method = orig.getMethod();
154 }
155
156
157 /**
158 * @param req the request
159 * @param resp the response
160 */
161 void set(Request req, Response resp)
162 {
163 this.request = req;
164 this.response = resp;
165 resp.http_resp = this;
166 resp.timeout = timeout;
167 this.aborted = resp.final_resp;
168 }
169
170
171 /**
172 * @param req the request
173 * @param resp the response
174 */
175 void set(Request req, HttpOutputStream out_stream)
176 {
177 this.request = req;
178 this.out_stream = out_stream;
179 }
180
181
182 // Methods
183
184 /**
185 * Give the status code for this request. These are grouped as follows:
186 * <UL>
187 * <LI> 1xx - Informational (new in HTTP/1.1)
188 * <LI> 2xx - Success
189 * <LI> 3xx - Redirection
190 * <LI> 4xx - Client Error
191 * <LI> 5xx - Server Error
192 * </UL>
193 *
194 * @exception IOException if any exception occurs on the socket.
195 * @exception ModuleException if any module encounters an exception.
196 */
197 public final int getStatusCode() throws IOException, ModuleException
198 {
199 if (!initialized) handleResponse();
200 return StatusCode;
201 }
202
203 /**
204 * Give the reason line associated with the status code.
205 *
206 * @exception IOException If any exception occurs on the socket.
207 * @exception ModuleException if any module encounters an exception.
208 */
209 public final String getReasonLine() throws IOException, ModuleException
210 {
211 if (!initialized) handleResponse();
212 return ReasonLine;
213 }
214
215 /**
216 * Get the HTTP version used for the response.
217 *
218 * @exception IOException If any exception occurs on the socket.
219 * @exception ModuleException if any module encounters an exception.
220 */
221 public final String getVersion() throws IOException, ModuleException
222 {
223 if (!initialized) handleResponse();
224 return Version;
225 }
226
227 /**
228 * Get the name and type of server.
229 *
230 * @deprecated This method is a remnant of V0.1; use
231 * <code>getHeader("Server")</code> instead.
232 * @see #getHeader(java.lang.String)
233 * @exception IOException If any exception occurs on the socket.
234 * @exception ModuleException if any module encounters an exception.
235 */
236 public final String getServer() throws IOException, ModuleException
237 {
238 if (!initialized) handleResponse();
239 return getHeader("Server");
240 }
241
242
243 /**
244 * Get the original URI used in the request.
245 *
246 * @return the URI used in primary request
247 */
248 public final URI getOriginalURI()
249 {
250 return OriginalURI;
251 }
252
253
254 /**
255 * Get the final URL of the document. This is set if the original
256 * request was deferred via the "moved" (301, 302, or 303) return
257 * status.
258 *
259 * @return the effective URL, or null if no redirection occured
260 * @exception IOException If any exception occurs on the socket.
261 * @exception ModuleException if any module encounters an exception.
262 * @deprecated use getEffectiveURI() instead
263 * @see #getEffectiveURI
264 */
265 public final URL getEffectiveURL() throws IOException, ModuleException
266 {
267 if (!initialized) handleResponse();
268 if (EffectiveURI != null)
269 return EffectiveURI.toURL();
270 return null;
271 }
272
273
274 /**
275 * Get the final URI of the document. If the request was redirected
276 * via the "moved" (301, 302, 303, or 307) return status this returns
277 * the URI used in the last redirection; otherwise it returns the
278 * original URI.
279 *
280 * @return the effective URI
281 * @exception IOException If any exception occurs on the socket.
282 * @exception ModuleException if any module encounters an exception.
283 */
284 public final URI getEffectiveURI() throws IOException, ModuleException
285 {
286 if (!initialized) handleResponse();
287 if (EffectiveURI != null)
288 return EffectiveURI;
289 return OriginalURI;
290 }
291
292
293 /**
294 * Retrieves the value for a given header.
295 *
296 * @param hdr the header name.
297 * @return the value for the header, or null if non-existent.
298 * @exception IOException If any exception occurs on the socket.
299 * @exception ModuleException if any module encounters an exception.
300 */
301 public String getHeader(String hdr) throws IOException, ModuleException
302 {
303 if (!initialized) handleResponse();
304 return (String) Headers.get(hdr.trim());
305 }
306
307 /**
308 * Retrieves the value for a given header. The value is parsed as an
309 * int.
310 *
311 * @param hdr the header name.
312 * @return the value for the header if the header exists
313 * @exception NumberFormatException if the header's value is not a number
314 * or if the header does not exist.
315 * @exception IOException if any exception occurs on the socket.
316 * @exception ModuleException if any module encounters an exception.
317 */
318 public int getHeaderAsInt(String hdr)
319 throws IOException, ModuleException, NumberFormatException
320 {
321 String val = getHeader(hdr);
322 if (val == null)
323 throw new NumberFormatException("null");
324 return Integer.parseInt(val);
325 }
326
327 /**
328 * Retrieves the value for a given header. The value is parsed as a
329 * date; if this fails it is parsed as a long representing the number
330 * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
331 * exception is thrown.
332 * <br>Note: When sending dates use Util.httpDate().
333 *
334 * @param hdr the header name.
335 * @return the value for the header, or null if non-existent.
336 * @exception IllegalArgumentException if the header's value is neither a
337 * legal date nor a number.
338 * @exception IOException if any exception occurs on the socket.
339 * @exception ModuleException if any module encounters an exception.
340 */
341 public Date getHeaderAsDate(String hdr)
342 throws IOException, IllegalArgumentException, ModuleException
343 {
344 String raw_date = getHeader(hdr);
345 if (raw_date == null) return null;
346
347 // asctime() format is missing an explicit GMT specifier
348 if (raw_date.toUpperCase().indexOf("GMT") == -1 &&
349 raw_date.indexOf(' ') > 0)
350 raw_date += " GMT";
351
352 Date date;
353
354 try
355 { date = Util.parseHttpDate(raw_date); }
356 catch (IllegalArgumentException iae)
357 {
358 // some servers erroneously send a number, so let's try that
359 long time;
360 try
361 { time = Long.parseLong(raw_date); }
362 catch (NumberFormatException nfe)
363 { throw iae; } // give up
364 if (time < 0) time = 0;
365 date = new Date(time * 1000L);
366 }
367
368 return date;
369 }
370
371 /**
372 * Returns an enumeration of all the headers available via getHeader().
373 *
374 * @exception IOException If any exception occurs on the socket.
375 * @exception ModuleException if any module encounters an exception.
376 */
377 public Enumeration listHeaders() throws IOException, ModuleException
378 {
379 if (!initialized) handleResponse();
380 return Headers.keys();
381 }
382
383 /** ++GRINDER MODIFICATION **/
384 public long getTimeToFirstByte(){
385 return ttfb;
386 }
387 /** --GRINDER MODIFICATION **/
388
389 /**
390 * Retrieves the value for a given trailer. This should not be invoked
391 * until all response data has been read. If invoked before it will
392 * call <code>getData()</code> to force the data to be read.
393 *
394 * @param trailer the trailer name.
395 * @return the value for the trailer, or null if non-existent.
396 * @exception IOException If any exception occurs on the socket.
397 * @exception ModuleException if any module encounters an exception.
398 * @see #getData()
399 */
400 public String getTrailer(String trailer) throws IOException, ModuleException
401 {
402 if (!got_trailers) getTrailers();
403 return (String) Trailers.get(trailer.trim());
404 }
405
406 /**
407 * Retrieves the value for a given tailer. The value is parsed as an
408 * int.
409 *
410 * @param trailer the tailer name.
411 * @return the value for the trailer if the trailer exists
412 * @exception NumberFormatException if the trailer's value is not a number
413 * or if the trailer does not exist.
414 * @exception IOException if any exception occurs on the socket.
415 * @exception ModuleException if any module encounters an exception.
416 */
417 public int getTrailerAsInt(String trailer)
418 throws IOException, ModuleException, NumberFormatException
419 {
420 String val = getTrailer(trailer);
421 if (val == null)
422 throw new NumberFormatException("null");
423 return Integer.parseInt(val);
424 }
425
426 /**
427 * Retrieves the value for a given trailer. The value is parsed as a
428 * date; if this fails it is parsed as a long representing the number
429 * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
430 * IllegalArgumentException is thrown.
431 * <br>Note: When sending dates use Util.httpDate().
432 *
433 * @param trailer the trailer name.
434 * @return the value for the trailer, or null if non-existent.
435 * @exception IllegalArgumentException if the trailer's value is neither a
436 * legal date nor a number.
437 * @exception IOException if any exception occurs on the socket.
438 * @exception ModuleException if any module encounters an exception.
439 */
440 public Date getTrailerAsDate(String trailer)
441 throws IOException, IllegalArgumentException, ModuleException
442 {
443 String raw_date = getTrailer(trailer);
444 if (raw_date == null) return null;
445
446 // asctime() format is missing an explicit GMT specifier
447 if (raw_date.toUpperCase().indexOf("GMT") == -1 &&
448 raw_date.indexOf(' ') > 0)
449 raw_date += " GMT";
450
451 Date date;
452
453 try
454 { date = Util.parseHttpDate(raw_date); }
455 catch (IllegalArgumentException iae)
456 {
457 // some servers erroneously send a number, so let's try that
458 long time;
459 try
460 { time = Long.parseLong(raw_date); }
461 catch (NumberFormatException nfe)
462 { throw iae; } // give up
463 if (time < 0) time = 0;
464 date = new Date(time * 1000L);
465 }
466
467 return date;
468 }
469
470 /**
471 * Returns an enumeration of all the trailers available via getTrailer().
472 *
473 * @exception IOException If any exception occurs on the socket.
474 * @exception ModuleException if any module encounters an exception.
475 */
476 public Enumeration listTrailers() throws IOException, ModuleException
477 {
478 if (!got_trailers) getTrailers();
479 return Trailers.keys();
480 }
481
482
483 /**
484 * Reads all the response data into a byte array. Note that this method
485 * won't return until <em>all</em> the data has been received (so for
486 * instance don't invoke this method if the server is doing a server
487 * push). If <code>getInputStream()</code> had been previously invoked
488 * then this method only returns any unread data remaining on the stream
489 * and then closes it.
490 *
491 * <P>Note to the unwary: code like
492 *<PRE>
493 * System.out.println("The data: " + resp.getData())
494 *</PRE>
495 * will probably not do what you want - use
496 *<PRE>
497 * System.out.println("The data: " + resp.getText())
498 *</PRE>
499 * instead.
500 *
501 * @see #getInputStream()
502 * @return an array containing the data (body) returned. If no data
503 * was returned then it's set to a zero-length array.
504 * @exception IOException If any io exception occured while reading
505 * the data
506 * @exception ModuleException if any module encounters an exception.
507 */
508 public synchronized byte[] getData() throws IOException, ModuleException
509 {
510 if (!initialized) handleResponse();
511
512 if (Data == null)
513 {
514 try
515 { readResponseData(inp_stream); }
516 catch (InterruptedIOException ie) // don't intercept
517 { throw ie; }
518 catch (IOException ioe)
519 {
520 Log.write(Log.RESP, "HResp: (\"" + method + " " +
521 OriginalURI.getPathAndQuery() + "\")");
522 Log.write(Log.RESP, " ", ioe);
523
524 try { inp_stream.close(); } catch (Exception e) { }
525 throw ioe;
526 }
527
528 inp_stream.close();
529 }
530
531 return Data;
532 }
533
534 /**
535 * Reads all the response data into a buffer and turns it into a string
536 * using the appropriate character converter. Since this uses {@link
537 * #getData() getData()}, the caveats of that method apply here as well.
538 *
539 * @see #getData()
540 * @return the body as a String. If no data was returned then an empty
541 * string is returned.
542 * @exception IOException If any io exception occured while reading
543 * the data, or if the content is not text
544 * @exception ModuleException if any module encounters an exception.
545 * @exception ParseException if an error occured trying to parse the
546 * content-type header field
547 */
548 public synchronized String getText()
549 throws IOException, ModuleException, ParseException
550 {
551 String ct = getHeader("Content-Type");
552
553 /** ++GRINDER MODIFICATION **/
554 if (ct == null) {
555 return new String(getData(), "ISO-8859-1");
556 }
557
558 // if (ct == null || !ct.toLowerCase().startsWith("text/"))
559 // throw new IOException("Content-Type `" + ct + "' is not a text type");
560 /** --GRINDER MODIFICATION **/
561
562 String charset = Util.getParameter("charset", ct);
563 if (charset == null)
564 charset = "ISO-8859-1";
565
566 return new String(getData(), charset);
567 }
568
569 /**
570 * Gets an input stream from which the returned data can be read. Note
571 * that if <code>getData()</code> had been previously invoked it will
572 * actually return a ByteArrayInputStream created from that data.
573 *
574 * @see #getData()
575 * @return the InputStream.
576 * @exception IOException If any exception occurs on the socket.
577 * @exception ModuleException if any module encounters an exception.
578 */
579 public synchronized InputStream getInputStream()
580 throws IOException, ModuleException
581 {
582 if (!initialized) handleResponse();
583
584 if (Data == null)
585 return inp_stream;
586 else
587 {
588 getData(); // ensure complete data is read
589 return new ByteArrayInputStream(Data);
590 }
591 }
592
593
594 /**
595 * Should the request be retried by the application? If the application
596 * used an <var>HttpOutputStream</var> in the request then various
597 * modules (such as the redirection and authorization modules) are not
598 * able to resend the request themselves. Instead, it becomes the
599 * application's responsibility. The application can check this flag, and
600 * if it's set, resend the exact same request. The modules such as the
601 * RedirectionModule or AuthorizationModule will then recognize the resend
602 * and fix up or redirect the request as required (i.e. they defer their
603 * normal action until the resend).
604 *
605 * <P>If the application resends the request then it <strong>must</strong>
606 * use the same <var>HttpOutputStream</var> instance. This is because the
607 * modules use this to recognize the retried request and to perform the
608 * necessary work on the request before it's sent.
609 *
610 * <P>Here is a skeleton example of usage:
611 * <PRE>
612 * OutputStream out = new HttpOutputStream(1234);
613 * do
614 * {
615 * rsp = con.Post("/cgi-bin/my_cgi", out);
616 * out.write(...);
617 * out.close();
618 * } while (rsp.retryRequest());
619 *
620 * if (rsp.getStatusCode() >= 300)
621 * ...
622 * </PRE>
623 *
624 * <P>Note that for this to ever return true, the java system property
625 * <var>HTTPClient.deferStreamed</var> must be set to true at the beginning
626 * of the application (before the HTTPConnection class is loaded). This
627 * prevents unwary applications from causing inadvertent memory leaks. If
628 * an application does set this, then it <em>must</em> resend any request
629 * whose response returns true here in order to prevent memory leaks (a
630 * switch to JDK 1.2 will allow us to use weak references and eliminate
631 * this problem).
632 *
633 * @return true if the request should be retried.
634 * @exception IOException If any exception occurs on the socket.
635 * @exception ModuleException if any module encounters an exception.
636 */
637 public boolean retryRequest() throws IOException, ModuleException
638 {
639 if (!initialized)
640 {
641 try
642 { handleResponse(); }
643 catch (RetryException re)
644 { this.retry = response.retry; }
645 }
646 return retry;
647 }
648
649
650 /**
651 * produces a full list of headers and their values, one per line.
652 *
653 * @return a string containing the headers
654 */
655 public String toString()
656 {
657 if (!initialized)
658 {
659 try
660 { handleResponse(); }
661 catch (Exception e)
662 {
663 if (!(e instanceof InterruptedIOException))
664 {
665 Log.write(Log.RESP, "HResp: (\"" + method + " " +
666 OriginalURI.getPathAndQuery() + "\")");
667 Log.write(Log.RESP, " ", e);
668 }
669 return "Failed to read headers: " + e;
670 }
671 }
672
673 String nl = System.getProperty("line.separator", "\n");
674
675 StringBuffer str = new StringBuffer(Version);
676 str.append(' ');
677 str.append(StatusCode);
678 str.append(' ');
679 str.append(ReasonLine);
680 str.append(nl);
681
682 if (EffectiveURI != null)
683 {
684 str.append("Effective-URI: ");
685 str.append(EffectiveURI);
686 str.append(nl);
687 }
688
689 Enumeration hdr_list = Headers.keys();
690 while (hdr_list.hasMoreElements())
691 {
692 String hdr = (String) hdr_list.nextElement();
693 str.append(hdr);
694 str.append(": ");
695 str.append(Headers.get(hdr));
696 str.append(nl);
697 }
698
699 return str.toString();
700 }
701
702
703 // Helper Methods
704
705
706 HTTPClientModule[] getModules()
707 {
708 return modules;
709 }
710
711
712 /**
713 * Processes a Response. This is done by calling the response handler
714 * in each module. When all is done, the various fields of this instance
715 * are intialized from the last Response.
716 *
717 * @exception IOException if any handler throws an IOException.
718 * @exception ModuleException if any module encounters an exception.
719 * @return true if a new request was generated. This is used for internal
720 * subrequests only
721 */
722 synchronized boolean handleResponse() throws IOException, ModuleException
723 {
724 if (initialized) return false;
725
726
727 /* first get the response if necessary */
728
729 if (out_stream != null)
730 {
731 response = out_stream.getResponse();
732 response.http_resp = this;
733 out_stream = null;
734 }
735
736
737 /* go through modules and handle them */
738
739 doModules: while (true)
740 {
741
742 Phase1: for (int idx=0; idx<modules.length && !aborted; idx++)
743 {
744 try
745 { modules[idx].responsePhase1Handler(response, request); }
746 catch (RetryException re)
747 {
748 if (re.restart)
749 continue doModules;
750 else
751 throw re;
752 }
753 }
754
755 Phase2: for (int idx=0; idx<modules.length && !aborted; idx++)
756 {
757 int sts = modules[idx].responsePhase2Handler(response, request);
758 switch (sts)
759 {
760 case RSP_CONTINUE: // continue processing
761 break;
762
763 case RSP_RESTART: // restart response processing
764 idx = -1;
765 continue doModules;
766
767 case RSP_SHORTCIRC: // stop processing and return
768 break doModules;
769
770 case RSP_REQUEST: // go to phase 1
771 case RSP_NEWCON_REQ: // process the request using a new con
772 response.getInputStream().close();
773 if (handle_trailers) invokeTrailerHandlers(true);
774 if (request.internal_subrequest) return true;
775 request.getConnection().
776 handleRequest(request, this, response, true);
777 if (initialized) break doModules;
778
779 idx = -1;
780 continue doModules;
781
782 case RSP_SEND: // send the request immediately
783 case RSP_NEWCON_SND: // send the request using a new con
784 response.getInputStream().close();
785 if (handle_trailers) invokeTrailerHandlers(true);
786 if (request.internal_subrequest) return true;
787 request.getConnection().
788 handleRequest(request, this, response, false);
789 idx = -1;
790 continue doModules;
791
792 default: // not valid
793 throw new Error("HTTPClient Internal Error: invalid status"+
794 " " + sts + " returned by module " +
795 modules[idx].getClass().getName());
796 }
797 }
798
799 Phase3: for (int idx=0; idx<modules.length && !aborted; idx++)
800 {
801 modules[idx].responsePhase3Handler(response, request);
802 }
803
804 break doModules;
805 }
806
807 /* force a read on the response in case none of the modules did */
808 response.getStatusCode();
809
810 /* all done, so copy data */
811 if (!request.internal_subrequest)
812 init(response);
813
814 if (handle_trailers)
815 invokeTrailerHandlers(false);
816
817 return false;
818 }
819
820
821 /**
822 * Copies the relevant fields from Response and marks this as initialized.
823 *
824 * @param resp the Response class to copy from
825 */
826 void init(Response resp)
827 {
828 if (initialized) return;
829
830 this.StatusCode = resp.StatusCode;
831 this.ReasonLine = resp.ReasonLine;
832 this.Version = resp.Version;
833 this.EffectiveURI = resp.EffectiveURI;
834 this.ContentLength = resp.ContentLength;
835 this.Headers = resp.Headers;
836 this.inp_stream = resp.inp_stream;
837 this.Data = resp.Data;
838 this.retry = resp.retry;
839 this.ttfb = resp.getTtfb();
840 initialized = true;
841 }
842
843
844 private boolean handle_trailers = false;
845 private boolean trailers_handled = false;
846
847 /**
848 * This is invoked by the RespInputStream when it is close()'d. It
849 * just invokes the trailer handler in each module.
850 *
851 * @param force invoke the handlers even if not initialized yet?
852 * @exception IOException if thrown by any module
853 * @exception ModuleException if thrown by any module
854 */
855 void invokeTrailerHandlers(boolean force)
856 throws IOException, ModuleException
857 {
858 if (trailers_handled) return;
859
860 if (!force && !initialized)
861 {
862 handle_trailers = true;
863 return;
864 }
865
866 for (int idx=0; idx<modules.length && !aborted; idx++)
867 {
868 modules[idx].trailerHandler(response, request);
869 }
870
871 trailers_handled = true;
872 }
873
874
875 /**
876 * Mark this request as having been aborted. It's invoked by
877 * HTTPConnection.stop().
878 */
879 void markAborted()
880 {
881 aborted = true;
882 }
883
884
885 /**
886 * Gets any trailers from the response if we haven't already done so.
887 */
888 private synchronized void getTrailers() throws IOException, ModuleException
889 {
890 if (got_trailers) return;
891 if (!initialized) handleResponse();
892
893 response.getTrailer("Any");
894 Trailers = response.Trailers;
895 got_trailers = true;
896
897 invokeTrailerHandlers(false);
898 }
899
900
901 /**
902 * Reads the response data received. Does not return until either
903 * Content-Length bytes have been read or EOF is reached.
904 *
905 * @inp the input stream from which to read the data
906 * @exception IOException if any read on the input stream fails
907 */
908 private void readResponseData(InputStream inp)
909 throws IOException, ModuleException
910 {
911 /** ++GRINDER MODIFICATION * */
912 // if (ContentLength == 0)
913 // return;
914 /** --GRINDER MODIFICATION * */
915
916 if (Data == null)
917 Data = new byte[0];
918
919 /** ++GRINDER MODIFICATION * */
920 if (ContentLength == 0)
921 return;
922 /** --GRINDER MODIFICATION * */
923
924 // read response data
925 int off = Data.length;
926
927 try {
928 /** ++GRINDER MODIFICATION * */
929 // // check Content-length header in case CE-Module removed it
930 // if (getHeader("Content-Length") != null)
931 // {
932 // int rcvd = 0;
933 // Data = new byte[ContentLength];
934 //
935 // do
936 // {
937 // off += rcvd;
938 // rcvd = inp.read(Data, off, ContentLength-off);
939 // } while (rcvd != -1 && off+rcvd < ContentLength);
940 //
941 // /* Don't do this!
942 // * If we do, then getData() won't work after a getInputStream()
943 // * because we'll never get all the expected data. Instead, let
944 // * the underlying RespInputStream throw the EOF.
945 // if (rcvd == -1) // premature EOF
946 // {
947 // throw new EOFException("Encountered premature EOF while " +
948 // "reading headers: received " + off +
949 // " bytes instead of the expected " +
950 // ContentLength + " bytes");
951 // }
952 // */
953 // }
954 // else
955 // {
956 // int inc = 1000,
957 // rcvd = 0;
958 //
959 // do
960 // {
961 // off += rcvd;
962 // Data = Util.resizeArray(Data, off+inc);
963 // } while ((rcvd = inp.read(Data, off, inc)) != -1);
964 //
965 // Data = Util.resizeArray(Data, off);
966 // }
967
968 final HTTPConnection.BandwidthLimiterFactory
969 bandwidthLimiterFactory =
970 request.getConnection().getBandwidthLimiterFactory();
971
972 final HTTPConnection.BandwidthLimiter bandwidthLimiter =
973 bandwidthLimiterFactory.create();
974
975 final boolean fixedSize;
976
977 // Check Content-length header in case CE-Module removed it.
978 if (getHeader("Content-Length") != null) {
979 // As per the original code, we don't raise problems about unexpected
980 // EOFs if the available data doesn't match the content length.
981 Data = new byte[ContentLength];
982 fixedSize = true;
983 }
984 else {
985 Data = new byte[1000];
986 fixedSize = false;
987 }
988
989 int rcvd = 0;
990
991 do {
992 off += rcvd;
993
994 if (fixedSize) {
995 if (off >= Data.length) {
996 break;
997 }
998 }
999 else {
1000 // Grow exponentially so that the number of copies for an N byte
1001 // response is O(ln N). We resize every time we've used at least half
1002 // the remaining bytes.
1003 if ((Data.length - off) < Data.length / 2) {
1004 Data = Util.resizeArray(Data, Data.length * 2);
1005 }
1006 }
1007
1008 final int maximumBytes =
1009 Math.min(Data.length - off, bandwidthLimiter.maximumBytes(off));
1010
1011 rcvd = inp.read(Data, off, maximumBytes);
1012 }
1013 while (rcvd != -1);
1014
1015 if (off < Data.length) {
1016 Data = Util.resizeArray(Data, off);
1017 }
1018
1019 /** --GRINDER MODIFICATION * */
1020 }
1021 catch (IOException ioe)
1022 {
1023 Data = Util.resizeArray(Data, off);
1024 throw ioe;
1025 }
1026 finally
1027 {
1028 try
1029 { inp.close(); }
1030 catch (IOException ioe)
1031 { }
1032 }
1033 }
1034
1035
1036 int getTimeout()
1037 {
1038 return timeout;
1039 }
1040 }