HttpOutputStream.java
资源名称:HTTP客户端.zip [点击查看]
上传用户:demmber
上传日期:2007-12-22
资源大小:717k
文件大小:13k
源码类别:
Java编程
开发平台:
Java
- /*
- * @(#)HttpOutputStream.java 0.3-3 06/05/2001
- *
- * This file is part of the HTTPClient package
- * Copyright (C) 1996-2001 Ronald Tschal鋜
- *
- * 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:
- *
- * ronald@innovation.ch
- *
- * The HTTPClient's home page is located at:
- *
- * http://www.innovation.ch/java/HTTPClient/
- *
- */
- 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:
- * <PRE>
- * OutputStream out = new HttpOutputStream(12345);
- * rsp = con.Post("/cgi-bin/my_cgi", out);
- * out.write(...);
- * out.close();
- * if (rsp.getStatusCode() >= 300)
- * ...
- * </PRE>
- *
- * <P>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.
- *
- * <P>Another reason that using the <var>HttpOutputStream(length)</var>
- * constructor is recommended over the <var>HttpOutputStream()</var> 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.
- *
- * <P>If you cannot use the <var>HttpOutputStream(length)</var> constructor and
- * are having problems sending requests (usually a 411 response) then you can
- * try setting the system property <var>HTTPClient.dontChunkRequests</var> to
- * <var>true</var> (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.
- *
- * <P>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鋜
- * @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 */
- private int length;
- /** the length of the data received so far */
- private int rcvd = 0;
- /** 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
- * <strong>highly</strong> recommended that this constructor be avoided
- * where possible and <code>HttpOutputStream(int)</code> used instead.
- *
- * @see HttpOutputStream#HttpOutputStream(int)
- */
- public HttpOutputStream()
- {
- length = -1;
- }
- /**
- * This creates an output stream which will take <var>length</var> bytes
- * of data.
- *
- * @param length the number of bytes which will be sent over this stream
- */
- public HttpOutputStream(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 int getLength()
- {
- return length;
- }
- /**
- * Gets the trailers which were set with <code>setTrailers()</code>.
- *
- * @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.
- *
- * <P>Any trailers set here <strong>should</strong> be mentioned
- * in a <var>Trailer</var> header in the request (see section 14.40
- * of draft-ietf-http-v11-spec-rev-06.txt).
- *
- * <P>This method (and its related <code>getTrailers()</code>)) are
- * in this class and not in <var>Request</var> 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 <strong>must</strong>
- * 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 <code>write(byte[], int, int)</code>.
- *
- * @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 <var>off</var>) to write
- * @exception IOException if any exception is thrown by the socket, or
- * if writing <var>len</var> 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 <strong>must</strong> 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<len; idx++)
- {
- if (hdrs[idx].getName().equalsIgnoreCase("Trailer"))
- {
- System.arraycopy(hdrs, idx+1, hdrs, idx, len-idx-1);
- len--;
- }
- }
- // add the trailers to the headers
- hdrs = Util.resizeArray(hdrs, len+trailers.length);
- System.arraycopy(trailers, 0, hdrs, len, trailers.length);
- req.setHeaders(hdrs);
- }
- Log.write(Log.CONN, "OutS: Sending request");
- try
- { resp = req.getConnection().sendRequest(req, con_to); }
- catch (ModuleException me)
- { throw new IOException(me.toString()); }
- notify();
- }
- else
- {
- if (rcvd < length)
- {
- IOException ioe =
- new IOException("Premature close: only " + rcvd +
- " bytes written instead of the " +
- "expected " + length);
- req.getConnection().closeDemux(ioe, false);
- req.getConnection().outputFinished();
- throw ioe;
- }
- try
- {
- if (length == -1)
- {
- if (Log.isEnabled(Log.CONN) && trailers.length > 0)
- {
- Log.write(Log.CONN, "OutS: Sending trailers:");
- for (int idx=0; idx<trailers.length; idx++)
- Log.write(Log.CONN, " " +
- trailers[idx].getName() + ": " +
- trailers[idx].getValue());
- }
- os.write(Codecs.chunkedEncode(null, 0, 0, trailers, true));
- }
- os.flush();
- Log.write(Log.CONN, "OutS: All data sent");
- }
- catch (IOException ioe)
- {
- req.getConnection().closeDemux(ioe, true);
- throw ioe;
- }
- finally
- {
- req.getConnection().outputFinished();
- }
- }
- }
- /**
- * produces a string describing this stream.
- *
- * @return a string containing the name and the length
- */
- public String toString()
- {
- return getClass().getName() + "[length=" + length + "]";
- }
- }
English
