001: /*
002: * @(#)HttpOutputStream.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.OutputStream;
032: import java.io.ByteArrayOutputStream;
033: import java.io.IOException;
034:
035: /**
036: * This class provides an output stream for requests. The stream must first
037: * be associated with a request before it may be used; this is done by
038: * passing it to one of the request methods in HTTPConnection. Example:
039: * <PRE>
040: * OutputStream out = new HttpOutputStream(12345);
041: * rsp = con.Post("/cgi-bin/my_cgi", out);
042: * out.write(...);
043: * out.close();
044: * if (rsp.getStatusCode() >= 300)
045: * ...
046: * </PRE>
047: *
048: * <P>There are two constructors for this class, one taking a length parameter,
049: * and one without any parameters. If the stream is created with a length
050: * then the request will be sent with the corresponding Content-length header
051: * and anything written to the stream will be written on the socket immediately.
052: * This is the preferred way. If the stream is created without a length then
053: * one of two things will happen: if, at the time of the request, the server
054: * is known to understand HTTP/1.1 then each write() will send the data
055: * immediately using the chunked encoding. If, however, either the server
056: * version is unknown (because this is first request to that server) or the
057: * server only understands HTTP/1.0 then all data will be written to a buffer
058: * first, and only when the stream is closed will the request be sent.
059: *
060: * <P>Another reason that using the <var>HttpOutputStream(length)</var>
061: * constructor is recommended over the <var>HttpOutputStream()</var> one is
062: * that some HTTP/1.1 servers do not allow the chunked transfer encoding to
063: * be used when POSTing to a cgi script. This is because the way the cgi API
064: * is defined the cgi script expects a Content-length environment variable.
065: * If the data is sent using the chunked transfer encoding however, then the
066: * server would have to buffer all the data before invoking the cgi so that
067: * this variable could be set correctly. Not all servers are willing to do
068: * this.
069: *
070: * <P>If you cannot use the <var>HttpOutputStream(length)</var> constructor and
071: * are having problems sending requests (usually a 411 response) then you can
072: * try setting the system property <var>HTTPClient.dontChunkRequests</var> to
073: * <var>true</var> (this needs to be done either on the command line or
074: * somewhere in the code before the HTTPConnection is first accessed). This
075: * will prevent the client from using the chunked encoding in this case and
076: * will cause the HttpOutputStream to buffer all the data instead, sending it
077: * only when close() is invoked.
078: *
079: * <P>The behaviour of a request sent with an output stream may differ from
080: * that of a request sent with a data parameter. The reason for this is that
081: * the various modules cannot resend a request which used an output stream.
082: * Therefore such things as authorization and retrying of requests won't be
083: * done by the HTTPClient for such requests.
084: *
085: * @version 0.3-2 18/06/1999
086: * @author Ronald Tschalär
087: * @since V0.3
088: */
089:
090: public class HttpOutputStream extends OutputStream implements
091: GlobalConstants {
092: /** null trailers */
093: private static final NVPair[] empty = new NVPair[0];
094:
095: /** the length of the data to be sent */
096: private int length;
097:
098: /** the length of the data received so far */
099: private int rcvd = 0;
100:
101: /** the request this stream is associated with */
102: private Request req = null;
103:
104: /** the response from sendRequest if we stalled the request */
105: private Response resp = null;
106:
107: /** the socket output stream */
108: private OutputStream os = null;
109:
110: /** the buffer to be used if needed */
111: private ByteArrayOutputStream bos = null;
112:
113: /** the trailers to send if using chunked encoding. */
114: private NVPair[] trailers = empty;
115:
116: /** the timeout to pass to SendRequest() */
117: private int con_to = 0;
118:
119: /** just ignore all the data if told to do so */
120: private boolean ignore = false;
121:
122: // Constructors
123:
124: /**
125: * Creates an output stream of unspecified length. Note that it is
126: * <strong>highly</strong> recommended that this constructor be avoided
127: * where possible and <code>HttpOutputStream(int)</code> used instead.
128: *
129: * @see HttpOutputStream#HttpOutputStream(int)
130: */
131: public HttpOutputStream() {
132: length = -1;
133: }
134:
135: /**
136: * This creates an output stream which will take <var>length</var> bytes
137: * of data.
138: *
139: * @param length the number of bytes which will be sent over this stream
140: */
141: public HttpOutputStream(int length) {
142: if (length < 0)
143: throw new IllegalArgumentException(
144: "Length must be greater equal 0");
145: this .length = length;
146: }
147:
148: // Methods
149:
150: /**
151: * Associates this stream with a request and the actual output stream.
152: * No other methods in this class may be invoked until this method has
153: * been invoked by the HTTPConnection.
154: *
155: * @param req the request this stream is to be associated with
156: * @param os the underlying output stream to write our data to, or null
157: * if we should write to a ByteArrayOutputStream instead.
158: * @param con_to connection timeout to use in sendRequest()
159: */
160: void goAhead(Request req, OutputStream os, int con_to) {
161: this .req = req;
162: this .os = os;
163: this .con_to = con_to;
164:
165: if (os == null)
166: bos = new ByteArrayOutputStream();
167:
168: if (DebugConn) {
169: System.err.println("OutS: Stream ready for writing");
170: if (bos != null)
171: System.err
172: .println("OutS: Buffering all data before sending "
173: + "request");
174: }
175: }
176:
177: /**
178: * Setup this stream to dump the data to the great bit-bucket in the sky.
179: * This is needed for when a module handles the request directly.
180: *
181: * @param req the request this stream is to be associated with
182: */
183: void ignoreData(Request req) {
184: this .req = req;
185: ignore = true;
186: }
187:
188: /**
189: * Return the response we got from sendRequest(). This waits until
190: * the request has actually been sent.
191: *
192: * @return the response returned by sendRequest()
193: */
194: synchronized Response getResponse() {
195: while (resp == null)
196: try {
197: wait();
198: } catch (InterruptedException ie) {
199: }
200:
201: return resp;
202: }
203:
204: /**
205: * Returns the number of bytes this stream is willing to accept, or -1
206: * if it is unbounded.
207: *
208: * @return the number of bytes
209: */
210: public int getLength() {
211: return length;
212: }
213:
214: /**
215: * Gets the trailers which were set with <code>setTrailers()</code>.
216: *
217: * @return an array of header fields
218: * @see #setTrailers(NVPair[])
219: */
220: public NVPair[] getTrailers() {
221: return trailers;
222: }
223:
224: /**
225: * Sets the trailers to be sent if the output is sent with the
226: * chunked transfer encoding. These must be set before the output
227: * stream is closed for them to be sent.
228: *
229: * <P>Any trailers set here <strong>should</strong> be mentioned
230: * in a <var>Trailer</var> header in the request (see section 14.40
231: * of draft-ietf-http-v11-spec-rev-06.txt).
232: *
233: * <P>This method (and its related <code>getTrailers()</code>)) are
234: * in this class and not in <var>Request</var> because setting
235: * trailers is something an application may want to do, not only
236: * modules.
237: *
238: * @param trailers an array of header fields
239: */
240: public void setTrailers(NVPair[] trailers) {
241: if (trailers != null)
242: this .trailers = trailers;
243: else
244: this .trailers = empty;
245: }
246:
247: /**
248: * Writes a single byte on the stream. It is subject to the same rules
249: * as <code>write(byte[], int, int)</code>.
250: *
251: * @param b the byte to write
252: * @exception IOException if any exception is thrown by the socket
253: * @see #write(byte[], int, int)
254: */
255: public void write(int b) throws IOException, IllegalAccessError {
256: byte[] tmp = { (byte) b };
257: write(tmp, 0, 1);
258: }
259:
260: /**
261: * Writes an array of bytes on the stream. This method may not be used
262: * until this stream has been passed to one of the methods in
263: * HTTPConnection (i.e. until it has been associated with a request).
264: *
265: * @param buf an array containing the data to write
266: * @param off the offset of the data whithin the buffer
267: * @param len the number bytes (starting at <var>off</var>) to write
268: * @exception IOException if any exception is thrown by the socket, or
269: * if writing <var>len</var> bytes would cause more bytes to
270: * be written than this stream is willing to accept.
271: * @exception IllegalAccessError if this stream has not been associated
272: * with a request yet
273: */
274: public synchronized void write(byte[] buf, int off, int len)
275: throws IOException, IllegalAccessError {
276: if (req == null)
277: throw new IllegalAccessError(
278: "Stream not associated with a request");
279:
280: if (ignore)
281: return;
282:
283: if (length != -1 && rcvd + len > length) {
284: IOException ioe = new IOException(
285: "Tried to write too many bytes (" + (rcvd + len)
286: + " > " + length + ")");
287: req.getConnection().closeDemux(ioe, false);
288: req.getConnection().outputFinished();
289: throw ioe;
290: }
291:
292: try {
293: if (bos != null)
294: bos.write(buf, off, len);
295: else if (length != -1)
296: os.write(buf, off, len);
297: else
298: os.write(Codecs.chunkedEncode(buf, off, len, null,
299: false));
300: } catch (IOException ioe) {
301: req.getConnection().closeDemux(ioe, true);
302: req.getConnection().outputFinished();
303: throw ioe;
304: }
305:
306: rcvd += len;
307: }
308:
309: /**
310: * Closes the stream and causes the data to be sent if it has not already
311: * been done so. This method <strong>must</strong> be invoked when all
312: * data has been written.
313: *
314: * @exception IOException if any exception is thrown by the underlying
315: * socket, or if too few bytes were written.
316: * @exception IllegalAccessError if this stream has not been associated
317: * with a request yet.
318: */
319: public synchronized void close() throws IOException,
320: IllegalAccessError {
321: if (req == null)
322: throw new IllegalAccessError(
323: "Stream not associated with a request");
324:
325: if (ignore)
326: return;
327:
328: if (bos != null) {
329: req.setData(bos.toByteArray());
330: req.setStream(null);
331:
332: if (trailers.length > 0) {
333: NVPair[] hdrs = req.getHeaders();
334:
335: // remove any Trailer header field
336:
337: int len = hdrs.length;
338: for (int idx = 0; idx < len; idx++) {
339: if (hdrs[idx].getName().equalsIgnoreCase("Trailer")) {
340: System.arraycopy(hdrs, idx + 1, hdrs, idx, len
341: - idx - 1);
342: len--;
343: }
344: }
345:
346: // add the trailers to the headers
347:
348: hdrs = Util.resizeArray(hdrs, len + trailers.length);
349: System.arraycopy(trailers, 0, hdrs, len,
350: trailers.length);
351:
352: req.setHeaders(hdrs);
353: }
354:
355: if (DebugConn)
356: System.err.println("OutS: Sending request");
357:
358: try {
359: resp = req.getConnection().sendRequest(req, con_to);
360: } catch (ModuleException me) {
361: throw new IOException(me.toString());
362: }
363: notify();
364: } else {
365: if (rcvd < length) {
366: IOException ioe = new IOException(
367: "Premature close: only " + rcvd
368: + " bytes written instead of the "
369: + "expected " + length);
370: req.getConnection().closeDemux(ioe, false);
371: req.getConnection().outputFinished();
372: throw ioe;
373: }
374:
375: try {
376: if (length == -1) {
377: if (DebugConn && trailers.length > 0) {
378: System.err.println("OutS: Sending trailers:");
379: for (int idx = 0; idx < trailers.length; idx++)
380: System.err.println(" "
381: + trailers[idx].getName() + ": "
382: + trailers[idx].getValue());
383: }
384:
385: os.write(Codecs.chunkedEncode(null, 0, 0, trailers,
386: true));
387: }
388:
389: os.flush();
390:
391: if (DebugConn)
392: System.err.println("OutS: All data sent");
393: } catch (IOException ioe) {
394: req.getConnection().closeDemux(ioe, true);
395: throw ioe;
396: } finally {
397: req.getConnection().outputFinished();
398: }
399: }
400: }
401:
402: /**
403: * produces a string describing this stream.
404: *
405: * @return a string containing the name and the length
406: */
407: public String toString() {
408: return getClass().getName() + "[length=" + length + "]";
409: }
410: }
|