View Javadoc

1   /*
2    * @(#)HttpOutputStream.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  package HTTPClient;
37  
38  import java.io.OutputStream;
39  import java.io.ByteArrayOutputStream;
40  import java.io.IOException;
41  
42  /**
43   * This class provides an output stream for requests. The stream must first
44   * be associated with a request before it may be used; this is done by
45   * passing it to one of the request methods in HTTPConnection. Example:
46   * <PRE>
47   *    OutputStream out = new HttpOutputStream(12345);
48   *    rsp = con.Post("/cgi-bin/my_cgi", out);
49   *    out.write(...);
50   *    out.close();
51   *    if (rsp.getStatusCode() >= 300)
52   *        ...
53   * </PRE>
54   *
55   * <P>There are two constructors for this class, one taking a length parameter,
56   * and one without any parameters. If the stream is created with a length
57   * then the request will be sent with the corresponding Content-length header
58   * and anything written to the stream will be written on the socket immediately.
59   * This is the preferred way. If the stream is created without a length then
60   * one of two things will happen: if, at the time of the request, the server
61   * is known to understand HTTP/1.1 then each write() will send the data
62   * immediately using the chunked encoding. If, however, either the server
63   * version is unknown (because this is first request to that server) or the
64   * server only understands HTTP/1.0 then all data will be written to a buffer
65   * first, and only when the stream is closed will the request be sent.
66   *
67   * <P>Another reason that using the <var>HttpOutputStream(length)</var>
68   * constructor is recommended over the <var>HttpOutputStream()</var> one is
69   * that some HTTP/1.1 servers do not allow the chunked transfer encoding to
70   * be used when POSTing to a cgi script. This is because the way the cgi API
71   * is defined the cgi script expects a Content-length environment variable.
72   * If the data is sent using the chunked transfer encoding however, then the
73   * server would have to buffer all the data before invoking the cgi so that
74   * this variable could be set correctly. Not all servers are willing to do
75   * this.
76   *
77   * <P>If you cannot use the <var>HttpOutputStream(length)</var> constructor and
78   * are having problems sending requests (usually a 411 response) then you can
79   * try setting the system property <var>HTTPClient.dontChunkRequests</var> to
80   * <var>true</var> (this needs to be done either on the command line or
81   * somewhere in the code before the HTTPConnection is first accessed). This
82   * will prevent the client from using the chunked encoding in this case and
83   * will cause the HttpOutputStream to buffer all the data instead, sending it
84   * only when close() is invoked.
85   *
86   * <P>The behaviour of a request sent with an output stream may differ from
87   * that of a request sent with a data parameter. The reason for this is that
88   * the various modules cannot resend a request which used an output stream.
89   * Therefore such things as authorization and retrying of requests won't be
90   * done by the HTTPClient for such requests. But see {@link
91   * HTTPResponse#retryRequest() HTTPResponse.retryRequest} for a partial
92   * solution.
93   *
94   * @version	0.3-3  06/05/2001
95   * @author	Ronald Tschalär
96   * @since	V0.3
97   */
98  public class HttpOutputStream extends OutputStream
99  {
100     /** null trailers */
101     private static final NVPair[] empty = new NVPair[0];
102 
103     /** the length of the data to be sent */
104 
105     /** ++GRINDER MODIFICATION **/
106     //private int length;
107     private long length;
108 
109     /** the length of the data received so far */
110     // private int rcvd = 0;
111     private long rcvd = 0;
112     /** --GRINDER MODIFICATION **/
113 
114     /** the request this stream is associated with */
115     private Request req = null;
116 
117     /** the response from sendRequest if we stalled the request */
118     private Response resp = null;
119 
120     /** the socket output stream */
121     private OutputStream os = null;
122 
123     /** the buffer to be used if needed */
124     private ByteArrayOutputStream bos = null;
125 
126     /** the trailers to send if using chunked encoding. */
127     private NVPair[] trailers = empty;
128 
129     /** the timeout to pass to SendRequest() */
130     private int con_to = 0;
131 
132     /** just ignore all the data if told to do so */
133     private boolean ignore = false;
134 
135 
136     // Constructors
137 
138     /**
139      * Creates an output stream of unspecified length. Note that it is
140      * <strong>highly</strong> recommended that this constructor be avoided
141      * where possible and <code>HttpOutputStream(int)</code> used instead.
142      *
143      * @see HttpOutputStream#HttpOutputStream(int)
144      */
145     public HttpOutputStream()
146     {
147 	length = -1;
148     }
149 
150 
151     /**
152      * This creates an output stream which will take <var>length</var> bytes
153      * of data.
154      *
155      * @param length the number of bytes which will be sent over this stream
156      */
157     public HttpOutputStream(long /*GRINDER MODIFICATION int */ length)
158     {
159 	if (length < 0)
160 	   throw new IllegalArgumentException("Length must be greater equal 0");
161 	this.length = length;
162     }
163 
164 
165     // Methods
166 
167     /**
168      * Associates this stream with a request and the actual output stream.
169      * No other methods in this class may be invoked until this method has
170      * been invoked by the HTTPConnection.
171      *
172      * @param req    the request this stream is to be associated with
173      * @param os     the underlying output stream to write our data to, or null
174      *               if we should write to a ByteArrayOutputStream instead.
175      * @param con_to connection timeout to use in sendRequest()
176      */
177     void goAhead(Request req, OutputStream os, int con_to)
178     {
179 	this.req    = req;
180 	this.os     = os;
181 	this.con_to = con_to;
182 
183 	if (os == null)
184 	    bos = new ByteArrayOutputStream();
185 
186 	Log.write(Log.CONN, "OutS:  Stream ready for writing");
187 	if (bos != null)
188 	    Log.write(Log.CONN, "OutS:  Buffering all data before sending " +
189 			        "request");
190     }
191 
192 
193     /**
194      * Setup this stream to dump the data to the great bit-bucket in the sky.
195      * This is needed for when a module handles the request directly.
196      *
197      * @param req the request this stream is to be associated with
198      */
199     void ignoreData(Request req)
200     {
201 	this.req = req;
202 	ignore = true;
203     }
204 
205 
206     /**
207      * Return the response we got from sendRequest(). This waits until
208      * the request has actually been sent.
209      *
210      * @return the response returned by sendRequest()
211      */
212     synchronized Response getResponse()
213     {
214 	while (resp == null)
215 	    try { wait(); } catch (InterruptedException ie) { }
216 
217 	return resp;
218     }
219 
220 
221     /**
222      * Returns the number of bytes this stream is willing to accept, or -1
223      * if it is unbounded.
224      *
225      * @return the number of bytes
226      */
227     public long /* GRINDER MODIFICICATION int */ getLength()
228     {
229 	return length;
230     }
231 
232 
233     /**
234      * Gets the trailers which were set with <code>setTrailers()</code>.
235      *
236      * @return an array of header fields
237      * @see #setTrailers(HTTPClient.NVPair[])
238      */
239     public NVPair[] getTrailers()
240     {
241 	return trailers;
242     }
243 
244 
245     /**
246      * Sets the trailers to be sent if the output is sent with the
247      * chunked transfer encoding. These must be set before the output
248      * stream is closed for them to be sent.
249      *
250      * <P>Any trailers set here <strong>should</strong> be mentioned
251      * in a <var>Trailer</var> header in the request (see section 14.40
252      * of draft-ietf-http-v11-spec-rev-06.txt).
253      *
254      * <P>This method (and its related <code>getTrailers()</code>)) are
255      * in this class and not in <var>Request</var> because setting
256      * trailers is something an application may want to do, not only
257      * modules.
258      *
259      * @param trailers an array of header fields
260      */
261     public void setTrailers(NVPair[] trailers)
262     {
263 	if (trailers != null)
264 	    this.trailers = trailers;
265 	else
266 	    this.trailers = empty;
267     }
268 
269 
270     /**
271      * Reset this output stream, so it may be reused in a retried request.
272      * This method may only be invoked by modules, and <strong>must</strong>
273      * never be invoked by an application.
274      */
275     public void reset()
276     {
277 	rcvd     = 0;
278 	req      = null;
279 	resp     = null;
280 	os       = null;
281 	bos      = null;
282 	con_to   = 0;
283 	ignore   = false;
284     }
285 
286 
287     /**
288      * Writes a single byte on the stream. It is subject to the same rules
289      * as <code>write(byte[], int, int)</code>.
290      *
291      * @param b the byte to write
292      * @exception IOException if any exception is thrown by the socket
293      * @see #write(byte[], int, int)
294      */
295     public void write(int b)  throws IOException, IllegalAccessError
296     {
297 	byte[] tmp = { (byte) b };
298 	write(tmp, 0, 1);
299     }
300 
301 
302     /**
303      * Writes an array of bytes on the stream. This method may not be used
304      * until this stream has been passed to one of the methods in
305      * HTTPConnection (i.e. until it has been associated with a request).
306      *
307      * @param buf an array containing the data to write
308      * @param off the offset of the data whithin the buffer
309      * @param len the number bytes (starting at <var>off</var>) to write
310      * @exception IOException if any exception is thrown by the socket, or
311      *            if writing <var>len</var> bytes would cause more bytes to
312      *            be written than this stream is willing to accept.
313      * @exception IllegalAccessError if this stream has not been associated
314      *            with a request yet
315      */
316     public synchronized void write(byte[] buf, int off, int len)
317 	    throws IOException, IllegalAccessError
318     {
319 	if (req == null)
320 	    throw new IllegalAccessError("Stream not associated with a request");
321 
322 	if (ignore) return;
323 
324 	if (length != -1  &&  rcvd+len > length)
325 	{
326 	    IOException ioe =
327 		new IOException("Tried to write too many bytes (" + (rcvd+len) +
328 				" > " + length + ")");
329 	    req.getConnection().closeDemux(ioe, false);
330 	    req.getConnection().outputFinished();
331 	    throw ioe;
332 	}
333 
334 	try
335 	{
336 	    if (bos != null)
337 		bos.write(buf, off, len);
338 	    else if (length != -1)
339 		os.write(buf, off, len);
340 	    else
341 		os.write(Codecs.chunkedEncode(buf, off, len, null, false));
342 	}
343 	catch (IOException ioe)
344 	{
345 	    req.getConnection().closeDemux(ioe, true);
346 	    req.getConnection().outputFinished();
347 	    throw ioe;
348 	}
349 
350 	rcvd += len;
351     }
352 
353 
354     /**
355      * Closes the stream and causes the data to be sent if it has not already
356      * been done so. This method <strong>must</strong> be invoked when all
357      * data has been written.
358      *
359      * @exception IOException if any exception is thrown by the underlying
360      *            socket, or if too few bytes were written.
361      * @exception IllegalAccessError if this stream has not been associated
362      *            with a request yet.
363      */
364     public synchronized void close()  throws IOException, IllegalAccessError
365     {
366 	if (req == null)
367 	    throw new IllegalAccessError("Stream not associated with a request");
368 
369 	if (ignore) return;
370 
371 	if (bos != null)
372 	{
373 	    req.setData(bos.toByteArray());
374 	    req.setStream(null);
375 
376 	    if (trailers.length > 0)
377 	    {
378 		NVPair[] hdrs = req.getHeaders();
379 
380 		// remove any Trailer header field
381 
382 		int len = hdrs.length;
383 		for (int idx=0; idx<len; idx++)
384 		{
385 		    if (hdrs[idx].getName().equalsIgnoreCase("Trailer"))
386 		    {
387 			System.arraycopy(hdrs, idx+1, hdrs, idx, len-idx-1);
388 			len--;
389 		    }
390 		}
391 
392 
393 		// add the trailers to the headers
394 
395 		hdrs = Util.resizeArray(hdrs, len+trailers.length);
396 		System.arraycopy(trailers, 0, hdrs, len, trailers.length);
397 
398 		req.setHeaders(hdrs);
399 	    }
400 
401 	    Log.write(Log.CONN, "OutS:  Sending request");
402 
403 	    try
404 		{ resp = req.getConnection().sendRequest(req, con_to); }
405 	    catch (ModuleException me)
406 		{ throw new IOException(me.toString()); }
407 	    notify();
408 	}
409 	else
410 	{
411 	    if (rcvd < length)
412 	    {
413 		IOException ioe =
414 		    new IOException("Premature close: only " + rcvd +
415 				    " bytes written instead of the " +
416 				    "expected " + length);
417 		req.getConnection().closeDemux(ioe, false);
418 		req.getConnection().outputFinished();
419 		throw ioe;
420 	    }
421 
422 	    try
423 	    {
424 		if (length == -1)
425 		{
426 		    if (Log.isEnabled(Log.CONN)  &&  trailers.length > 0)
427 		    {
428 			Log.write(Log.CONN, "OutS:  Sending trailers:");
429 			for (int idx=0; idx<trailers.length; idx++)
430 			    Log.write(Log.CONN, "       " +
431 						trailers[idx].getName() + ": " +
432 						trailers[idx].getValue());
433 		    }
434 
435 		    os.write(Codecs.chunkedEncode(null, 0, 0, trailers, true));
436 		}
437 
438 		os.flush();
439 
440 		Log.write(Log.CONN, "OutS:  All data sent");
441 	    }
442 	    catch (IOException ioe)
443 	    {
444 		req.getConnection().closeDemux(ioe, true);
445 		throw ioe;
446 	    }
447 	    finally
448 	    {
449 		req.getConnection().outputFinished();
450 	    }
451 	}
452     }
453 
454 
455     /**
456      * produces a string describing this stream.
457      *
458      * @return a string containing the name and the length
459      */
460     public String toString()
461     {
462 	return getClass().getName() + "[length=" + length + "]";
463     }
464 }