KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > Response


1 /*
2  * @(#)Response.java 0.3-2 18/06/1999
3  *
4  * This file is part of the HTTPClient package
5  * Copyright (C) 1996-1999 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  */

28
29 package HTTPClient;
30
31 import java.io.InputStream JavaDoc;
32 import java.io.SequenceInputStream JavaDoc;
33 import java.io.ByteArrayInputStream JavaDoc;
34 import java.io.IOException JavaDoc;
35 import java.io.InterruptedIOException JavaDoc;
36 import java.io.EOFException JavaDoc;
37 import java.net.URL JavaDoc;
38 import java.net.ProtocolException JavaDoc;
39 import java.util.Date JavaDoc;
40 import java.util.Vector JavaDoc;
41 import java.util.StringTokenizer JavaDoc;
42 import java.util.NoSuchElementException JavaDoc;
43
44
45 /**
46  * This class represents an intermediate response. It's used internally by the
47  * modules. When all modules have handled the response then the HTTPResponse
48  * fills in its fields with the data from this class.
49  *
50  * @version 0.3-2 18/06/1999
51  * @author Ronald Tschalär
52  */

53
54 public final class Response implements RoResponse, GlobalConstants
55 {
56     /** our http connection */
57     private HTTPConnection connection;
58
59     /** our stream demux */
60     private StreamDemultiplexor stream_handler;
61
62     /** the HTTPResponse we're coupled with */
63             HTTPResponse http_resp;
64
65     /** the timeout for read operations */
66             int timeout = 0;
67
68     /** our input stream (usually from the stream demux). Push input streams
69      * onto this if necessary. */

70     public InputStream JavaDoc inp_stream;
71
72     /** our response input stream from the stream demux */
73     private RespInputStream resp_inp_stream = null;
74
75     /** the method used in the request */
76     private String JavaDoc method;
77
78     /** the resource in the request (for debugging purposes) */
79             String JavaDoc resource;
80
81     /** was a proxy used for the request? */
82     private boolean used_proxy;
83
84     /** did the request contain an entity? */
85     private boolean sent_entity;
86
87     /** the status code returned. */
88             int StatusCode = 0;
89
90     /** the reason line associated with the status code. */
91             String JavaDoc ReasonLine;
92
93     /** the HTTP version of the response. */
94             String JavaDoc Version;
95
96     /** the final URI of the document. */
97             URI EffectiveURI = null;
98
99     /** any headers which were received and do not fit in the above list. */
100             CIHashtable Headers = new CIHashtable();
101
102     /** any trailers which were received and do not fit in the above list. */
103             CIHashtable Trailers = new CIHashtable();
104
105     /** the message length of the response if either there is no data (in which
106      * case ContentLength=0) or if the message length is controlled by a
107      * Content-Length header. If neither of these, then it's -1 */

108             int ContentLength = -1;
109
110     /** this indicates how the length of the entity body is determined */
111             int cd_type = CD_HDRS;
112
113     /** the data (body) returned. */
114             byte[] Data = null;
115
116     /** signals if we in the process of reading the headers */
117             boolean reading_headers = false;
118
119     /** signals if we have got and parsed the headers yet */
120             boolean got_headers = false;
121
122     /** signals if we have got and parsed the trailers yet */
123             boolean got_trailers = false;
124
125     /** remembers any exception received while reading/parsing headers */
126     private IOException JavaDoc exception = null;
127
128     /** should this response be handled further? */
129             boolean final_resp = false;
130
131
132     // Constructors
133

134     /**
135      * Creates a new Response and registers it with the stream-demultiplexor.
136      */

137     Response(Request request, boolean used_proxy,
138          StreamDemultiplexor stream_handler)
139         throws IOException JavaDoc
140     {
141     this.connection = request.getConnection();
142     this.method = request.getMethod();
143     this.resource = request.getRequestURI();
144     this.used_proxy = used_proxy;
145     this.stream_handler = stream_handler;
146     sent_entity = (request.getData() != null) ? true : false;
147
148     stream_handler.register(this, request);
149     resp_inp_stream = stream_handler.getStream(this);
150     inp_stream = resp_inp_stream;
151     }
152
153
154     /**
155      * Creates a new Response that reads from the given stream. This is
156      * used for the CONNECT subrequest which is used in establishing an
157      * SSL tunnel through a proxy.
158      *
159      * @param request the subrequest
160      * @param is the input stream from which to read the headers and
161      * data.
162      */

163     Response(Request request, InputStream JavaDoc is) throws IOException JavaDoc
164     {
165     this.connection = request.getConnection();
166     this.method = request.getMethod();
167     this.resource = request.getRequestURI();
168     used_proxy = false;
169     stream_handler = null;
170     sent_entity = (request.getData() != null) ? true : false;
171     inp_stream = is;
172     }
173
174
175     /**
176      * Create a new response with the given info. This is used when
177      * creating a response in a requestHandler().
178      *
179      * <P>If <var>data</var> is not null then that is used; else if the
180      * <var>is</var> is not null that is used; else the entity is empty.
181      * If the input stream is used then <var>cont_len</var> specifies
182      * the length of the data that can be read from it, or -1 if unknown.
183      *
184      * @param version the response version (such as "HTTP/1.1")
185      * @param status the status code
186      * @param reason the reason line
187      * @param headers the response headers
188      * @param data the response entity
189      * @param is the response entity as an InputStream
190      * @param cont_len the length of the data in the InputStream
191      */

192     public Response(String JavaDoc version, int status, String JavaDoc reason, NVPair[] headers,
193             byte[] data, InputStream JavaDoc is, int cont_len)
194     {
195     this.Version = version;
196     this.StatusCode = status;
197     this.ReasonLine = reason;
198     if (headers != null)
199         for (int idx=0; idx<headers.length; idx++)
200         setHeader(headers[idx].getName(), headers[idx].getValue());
201     if (data != null)
202         this.Data = data;
203     else if (is == null)
204         this.Data = new byte[0];
205     else
206     {
207         this.inp_stream = is;
208         ContentLength = cont_len;
209     }
210
211     got_headers = true;
212     got_trailers = true;
213     }
214
215
216     // Methods
217

218     /**
219      * give the status code for this request. These are grouped as follows:
220      * <UL>
221      * <LI> 1xx - Informational (new in HTTP/1.1)
222      * <LI> 2xx - Success
223      * <LI> 3xx - Redirection
224      * <LI> 4xx - Client Error
225      * <LI> 5xx - Server Error
226      * </UL>
227      *
228      * @exception IOException If any exception occurs on the socket.
229      */

230     public final int getStatusCode() throws IOException JavaDoc
231     {
232     if (!got_headers) getHeaders(true);
233     return StatusCode;
234     }
235
236     /**
237      * give the reason line associated with the status code.
238      *
239      * @exception IOException If any exception occurs on the socket.
240      */

241     public final String JavaDoc getReasonLine() throws IOException JavaDoc
242     {
243     if (!got_headers) getHeaders(true);
244     return ReasonLine;
245     }
246
247     /**
248      * get the HTTP version used for the response.
249      *
250      * @exception IOException If any exception occurs on the socket.
251      */

252     public final String JavaDoc getVersion() throws IOException JavaDoc
253     {
254     if (!got_headers) getHeaders(true);
255     return Version;
256     }
257
258     /**
259      * Wait for either a '100 Continue' or an error.
260      *
261      * @return the return status.
262      */

263     int getContinue() throws IOException JavaDoc
264     {
265     getHeaders(false);
266     return StatusCode;
267     }
268
269     /**
270      * get the final URI of the document. This is set if the original
271      * request was deferred via the "moved" (301, 302, or 303) return
272      * status.
273      *
274      * @return the new URI, or null if not redirected
275      * @exception IOException If any exception occurs on the socket.
276      */

277     public final URI getEffectiveURI() throws IOException JavaDoc
278     {
279     if (!got_headers) getHeaders(true);
280     return EffectiveURI;
281     }
282
283     /**
284      * set the final URI of the document. This is only for internal use.
285      */

286     public void setEffectiveURI(URI final_uri)
287     {
288     EffectiveURI = final_uri;
289     }
290
291     /**
292      * get the final URL of the document. This is set if the original
293      * request was deferred via the "moved" (301, 302, or 303) return
294      * status.
295      *
296      * @exception IOException If any exception occurs on the socket.
297      * @deprecated use getEffectiveURI() instead
298      * @see #getEffectiveURI
299      */

300     public final URL JavaDoc getEffectiveURL() throws IOException JavaDoc
301     {
302     return getEffectiveURI().toURL();
303     }
304
305     /**
306      * set the final URL of the document. This is only for internal use.
307      *
308      * @deprecated use setEffectiveURI() instead
309      * @see #setEffectiveURI
310      */

311     public void setEffectiveURL(URL JavaDoc final_url)
312     {
313     try
314         { setEffectiveURI(new URI(final_url)); }
315     catch (ParseException pe)
316         { throw new Error JavaDoc(pe.toString()); } // shouldn't happen
317
}
318
319     /**
320      * retrieves the field for a given header.
321      *
322      * @param hdr the header name.
323      * @return the value for the header, or null if non-existent.
324      * @exception IOException If any exception occurs on the socket.
325      */

326     public String JavaDoc getHeader(String JavaDoc hdr) throws IOException JavaDoc
327     {
328     if (!got_headers) getHeaders(true);
329     return (String JavaDoc) Headers.get(hdr.trim());
330     }
331
332     /**
333      * retrieves the field for a given header. The value is parsed as an
334      * int.
335      *
336      * @param hdr the header name.
337      * @return the value for the header if the header exists
338      * @exception NumberFormatException if the header's value is not a number
339      * or if the header does not exist.
340      * @exception IOException if any exception occurs on the socket.
341      */

342     public int getHeaderAsInt(String JavaDoc hdr)
343         throws IOException JavaDoc, NumberFormatException JavaDoc
344     {
345     return Integer.parseInt(getHeader(hdr));
346     }
347
348     /**
349      * retrieves the field for a given header. The value is parsed as a
350      * date; if this fails it is parsed as a long representing the number
351      * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
352      * IllegalArgumentException is thrown.
353      *
354      * <P>Note: When sending dates use Util.httpDate().
355      *
356      * @param hdr the header name.
357      * @return the value for the header, or null if non-existent.
358      * @exception IOException If any exception occurs on the socket.
359      * @exception IllegalArgumentException If the header cannot be parsed
360      * as a date or time.
361      */

362     public Date JavaDoc getHeaderAsDate(String JavaDoc hdr)
363         throws IOException JavaDoc, IllegalArgumentException JavaDoc
364     {
365     String JavaDoc raw_date = getHeader(hdr);
366     if (raw_date == null) return null;
367
368     // asctime() format is missing an explicit GMT specifier
369
if (raw_date.toUpperCase().indexOf("GMT") == -1)
370         raw_date += " GMT";
371
372     Date JavaDoc date;
373
374     try
375         { date = new Date JavaDoc(raw_date); }
376     catch (IllegalArgumentException JavaDoc iae)
377     {
378         long time;
379         try
380         { time = Long.parseLong(raw_date); }
381         catch (NumberFormatException JavaDoc nfe)
382         { throw iae; }
383         if (time < 0) time = 0;
384         date = new Date JavaDoc(time * 1000L);
385     }
386
387     return date;
388     }
389
390
391     /**
392      * Set a header field in the list of headers. If the header already
393      * exists it will be overwritten; otherwise the header will be added
394      * to the list. This is used by some modules when they process the
395      * header so that higher level stuff doesn't get confused when the
396      * headers and data don't match.
397      *
398      * @param header The name of header field to set.
399      * @param value The value to set the field to.
400      */

401     public void setHeader(String JavaDoc header, String JavaDoc value)
402     {
403     Headers.put(header.trim(), value.trim());
404     }
405
406
407     /**
408      * Removes a header field from the list of headers. This is used by
409      * some modules when they process the header so that higher level stuff
410      * doesn't get confused when the headers and data don't match.
411      *
412      * @param header The name of header field to remove.
413      */

414     public void deleteHeader(String JavaDoc header)
415     {
416     Headers.remove(header.trim());
417     }
418
419
420     /**
421      * Retrieves the field for a given trailer. Note that this should not
422      * be invoked until all the response data has been read. If invoked
423      * before, it will force the data to be read via <code>getData()</code>.
424      *
425      * @param trailer the trailer name.
426      * @return the value for the trailer, or null if non-existent.
427      * @exception IOException If any exception occurs on the socket.
428      */

429     public String JavaDoc getTrailer(String JavaDoc trailer) throws IOException JavaDoc
430     {
431     if (!got_trailers) getTrailers();
432     return (String JavaDoc) Trailers.get(trailer.trim());
433     }
434
435
436     /**
437      * Retrieves the field for a given tailer. The value is parsed as an
438      * int.
439      *
440      * @param trailer the tailer name.
441      * @return the value for the trailer if the trailer exists
442      * @exception NumberFormatException if the trailer's value is not a number
443      * or if the trailer does not exist.
444      * @exception IOException if any exception occurs on the socket.
445      */

446     public int getTrailerAsInt(String JavaDoc trailer)
447         throws IOException JavaDoc, NumberFormatException JavaDoc
448     {
449     return Integer.parseInt(getTrailer(trailer));
450     }
451
452
453     /**
454      * Retrieves the field for a given trailer. The value is parsed as a
455      * date; if this fails it is parsed as a long representing the number
456      * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
457      * IllegalArgumentException is thrown.
458      *
459      * <P>Note: When sending dates use Util.httpDate().
460      *
461      * @param trailer the trailer name.
462      * @return the value for the trailer, or null if non-existent.
463      * @exception IllegalArgumentException if the trailer's value is neither a
464      * legal date nor a number.
465      * @exception IOException if any exception occurs on the socket.
466      * @exception IllegalArgumentException If the header cannot be parsed
467      * as a date or time.
468      */

469     public Date JavaDoc getTrailerAsDate(String JavaDoc trailer)
470         throws IOException JavaDoc, IllegalArgumentException JavaDoc
471     {
472     String JavaDoc raw_date = getTrailer(trailer);
473     if (raw_date == null) return null;
474
475     // asctime() format is missing an explicit GMT specifier
476
if (raw_date.toUpperCase().indexOf("GMT") == -1)
477         raw_date += " GMT";
478
479     Date JavaDoc date;
480
481     try
482         { date = new Date JavaDoc(raw_date); }
483     catch (IllegalArgumentException JavaDoc iae)
484     {
485         // some servers erroneously send a number, so let's try that
486
long time;
487         try
488         { time = Long.parseLong(raw_date); }
489         catch (NumberFormatException JavaDoc nfe)
490         { throw iae; } // give up
491
if (time < 0) time = 0;
492         date = new Date JavaDoc(time * 1000L);
493     }
494
495     return date;
496     }
497
498
499     /**
500      * Set a trailer field in the list of trailers. If the trailer already
501      * exists it will be overwritten; otherwise the trailer will be added
502      * to the list. This is used by some modules when they process the
503      * trailer so that higher level stuff doesn't get confused when the
504      * trailer and data don't match.
505      *
506      * @param trailer The name of trailer field to set.
507      * @param value The value to set the field to.
508      */

509     public void setTrailer(String JavaDoc trailer, String JavaDoc value)
510     {
511     Trailers.put(trailer.trim(), value.trim());
512     }
513
514
515     /**
516      * Removes a trailer field from the list of trailers. This is used by
517      * some modules when they process the trailer so that higher level stuff
518      * doesn't get confused when the trailers and data don't match.
519      *
520      * @param trailer The name of trailer field to remove.
521      */

522     public void deleteTrailer(String JavaDoc trailer)
523     {
524     Trailers.remove(trailer.trim());
525     }
526
527
528     /**
529      * Reads all the response data into a byte array. Note that this method
530      * won't return until <em>all</em> the data has been received (so for
531      * instance don't invoke this method if the server is doing a server
532      * push). If getInputStream() had been previously called then this method
533      * only returns any unread data remaining on the stream and then closes
534      * it.
535      *
536      * @see #getInputStream()
537      * @return an array containing the data (body) returned. If no data
538      * was returned then it's set to a zero-length array.
539      * @exception IOException If any io exception occured while reading
540      * the data
541      */

542     public synchronized byte[] getData() throws IOException JavaDoc
543     {
544     if (!got_headers) getHeaders(true);
545
546     if (Data == null)
547     {
548         try
549         { readResponseData(inp_stream); }
550         catch (InterruptedIOException JavaDoc ie) // don't intercept
551
{ throw ie; }
552         catch (IOException JavaDoc ioe)
553         {
554         if (DebugResp)
555         {
556             System.err.print("Resp: (" + inp_stream.hashCode() +
557                      ") (" + Thread.currentThread() + ")");
558             ioe.printStackTrace();
559         }
560         try { inp_stream.close(); } catch (Exception JavaDoc e) { }
561         throw ioe;
562         }
563
564         inp_stream.close();
565     }
566
567     return Data;
568     }
569
570     /**
571      * Gets an input stream from which the returned data can be read. Note
572      * that if getData() had been previously called it will actually return
573      * a ByteArrayInputStream created from that data.
574      *
575      * @see #getData()
576      * @return the InputStream.
577      * @exception IOException If any exception occurs on the socket.
578      */

579     public synchronized InputStream JavaDoc getInputStream() throws IOException JavaDoc
580     {
581     if (!got_headers) getHeaders(true);
582
583     if (Data == null)
584         return inp_stream;
585     else
586         return new ByteArrayInputStream JavaDoc(Data);
587     }
588
589     /**
590      * Some responses such as those from a HEAD or with certain status
591      * codes don't have an entity. This is detected by the client and
592      * can be queried here. Note that this won't try to do a read() on
593      * the input stream (it will however cause the headers to be read
594      * and parsed if not already done).
595      *
596      * @return true if the response has an entity, false otherwise
597      * @since V0.3-1
598      */

599     public synchronized boolean hasEntity() throws IOException JavaDoc
600     {
601     if (!got_headers) getHeaders(true);
602
603     return (cd_type != CD_0);
604     }
605
606
607     // Helper Methods
608

609     /**
610      * Gets and parses the headers. Sets up Data if no data will be received.
611      *
612      * @param skip_cont if true skips over '100 Continue' status codes.
613      * @exception IOException If any exception occurs while reading the headers.
614      */

615     private synchronized void getHeaders(boolean skip_cont) throws IOException JavaDoc
616     {
617     if (got_headers) return;
618     if (exception != null)
619         throw (IOException JavaDoc) exception.fillInStackTrace();
620
621     reading_headers = true;
622     try
623     {
624         do
625         {
626         Headers.clear(); // clear any headers from 100 Continue
627
String JavaDoc headers = readResponseHeaders(inp_stream);
628         parseResponseHeaders(headers);
629         } while ((StatusCode == 100 && skip_cont) || // Continue
630
(StatusCode > 101 && StatusCode < 200)); // Unknown
631
}
632     catch (IOException JavaDoc ioe)
633     {
634         if (!(ioe instanceof InterruptedIOException JavaDoc))
635         exception = ioe;
636         if (ioe instanceof ProtocolException JavaDoc) // thrown internally
637
{
638         cd_type = CD_CLOSE;
639         if (stream_handler != null)
640             stream_handler.markForClose(this);
641         }
642         throw ioe;
643     }
644     finally
645         { reading_headers = false; }
646     if (StatusCode == 100) return;
647
648
649     // parse the Content-Length header
650

651     int cont_len = -1;
652     String JavaDoc cl_hdr = (String JavaDoc) Headers.get("Content-Length");
653     if (cl_hdr != null)
654     {
655         try
656         {
657         cont_len = Integer.parseInt(cl_hdr);
658         if (cont_len < 0)
659             throw new NumberFormatException JavaDoc();
660         }
661         catch (NumberFormatException JavaDoc nfe)
662         {
663         throw new ProtocolException JavaDoc("Invalid Content-length header"+
664                         " received: "+cl_hdr);
665         }
666     }
667
668
669     // parse the Transfer-Encoding header
670

671     boolean te_chunked = false, te_is_identity = true, ct_mpbr = false;
672     Vector JavaDoc te_hdr = null;
673     try
674         { te_hdr = Util.parseHeader((String JavaDoc) Headers.get("Transfer-Encoding")); }
675     catch (ParseException pe)
676         { }
677     if (te_hdr != null)
678     {
679         te_chunked = ((HttpHeaderElement) te_hdr.lastElement()).getName().
680              equalsIgnoreCase("chunked");
681         for (int idx=0; idx<te_hdr.size(); idx++)
682         if (((HttpHeaderElement) te_hdr.elementAt(idx)).getName().
683             equalsIgnoreCase("identity"))
684             te_hdr.removeElementAt(idx--);
685         else
686             te_is_identity = false;
687     }
688
689
690     // parse Content-Type header
691

692     try
693     {
694         String JavaDoc hdr;
695         if ((hdr = (String JavaDoc) Headers.get("Content-Type")) != null)
696         {
697         Vector JavaDoc phdr = Util.parseHeader(hdr);
698         ct_mpbr = phdr.contains(new HttpHeaderElement("multipart/byteranges")) ||
699               phdr.contains(new HttpHeaderElement("multipart/x-byteranges"));
700         }
701     }
702     catch (ParseException pe)
703         { }
704
705
706     // now determine content-delimiter
707

708     if (StatusCode < 200 || StatusCode == 204 || StatusCode == 205 ||
709         StatusCode == 304)
710     {
711         cd_type = CD_0;
712     }
713     else if (te_chunked)
714     {
715         cd_type = CD_CHUNKED;
716
717         te_hdr.removeElementAt(te_hdr.size()-1);
718         if (te_hdr.size() > 0)
719         setHeader("Transfer-Encoding", Util.assembleHeader(te_hdr));
720         else
721         deleteHeader("Transfer-Encoding");
722     }
723     else if (cont_len != -1 && te_is_identity)
724         cd_type = CD_CONTLEN;
725     else if (ct_mpbr && te_is_identity)
726         cd_type = CD_MP_BR;
727     else if (!method.equals("HEAD"))
728     {
729         cd_type = CD_CLOSE;
730         if (stream_handler != null)
731         stream_handler.markForClose(this);
732
733         if (Version.equals("HTTP/0.9"))
734         {
735         inp_stream =
736             new SequenceInputStream JavaDoc(new ByteArrayInputStream JavaDoc(Data),
737                         inp_stream);
738         Data = null;
739         }
740     }
741
742     if (cd_type == CD_CONTLEN)
743         ContentLength = cont_len;
744     else
745         deleteHeader("Content-Length"); // Content-Length is not valid in this case
746

747     /* We treat HEAD specially down here because the above code needs
748      * to know whether to remove the Content-length header or not.
749      */

750     if (method.equals("HEAD"))
751         cd_type = CD_0;
752
753     if (cd_type == CD_0)
754     {
755         ContentLength = 0;
756         Data = new byte[0];
757         inp_stream.close(); // we will not receive any more data
758
}
759
760     if (DebugResp)
761     {
762         System.err.println("Resp: Response entity delimiter: " +
763         (cd_type == CD_0 ? "No Entity" :
764          cd_type == CD_CLOSE ? "Close" :
765          cd_type == CD_CONTLEN ? "Content-Length" :
766          cd_type == CD_CHUNKED ? "Chunked" :
767          cd_type == CD_MP_BR ? "Multipart" :
768          "???" ) + " (" + inp_stream.hashCode() + ") (" +
769          Thread.currentThread() + ")");
770     }
771
772
773     // remove erroneous connection tokens
774

775     if (connection.ServerProtocolVersion >= HTTP_1_1)
776         deleteHeader("Proxy-Connection");
777     else // HTTP/1.0
778
{
779         if (connection.getProxyHost() != null)
780         deleteHeader("Connection");
781         else
782         deleteHeader("Proxy-Connection");
783
784         Vector JavaDoc pco;
785         try
786         { pco = Util.parseHeader((String JavaDoc) Headers.get("Connection")); }
787         catch (ParseException pe)
788         { pco = null; }
789
790         if (pco != null)
791         {
792         for (int idx=0; idx<pco.size(); idx++)
793         {
794             String JavaDoc name =
795                 ((HttpHeaderElement) pco.elementAt(idx)).getName();
796             if (!name.equalsIgnoreCase("keep-alive"))
797             {
798             pco.removeElementAt(idx);
799             deleteHeader(name);
800             idx--;
801             }
802         }
803
804         if (pco.size() > 0)
805             setHeader("Connection", Util.assembleHeader(pco));
806         else
807             deleteHeader("Connection");
808         }
809
810         try
811         { pco = Util.parseHeader((String JavaDoc) Headers.get("Proxy-Connection")); }
812         catch (ParseException pe)
813         { pco = null; }
814
815         if (pco != null)
816         {
817         for (int idx=0; idx<pco.size(); idx++)
818         {
819             String JavaDoc name =
820                 ((HttpHeaderElement) pco.elementAt(idx)).getName();
821             if (!name.equalsIgnoreCase("keep-alive"))
822             {
823             pco.removeElementAt(idx);
824             deleteHeader(name);
825             idx--;
826             }
827         }
828
829         if (pco.size() > 0)
830             setHeader("Proxy-Connection", Util.assembleHeader(pco));
831         else
832             deleteHeader("Proxy-Connection");
833         }
834     }
835
836
837     // this must be set before we invoke handleFirstRequest()
838
got_headers = true;
839
840     // special handling if this is the first response received
841
if (isFirstResponse)
842     {
843         if (!connection.handleFirstRequest(req, this))
844         {
845         // got a buggy server - need to redo the request
846
Response resp;
847         try
848             { resp = connection.sendRequest(req, timeout); }
849         catch (ModuleException me)
850             { throw new IOException JavaDoc(me.toString()); }
851         resp.getVersion();
852
853         this.StatusCode = resp.StatusCode;
854         this.ReasonLine = resp.ReasonLine;
855         this.Version = resp.Version;
856         this.EffectiveURI = resp.EffectiveURI;
857         this.ContentLength = resp.ContentLength;
858         this.Headers = resp.Headers;
859         this.inp_stream = resp.inp_stream;
860         this.Data = resp.Data;
861
862         req = null;
863         }
864     }
865     }
866
867
868     /* these are external to readResponseHeaders() because we need to be
869      * able to restart after an InterruptedIOException
870      */

871     private byte[] buf = new byte[7];
872     private int buf_pos = 0;
873     private StringBuffer JavaDoc hdrs = new StringBuffer JavaDoc(400);
874     private boolean reading_lines = false;
875     private boolean bol = true;
876     private boolean got_cr = false;
877
878     /**
879      * Reads the response headers received, folding continued lines.
880      *
881      * <P>Some of the code is a bit convoluted because we have to be able
882      * restart after an InterruptedIOException.
883      *
884      * @inp the input stream from which to read the response
885      * @return a (newline separated) list of headers
886      * @exception IOException if any read on the input stream fails
887      */

888     private String JavaDoc readResponseHeaders(InputStream JavaDoc inp) throws IOException JavaDoc
889     {
890     if (DebugResp)
891     {
892         if (buf_pos == 0)
893         System.err.println("Resp: Reading Response headers " +
894                     inp_stream.hashCode() + " (" +
895                     Thread.currentThread() + ")");
896         else
897         System.err.println("Resp: Resuming reading Response headers " +
898                     inp_stream.hashCode() + " (" +
899                     Thread.currentThread() + ")");
900     }
901
902
903     // read 7 bytes to see type of response
904
if (!reading_lines)
905     {
906         try
907         {
908         // Skip any leading white space to accomodate buggy responses
909
if (buf_pos == 0)
910         {
911             int c;
912             do
913             {
914             if ((c = inp.read()) == -1)
915                 throw new EOFException JavaDoc("Encountered premature EOF "
916                            + "while reading Version");
917             } while (Character.isSpace( (char) (c & 0xFF) )) ;
918             buf[0] = (byte) (c & 0xFF);
919             buf_pos = 1;
920         }
921
922         // Now read first seven bytes (the version string)
923
while (buf_pos < buf.length)
924         {
925             int got = inp.read(buf, buf_pos, buf.length-buf_pos);
926             if (got == -1)
927             throw new EOFException JavaDoc("Encountered premature EOF " +
928                         "while reading Version");
929             buf_pos += got;
930         }
931         }
932         catch (EOFException JavaDoc eof)
933         {
934         if (DebugResp)
935         {
936             System.err.print("Resp: (" + inp_stream.hashCode() + ") ("
937                      + Thread.currentThread() + ")");
938             eof.printStackTrace();
939         }
940         throw eof;
941         }
942         for (int idx=0; idx<buf.length; idx++)
943         hdrs.append((char) buf[idx]);
944
945         reading_lines = true;
946     }
947
948     if (hdrs.toString().startsWith("HTTP/") || // It's x.x
949
hdrs.toString().startsWith("HTTP ")) // NCSA bug
950
readLines(inp);
951
952     // reset variables for next round
953
buf_pos = 0;
954     reading_lines = false;
955     bol = true;
956     got_cr = false;
957
958     String JavaDoc tmp = hdrs.toString();
959     hdrs.setLength(0);
960     return tmp;
961     }
962
963
964     boolean trailers_read = false;
965
966     /**
967      * This is called by the StreamDemultiplexor to read all the trailers
968      * of a chunked encoded entity.
969      *
970      * @param inp the raw input stream to read from
971      * @exception IOException if any IOException is thrown by the stream
972      */

973     void readTrailers(InputStream JavaDoc inp) throws IOException JavaDoc
974     {
975     try
976     {
977         readLines(inp);
978         trailers_read = true;
979     }
980     catch (IOException JavaDoc ioe)
981     {
982         if (!(ioe instanceof InterruptedIOException JavaDoc))
983         exception = ioe;
984         throw ioe;
985     }
986     }
987
988
989     /**
990      * This reads a set of lines up to and including the first empty line.
991      * A line is terminated by either a <CR><LF> or <LF>. The lines are
992      * stored in the <var>hdrs</var> buffers. Continued lines are merged
993      * and stored as one line.
994      *
995      * <P>This method is restartable after an InterruptedIOException.
996      *
997      * @param inp the input stream to read from
998      * @exception IOException if any IOException is thrown by the stream
999      */

1000    private void readLines(InputStream JavaDoc inp) throws IOException JavaDoc
1001    {
1002    /* This loop is a merge of readLine() from DataInputStream and
1003     * the necessary header logic to merge continued lines and terminate
1004     * after an empty line. The reason this is explicit is because of
1005     * the need to handle InterruptedIOExceptions.
1006     */

1007    loop: while (true)
1008    {
1009        int b = inp.read();
1010        switch (b)
1011        {
1012        case -1:
1013            throw new EOFException JavaDoc("Encountered premature EOF while reading headers:\n" + hdrs);
1014        case '\r':
1015            got_cr = true;
1016            break;
1017        case '\n':
1018            if (bol) break loop; // all headers read
1019
hdrs.append('\n');
1020            bol = true;
1021            got_cr = false;
1022            break;
1023        case ' ':
1024        case '\t':
1025            if (bol) // a continued line
1026
{
1027            // replace previous \n with SP
1028
hdrs.setCharAt(hdrs.length()-1, ' ');
1029            bol = false;
1030            break;
1031            }
1032        default:
1033            if (got_cr)
1034            {
1035            hdrs.append('\r');
1036            got_cr = false;
1037            }
1038            hdrs.append((char) (b & 0xFF));
1039            bol = false;
1040            break;
1041        }
1042    }
1043    }
1044
1045
1046    /**
1047     * Parses the headers received into a new Response structure.
1048     *
1049     * @param headers a (newline separated) list of headers
1050     * @exception ProtocolException if any part of the headers do not
1051     * conform
1052     */

1053    private void parseResponseHeaders(String JavaDoc headers) throws ProtocolException JavaDoc
1054    {
1055    String JavaDoc sts_line = null;
1056    StringTokenizer JavaDoc lines = new StringTokenizer JavaDoc(headers, "\r\n"),
1057            elem;
1058
1059
1060    if (DebugResp)
1061        System.err.println("Resp: Parsing Response headers from Request "+
1062                "\"" + method + " " + resource + "\": (" +
1063                inp_stream.hashCode() + ") (" +
1064                Thread.currentThread() + ")\n\n"+headers);
1065
1066
1067    // Detect and handle HTTP/0.9 responses
1068

1069    if (!headers.regionMatches(true, 0, "HTTP/", 0, 5) &&
1070        !headers.regionMatches(true, 0, "HTTP ", 0, 5)) // NCSA bug
1071
{
1072        Version = "HTTP/0.9";
1073        StatusCode = 200;
1074        ReasonLine = "OK";
1075
1076        Data = new byte[headers.length()];
1077        headers.getBytes(0, headers.length(), Data, 0);
1078
1079        return;
1080    }
1081
1082
1083    // get the status line
1084

1085    try
1086    {
1087        sts_line = lines.nextToken();
1088        elem = new StringTokenizer JavaDoc(sts_line, " \t");
1089
1090        Version = elem.nextToken();
1091        StatusCode = Integer.valueOf(elem.nextToken()).intValue();
1092
1093        if (Version.equalsIgnoreCase("HTTP")) // NCSA bug
1094
Version = "HTTP/1.0";
1095    }
1096    catch (NoSuchElementException JavaDoc e)
1097    {
1098        throw new ProtocolException JavaDoc("Invalid HTTP status line received: " +
1099                    sts_line);
1100    }
1101    try
1102        { ReasonLine = elem.nextToken("").trim(); }
1103    catch (NoSuchElementException JavaDoc e)
1104        { ReasonLine = ""; }
1105
1106
1107    /* If the status code shows an error and we're sending (or have sent)
1108     * an entity and it's length is delimited by a Content-length header,
1109     * then we must close the the connection (if indeed it hasn't already
1110     * been done) - RFC-2068, Section 8.2 .
1111     */

1112    if (StatusCode >= 300 && sent_entity)
1113    {
1114        if (stream_handler != null)
1115        stream_handler.markForClose(this);
1116    }
1117
1118
1119    // get the rest of the headers
1120

1121    parseHeaderFields(lines, Headers);
1122
1123
1124    /* make sure the connection isn't closed prematurely if we have
1125     * trailer fields
1126     */

1127    if (Headers.get("Trailer") != null && resp_inp_stream != null)
1128        resp_inp_stream.dontTruncate();
1129
1130    // Mark the end of the connection if it's not to be kept alive
1131

1132    int vers;
1133    if (Version.equalsIgnoreCase("HTTP/0.9") ||
1134        Version.equalsIgnoreCase("HTTP/1.0"))
1135        vers = 0;
1136    else
1137        vers = 1;
1138
1139    try
1140    {
1141        String JavaDoc con = (String JavaDoc) Headers.get("Connection"),
1142          pcon = (String JavaDoc) Headers.get("Proxy-Connection");
1143
1144        // parse connection header
1145
if ((vers == 1 && con != null && Util.hasToken(con, "close"))
1146        ||
1147        (vers == 0 &&
1148         !((!used_proxy && con != null &&
1149                    Util.hasToken(con, "keep-alive")) ||
1150           (used_proxy && pcon != null &&
1151                    Util.hasToken(pcon, "keep-alive")))
1152        )
1153           )
1154        if (stream_handler != null)
1155            stream_handler.markForClose(this);
1156    }
1157    catch (ParseException pe) { }
1158    }
1159
1160
1161    /**
1162     * If the trailers have not been read it calls <code>getData()</code>
1163     * to first force all data and trailers to be read. Then the trailers
1164     * parsed into the <var>Trailers</var> hashtable.
1165     *
1166     * @exception IOException if any exception occured during reading of the
1167     * response
1168     */

1169    private synchronized void getTrailers() throws IOException JavaDoc
1170    {
1171    if (got_trailers) return;
1172    if (exception != null)
1173        throw (IOException JavaDoc) exception.fillInStackTrace();
1174
1175    if (DebugResp)
1176        System.err.println("Resp: Reading Response trailers " +
1177                inp_stream.hashCode() + " (" +
1178                Thread.currentThread() + ")");
1179
1180    try
1181    {
1182        if (!trailers_read)
1183        {
1184        if (resp_inp_stream != null)
1185            resp_inp_stream.readAll(timeout);
1186        }
1187
1188        if (trailers_read)
1189        {
1190        if (DebugResp)
1191            System.err.println("Resp: Parsing Response trailers from "+
1192                       "Request \"" + method + " " + resource +
1193                       "\": (" + inp_stream.hashCode() + ") ("+
1194                       Thread.currentThread() + ")\n\n"+hdrs);
1195
1196        parseHeaderFields(new StringTokenizer JavaDoc(hdrs.toString(), "\r\n"),
1197                  Trailers);
1198        }
1199    }
1200    finally
1201    {
1202        got_trailers = true;
1203    }
1204    }
1205
1206
1207    /**
1208     * Parses the given lines as header fields of the form "<name>: <value>"
1209     * into the given list.
1210     *
1211     * @param lines the header or trailer lines, one header field per line
1212     * @param list the Hashtable to store the parsed fields in
1213     * @exception ProtocolException if any part of the headers do not
1214     * conform
1215     */

1216    private void parseHeaderFields(StringTokenizer JavaDoc lines, CIHashtable list)
1217        throws ProtocolException JavaDoc
1218    {
1219    while (lines.hasMoreTokens())
1220    {
1221        String JavaDoc hdr = lines.nextToken();
1222        int sep = hdr.indexOf(':');
1223
1224        /* Once again we have to deal with broken servers and try
1225         * to wing it here. If no ':' is found, try using the first
1226         * space:
1227         */

1228        if (sep == -1)
1229        sep = hdr.indexOf(' ');
1230        if (sep == -1)
1231        {
1232        throw new ProtocolException JavaDoc("Invalid HTTP header received: " +
1233                        hdr);
1234        }
1235
1236        String JavaDoc hdr_name = hdr.substring(0, sep).trim();
1237
1238        int len = hdr.length();
1239        sep++;
1240        while (sep < len && Character.isSpace(hdr.charAt(sep))) sep++;
1241        String JavaDoc hdr_value = hdr.substring(sep);
1242
1243        String JavaDoc old_value = (String JavaDoc) list.get(hdr_name);
1244        if (old_value == null)
1245        list.put(hdr_name, hdr_value);
1246        else
1247        list.put(hdr_name, old_value + ", " + hdr_value);
1248    }
1249    }
1250
1251
1252    /**
1253     * Reads the response data received. Does not return until either
1254     * Content-Length bytes have been read or EOF is reached.
1255     *
1256     * @inp the input stream from which to read the data
1257     * @exception IOException if any read on the input stream fails
1258     */

1259    private void readResponseData(InputStream JavaDoc inp) throws IOException JavaDoc
1260    {
1261    if (ContentLength == 0)
1262        return;
1263
1264    if (Data == null)
1265        Data = new byte[0];
1266
1267
1268    // read response data
1269

1270    int off = Data.length;
1271
1272    try
1273    {
1274        // check Content-length header in case CE-Module removed it
1275
if (getHeader("Content-Length") != null)
1276        {
1277        int rcvd = 0;
1278        Data = new byte[ContentLength];
1279
1280        do
1281        {
1282            off += rcvd;
1283            rcvd = inp.read(Data, off, ContentLength-off);
1284        } while (rcvd != -1 && off+rcvd < ContentLength);
1285
1286        /* Don't do this!
1287         * If we do, then getData() won't work after a getInputStream()
1288         * because we'll never get all the expected data. Instead, let
1289         * the underlying RespInputStream throw the EOF.
1290        if (rcvd == -1) // premature EOF
1291        {
1292            throw new EOFException("Encountered premature EOF while " +
1293                        "reading headers: received " + off +
1294                        " bytes instead of the expected " +
1295                        ContentLength + " bytes");
1296        }
1297        */

1298        }
1299        else
1300        {
1301        int inc = 1000,
1302            rcvd = 0;
1303
1304        do
1305        {
1306            off += rcvd;
1307            Data = Util.resizeArray(Data, off+inc);
1308        } while ((rcvd = inp.read(Data, off, inc)) != -1);
1309
1310        Data = Util.resizeArray(Data, off);
1311        }
1312    }
1313    catch (IOException JavaDoc ioe)
1314    {
1315        Data = Util.resizeArray(Data, off);
1316        throw ioe;
1317    }
1318    finally
1319    {
1320        try
1321        { inp.close(); }
1322        catch (IOException JavaDoc ioe)
1323        { }
1324    }
1325    }
1326
1327
1328    Request req = null;
1329    boolean isFirstResponse = false;
1330    /**
1331     * This marks this response as belonging to the first request made
1332     * over an HTTPConnection. The <var>con</var> and <var>req</var>
1333     * parameters are needed in case we have to do a resend of the request -
1334     * this is to handle buggy servers which barf upon receiving a request
1335     * marked as HTTP/1.1 .
1336     *
1337     * @param con The HTTPConnection used
1338     * @param req The Request sent
1339     */

1340    void markAsFirstResponse(Request req)
1341    {
1342    this.req = req;
1343    isFirstResponse = true;
1344    }
1345}
1346
1347
Popular Tags