KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > HTTPClient > HttpOutputStream


1 /*
2  * @(#)HttpOutputStream.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
32 import java.io.OutputStream JavaDoc;
33 import java.io.ByteArrayOutputStream JavaDoc;
34 import java.io.IOException JavaDoc;
35
36 /**
37  * This class provides an output stream for requests. The stream must first
38  * be associated with a request before it may be used; this is done by
39  * passing it to one of the request methods in HTTPConnection. Example:
40  * <PRE>
41  * OutputStream out = new HttpOutputStream(12345);
42  * rsp = con.Post("/cgi-bin/my_cgi", out);
43  * out.write(...);
44  * out.close();
45  * if (rsp.getStatusCode() >= 300)
46  * ...
47  * </PRE>
48  *
49  * <P>There are two constructors for this class, one taking a length parameter,
50  * and one without any parameters. If the stream is created with a length
51  * then the request will be sent with the corresponding Content-length header
52  * and anything written to the stream will be written on the socket immediately.
53  * This is the preferred way. If the stream is created without a length then
54  * one of two things will happen: if, at the time of the request, the server
55  * is known to understand HTTP/1.1 then each write() will send the data
56  * immediately using the chunked encoding. If, however, either the server
57  * version is unknown (because this is first request to that server) or the
58  * server only understands HTTP/1.0 then all data will be written to a buffer
59  * first, and only when the stream is closed will the request be sent.
60  *
61  * <P>Another reason that using the <var>HttpOutputStream(length)</var>
62  * constructor is recommended over the <var>HttpOutputStream()</var> one is
63  * that some HTTP/1.1 servers do not allow the chunked transfer encoding to
64  * be used when POSTing to a cgi script. This is because the way the cgi API
65  * is defined the cgi script expects a Content-length environment variable.
66  * If the data is sent using the chunked transfer encoding however, then the
67  * server would have to buffer all the data before invoking the cgi so that
68  * this variable could be set correctly. Not all servers are willing to do
69  * this.
70  *
71  * <P>If you cannot use the <var>HttpOutputStream(length)</var> constructor and
72  * are having problems sending requests (usually a 411 response) then you can
73  * try setting the system property <var>HTTPClient.dontChunkRequests</var> to
74  * <var>true</var> (this needs to be done either on the command line or
75  * somewhere in the code before the HTTPConnection is first accessed). This
76  * will prevent the client from using the chunked encoding in this case and
77  * will cause the HttpOutputStream to buffer all the data instead, sending it
78  * only when close() is invoked.
79  *
80  * <P>The behaviour of a request sent with an output stream may differ from
81  * that of a request sent with a data parameter. The reason for this is that
82  * the various modules cannot resend a request which used an output stream.
83  * Therefore such things as authorization and retrying of requests won't be
84  * done by the HTTPClient for such requests.
85  *
86  * @version 0.3-2 18/06/1999
87  * @author Ronald Tschalär
88  * @since V0.3
89  */

90
91 public class HttpOutputStream extends OutputStream JavaDoc implements GlobalConstants
92 {
93     /** null trailers */
94     private static final NVPair[] empty = new NVPair[0];
95
96     /** the length of the data to be sent */
97     private int length;
98
99     /** the length of the data received so far */
100     private int rcvd = 0;
101
102     /** the request this stream is associated with */
103     private Request req = null;
104
105     /** the response from sendRequest if we stalled the request */
106     private Response resp = null;
107
108     /** the socket output stream */
109     private OutputStream JavaDoc os = null;
110
111     /** the buffer to be used if needed */
112     private ByteArrayOutputStream JavaDoc bos = null;
113
114     /** the trailers to send if using chunked encoding. */
115     private NVPair[] trailers = empty;
116
117     /** the timeout to pass to SendRequest() */
118     private int con_to = 0;
119
120     /** just ignore all the data if told to do so */
121     private boolean ignore = false;
122
123
124     // Constructors
125

126     /**
127      * Creates an output stream of unspecified length. Note that it is
128      * <strong>highly</strong> recommended that this constructor be avoided
129      * where possible and <code>HttpOutputStream(int)</code> used instead.
130      *
131      * @see HttpOutputStream#HttpOutputStream(int)
132      */

133     public HttpOutputStream()
134     {
135     length = -1;
136     }
137
138
139     /**
140      * This creates an output stream which will take <var>length</var> bytes
141      * of data.
142      *
143      * @param length the number of bytes which will be sent over this stream
144      */

145     public HttpOutputStream(int length)
146     {
147     if (length < 0)
148        throw new IllegalArgumentException JavaDoc("Length must be greater equal 0");
149     this.length = length;
150     }
151
152
153     // Methods
154

155     /**
156      * Associates this stream with a request and the actual output stream.
157      * No other methods in this class may be invoked until this method has
158      * been invoked by the HTTPConnection.
159      *
160      * @param req the request this stream is to be associated with
161      * @param os the underlying output stream to write our data to, or null
162      * if we should write to a ByteArrayOutputStream instead.
163      * @param con_to connection timeout to use in sendRequest()
164      */

165     void goAhead(Request req, OutputStream JavaDoc os, int con_to)
166     {
167     this.req = req;
168     this.os = os;
169     this.con_to = con_to;
170
171     if (os == null)
172         bos = new ByteArrayOutputStream JavaDoc();
173
174     if (DebugConn)
175     {
176         System.err.println("OutS: Stream ready for writing");
177         if (bos != null)
178         System.err.println("OutS: Buffering all data before sending " +
179                    "request");
180     }
181     }
182
183
184     /**
185      * Setup this stream to dump the data to the great bit-bucket in the sky.
186      * This is needed for when a module handles the request directly.
187      *
188      * @param req the request this stream is to be associated with
189      */

190     void ignoreData(Request req)
191     {
192     this.req = req;
193     ignore = true;
194     }
195
196
197     /**
198      * Return the response we got from sendRequest(). This waits until
199      * the request has actually been sent.
200      *
201      * @return the response returned by sendRequest()
202      */

203     synchronized Response getResponse()
204     {
205     while (resp == null)
206         try { wait(); } catch (InterruptedException JavaDoc ie) { }
207
208     return resp;
209     }
210
211
212     /**
213      * Returns the number of bytes this stream is willing to accept, or -1
214      * if it is unbounded.
215      *
216      * @return the number of bytes
217      */

218     public int getLength()
219     {
220     return length;
221     }
222
223
224     /**
225      * Gets the trailers which were set with <code>setTrailers()</code>.
226      *
227      * @return an array of header fields
228      * @see #setTrailers(NVPair[])
229      */

230     public NVPair[] getTrailers()
231     {
232     return trailers;
233     }
234
235
236     /**
237      * Sets the trailers to be sent if the output is sent with the
238      * chunked transfer encoding. These must be set before the output
239      * stream is closed for them to be sent.
240      *
241      * <P>Any trailers set here <strong>should</strong> be mentioned
242      * in a <var>Trailer</var> header in the request (see section 14.40
243      * of draft-ietf-http-v11-spec-rev-06.txt).
244      *
245      * <P>This method (and its related <code>getTrailers()</code>)) are
246      * in this class and not in <var>Request</var> because setting
247      * trailers is something an application may want to do, not only
248      * modules.
249      *
250      * @param trailers an array of header fields
251      */

252     public void setTrailers(NVPair[] trailers)
253     {
254     if (trailers != null)
255         this.trailers = trailers;
256     else
257         this.trailers = empty;
258     }
259
260
261     /**
262      * Writes a single byte on the stream. It is subject to the same rules
263      * as <code>write(byte[], int, int)</code>.
264      *
265      * @param b the byte to write
266      * @exception IOException if any exception is thrown by the socket
267      * @see #write(byte[], int, int)
268      */

269     public void write(int b) throws IOException JavaDoc, IllegalAccessError JavaDoc
270     {
271     byte[] tmp = { (byte) b };
272     write(tmp, 0, 1);
273     }
274
275
276     /**
277      * Writes an array of bytes on the stream. This method may not be used
278      * until this stream has been passed to one of the methods in
279      * HTTPConnection (i.e. until it has been associated with a request).
280      *
281      * @param buf an array containing the data to write
282      * @param off the offset of the data whithin the buffer
283      * @param len the number bytes (starting at <var>off</var>) to write
284      * @exception IOException if any exception is thrown by the socket, or
285      * if writing <var>len</var> bytes would cause more bytes to
286      * be written than this stream is willing to accept.
287      * @exception IllegalAccessError if this stream has not been associated
288      * with a request yet
289      */

290     public synchronized void write(byte[] buf, int off, int len)
291         throws IOException JavaDoc, IllegalAccessError JavaDoc
292     {
293     if (req == null)
294         throw new IllegalAccessError JavaDoc("Stream not associated with a request");
295
296     if (ignore) return;
297
298     if (length != -1 && rcvd+len > length)
299     {
300         IOException JavaDoc ioe =
301         new IOException JavaDoc("Tried to write too many bytes (" + (rcvd+len) +
302                 " > " + length + ")");
303         req.getConnection().closeDemux(ioe, false);
304         req.getConnection().outputFinished();
305         throw ioe;
306     }
307
308     try
309     {
310         if (bos != null)
311         bos.write(buf, off, len);
312         else if (length != -1)
313         os.write(buf, off, len);
314         else
315         os.write(Codecs.chunkedEncode(buf, off, len, null, false));
316     }
317     catch (IOException JavaDoc ioe)
318     {
319         req.getConnection().closeDemux(ioe, true);
320         req.getConnection().outputFinished();
321         throw ioe;
322     }
323
324     rcvd += len;
325     }
326
327
328     /**
329      * Closes the stream and causes the data to be sent if it has not already
330      * been done so. This method <strong>must</strong> be invoked when all
331      * data has been written.
332      *
333      * @exception IOException if any exception is thrown by the underlying
334      * socket, or if too few bytes were written.
335      * @exception IllegalAccessError if this stream has not been associated
336      * with a request yet.
337      */

338     public synchronized void close() throws IOException JavaDoc, IllegalAccessError JavaDoc
339     {
340     if (req == null)
341         throw new IllegalAccessError JavaDoc("Stream not associated with a request");
342
343     if (ignore) return;
344
345     if (bos != null)
346     {
347         req.setData(bos.toByteArray());
348         req.setStream(null);
349
350         if (trailers.length > 0)
351         {
352         NVPair[] hdrs = req.getHeaders();
353
354         // remove any Trailer header field
355

356         int len = hdrs.length;
357         for (int idx=0; idx<len; idx++)
358         {
359             if (hdrs[idx].getName().equalsIgnoreCase("Trailer"))
360             {
361             System.arraycopy(hdrs, idx+1, hdrs, idx, len-idx-1);
362             len--;
363             }
364         }
365
366
367         // add the trailers to the headers
368

369         hdrs = Util.resizeArray(hdrs, len+trailers.length);
370         System.arraycopy(trailers, 0, hdrs, len, trailers.length);
371
372         req.setHeaders(hdrs);
373         }
374
375         if (DebugConn) System.err.println("OutS: Sending request");
376
377         try
378         { resp = req.getConnection().sendRequest(req, con_to); }
379         catch (ModuleException me)
380         { throw new IOException JavaDoc(me.toString()); }
381         notify();
382     }
383     else
384     {
385         if (rcvd < length)
386         {
387         IOException JavaDoc ioe =
388             new IOException JavaDoc("Premature close: only " + rcvd +
389                     " bytes written instead of the " +
390                     "expected " + length);
391         req.getConnection().closeDemux(ioe, false);
392         req.getConnection().outputFinished();
393         throw ioe;
394         }
395
396         try
397         {
398         if (length == -1)
399         {
400             if (DebugConn && trailers.length > 0)
401             {
402             System.err.println("OutS: Sending trailers:");
403             for (int idx=0; idx<trailers.length; idx++)
404                 System.err.println(" " +
405                       trailers[idx].getName() + ": " +
406                       trailers[idx].getValue());
407             }
408
409             os.write(Codecs.chunkedEncode(null, 0, 0, trailers, true));
410         }
411
412         os.flush();
413
414         if (DebugConn) System.err.println("OutS: All data sent");
415         }
416         catch (IOException JavaDoc ioe)
417         {
418         req.getConnection().closeDemux(ioe, true);
419         throw ioe;
420         }
421         finally
422         {
423         req.getConnection().outputFinished();
424         }
425     }
426     }
427
428
429     /**
430      * produces a string describing this stream.
431      *
432      * @return a string containing the name and the length
433      */

434     public String JavaDoc toString()
435     {
436     return getClass().getName() + "[length=" + length + "]";
437     }
438 }
439
440
Popular Tags