All Downloads are FREE. Search and download functionalities are using the official Maven repository.

HTTPClient.HttpOutputStream Maven / Gradle / Ivy

Go to download

Modified version of HTTPClient used by The Grinder. The original can be found at http://www.innovation.ch/java/HTTPClient/.

There is a newer version: 3.11
Show newest version
/*
 * @(#)HttpOutputStream.java				0.3-3 06/05/2001
 *
 *  This file is part of the HTTPClient package
 *  Copyright (C) 1996-2001 Ronald Tschalär
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA 02111-1307, USA
 *
 *  For questions, suggestions, bug-reports, enhancement-requests etc.
 *  I may be contacted at:
 *
 *  [email protected]
 *
 *  The HTTPClient's home page is located at:
 *
 *  http://www.innovation.ch/java/HTTPClient/
 *
 * This file contains modifications for use with "The Grinder"
 * (http://grinder.sourceforge.net) under the terms of the LGPL. They
 * are marked below with the comment "GRINDER MODIFICATION".
 */

package HTTPClient;

import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * This class provides an output stream for requests. The stream must first
 * be associated with a request before it may be used; this is done by
 * passing it to one of the request methods in HTTPConnection. Example:
 * 
 *    OutputStream out = new HttpOutputStream(12345);
 *    rsp = con.Post("/cgi-bin/my_cgi", out);
 *    out.write(...);
 *    out.close();
 *    if (rsp.getStatusCode() >= 300)
 *        ...
 * 
* *

There are two constructors for this class, one taking a length parameter, * and one without any parameters. If the stream is created with a length * then the request will be sent with the corresponding Content-length header * and anything written to the stream will be written on the socket immediately. * This is the preferred way. If the stream is created without a length then * one of two things will happen: if, at the time of the request, the server * is known to understand HTTP/1.1 then each write() will send the data * immediately using the chunked encoding. If, however, either the server * version is unknown (because this is first request to that server) or the * server only understands HTTP/1.0 then all data will be written to a buffer * first, and only when the stream is closed will the request be sent. * *

Another reason that using the HttpOutputStream(length) * constructor is recommended over the HttpOutputStream() one is * that some HTTP/1.1 servers do not allow the chunked transfer encoding to * be used when POSTing to a cgi script. This is because the way the cgi API * is defined the cgi script expects a Content-length environment variable. * If the data is sent using the chunked transfer encoding however, then the * server would have to buffer all the data before invoking the cgi so that * this variable could be set correctly. Not all servers are willing to do * this. * *

If you cannot use the HttpOutputStream(length) constructor and * are having problems sending requests (usually a 411 response) then you can * try setting the system property HTTPClient.dontChunkRequests to * true (this needs to be done either on the command line or * somewhere in the code before the HTTPConnection is first accessed). This * will prevent the client from using the chunked encoding in this case and * will cause the HttpOutputStream to buffer all the data instead, sending it * only when close() is invoked. * *

The behaviour of a request sent with an output stream may differ from * that of a request sent with a data parameter. The reason for this is that * the various modules cannot resend a request which used an output stream. * Therefore such things as authorization and retrying of requests won't be * done by the HTTPClient for such requests. But see {@link * HTTPResponse#retryRequest() HTTPResponse.retryRequest} for a partial * solution. * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär * @since V0.3 */ public class HttpOutputStream extends OutputStream { /** null trailers */ private static final NVPair[] empty = new NVPair[0]; /** the length of the data to be sent */ /** ++GRINDER MODIFICATION **/ //private int length; private long length; /** the length of the data received so far */ // private int rcvd = 0; private long rcvd = 0; /** --GRINDER MODIFICATION **/ /** the request this stream is associated with */ private Request req = null; /** the response from sendRequest if we stalled the request */ private Response resp = null; /** the socket output stream */ private OutputStream os = null; /** the buffer to be used if needed */ private ByteArrayOutputStream bos = null; /** the trailers to send if using chunked encoding. */ private NVPair[] trailers = empty; /** the timeout to pass to SendRequest() */ private int con_to = 0; /** just ignore all the data if told to do so */ private boolean ignore = false; // Constructors /** * Creates an output stream of unspecified length. Note that it is * highly recommended that this constructor be avoided * where possible and HttpOutputStream(int) used instead. * * @see HttpOutputStream#HttpOutputStream(int) */ public HttpOutputStream() { length = -1; } /** * This creates an output stream which will take length bytes * of data. * * @param length the number of bytes which will be sent over this stream */ public HttpOutputStream(long /*GRINDER MODIFICATION int */ length) { if (length < 0) throw new IllegalArgumentException("Length must be greater equal 0"); this.length = length; } // Methods /** * Associates this stream with a request and the actual output stream. * No other methods in this class may be invoked until this method has * been invoked by the HTTPConnection. * * @param req the request this stream is to be associated with * @param os the underlying output stream to write our data to, or null * if we should write to a ByteArrayOutputStream instead. * @param con_to connection timeout to use in sendRequest() */ void goAhead(Request req, OutputStream os, int con_to) { this.req = req; this.os = os; this.con_to = con_to; if (os == null) bos = new ByteArrayOutputStream(); Log.write(Log.CONN, "OutS: Stream ready for writing"); if (bos != null) Log.write(Log.CONN, "OutS: Buffering all data before sending " + "request"); } /** * Setup this stream to dump the data to the great bit-bucket in the sky. * This is needed for when a module handles the request directly. * * @param req the request this stream is to be associated with */ void ignoreData(Request req) { this.req = req; ignore = true; } /** * Return the response we got from sendRequest(). This waits until * the request has actually been sent. * * @return the response returned by sendRequest() */ synchronized Response getResponse() { while (resp == null) try { wait(); } catch (InterruptedException ie) { } return resp; } /** * Returns the number of bytes this stream is willing to accept, or -1 * if it is unbounded. * * @return the number of bytes */ public long /* GRINDER MODIFICICATION int */ getLength() { return length; } /** * Gets the trailers which were set with setTrailers(). * * @return an array of header fields * @see #setTrailers(HTTPClient.NVPair[]) */ public NVPair[] getTrailers() { return trailers; } /** * Sets the trailers to be sent if the output is sent with the * chunked transfer encoding. These must be set before the output * stream is closed for them to be sent. * *

Any trailers set here should be mentioned * in a Trailer header in the request (see section 14.40 * of draft-ietf-http-v11-spec-rev-06.txt). * *

This method (and its related getTrailers())) are * in this class and not in Request because setting * trailers is something an application may want to do, not only * modules. * * @param trailers an array of header fields */ public void setTrailers(NVPair[] trailers) { if (trailers != null) this.trailers = trailers; else this.trailers = empty; } /** * Reset this output stream, so it may be reused in a retried request. * This method may only be invoked by modules, and must * never be invoked by an application. */ public void reset() { rcvd = 0; req = null; resp = null; os = null; bos = null; con_to = 0; ignore = false; } /** * Writes a single byte on the stream. It is subject to the same rules * as write(byte[], int, int). * * @param b the byte to write * @exception IOException if any exception is thrown by the socket * @see #write(byte[], int, int) */ public void write(int b) throws IOException, IllegalAccessError { byte[] tmp = { (byte) b }; write(tmp, 0, 1); } /** * Writes an array of bytes on the stream. This method may not be used * until this stream has been passed to one of the methods in * HTTPConnection (i.e. until it has been associated with a request). * * @param buf an array containing the data to write * @param off the offset of the data whithin the buffer * @param len the number bytes (starting at off) to write * @exception IOException if any exception is thrown by the socket, or * if writing len bytes would cause more bytes to * be written than this stream is willing to accept. * @exception IllegalAccessError if this stream has not been associated * with a request yet */ public synchronized void write(byte[] buf, int off, int len) throws IOException, IllegalAccessError { if (req == null) throw new IllegalAccessError("Stream not associated with a request"); if (ignore) return; if (length != -1 && rcvd+len > length) { IOException ioe = new IOException("Tried to write too many bytes (" + (rcvd+len) + " > " + length + ")"); req.getConnection().closeDemux(ioe, false); req.getConnection().outputFinished(); throw ioe; } try { if (bos != null) bos.write(buf, off, len); else if (length != -1) os.write(buf, off, len); else os.write(Codecs.chunkedEncode(buf, off, len, null, false)); } catch (IOException ioe) { req.getConnection().closeDemux(ioe, true); req.getConnection().outputFinished(); throw ioe; } rcvd += len; } /** * Closes the stream and causes the data to be sent if it has not already * been done so. This method must be invoked when all * data has been written. * * @exception IOException if any exception is thrown by the underlying * socket, or if too few bytes were written. * @exception IllegalAccessError if this stream has not been associated * with a request yet. */ public synchronized void close() throws IOException, IllegalAccessError { if (req == null) throw new IllegalAccessError("Stream not associated with a request"); if (ignore) return; if (bos != null) { req.setData(bos.toByteArray()); req.setStream(null); if (trailers.length > 0) { NVPair[] hdrs = req.getHeaders(); // remove any Trailer header field int len = hdrs.length; for (int idx=0; idx 0) { Log.write(Log.CONN, "OutS: Sending trailers:"); for (int idx=0; idx





© 2015 - 2024 Weber Informatics LLC | Privacy Policy