HttpOutputStream.java
上传用户:demmber
上传日期:2007-12-22
资源大小:717k
文件大小:13k
源码类别:

Java编程

开发平台:

Java

  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鋜
  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.  */
  32. package HTTPClient;
  33. import java.io.OutputStream;
  34. import java.io.ByteArrayOutputStream;
  35. import java.io.IOException;
  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. But see {@link
  85.  * HTTPResponse#retryRequest() HTTPResponse.retryRequest} for a partial
  86.  * solution.
  87.  *
  88.  * @version 0.3-3  06/05/2001
  89.  * @author Ronald Tschal鋜
  90.  * @since V0.3
  91.  */
  92. public class HttpOutputStream extends OutputStream
  93. {
  94.     /** null trailers */
  95.     private static final NVPair[] empty = new NVPair[0];
  96.     /** the length of the data to be sent */
  97.     private int length;
  98.     /** the length of the data received so far */
  99.     private int rcvd = 0;
  100.     /** the request this stream is associated with */
  101.     private Request req = null;
  102.     /** the response from sendRequest if we stalled the request */
  103.     private Response resp = null;
  104.     /** the socket output stream */
  105.     private OutputStream os = null;
  106.     /** the buffer to be used if needed */
  107.     private ByteArrayOutputStream bos = null;
  108.     /** the trailers to send if using chunked encoding. */
  109.     private NVPair[] trailers = empty;
  110.     /** the timeout to pass to SendRequest() */
  111.     private int con_to = 0;
  112.     /** just ignore all the data if told to do so */
  113.     private boolean ignore = false;
  114.     // Constructors
  115.     /**
  116.      * Creates an output stream of unspecified length. Note that it is
  117.      * <strong>highly</strong> recommended that this constructor be avoided
  118.      * where possible and <code>HttpOutputStream(int)</code> used instead.
  119.      *
  120.      * @see HttpOutputStream#HttpOutputStream(int)
  121.      */
  122.     public HttpOutputStream()
  123.     {
  124. length = -1;
  125.     }
  126.     /**
  127.      * This creates an output stream which will take <var>length</var> bytes
  128.      * of data.
  129.      *
  130.      * @param length the number of bytes which will be sent over this stream
  131.      */
  132.     public HttpOutputStream(int length)
  133.     {
  134. if (length < 0)
  135.    throw new IllegalArgumentException("Length must be greater equal 0");
  136. this.length = length;
  137.     }
  138.     // Methods
  139.     /**
  140.      * Associates this stream with a request and the actual output stream.
  141.      * No other methods in this class may be invoked until this method has
  142.      * been invoked by the HTTPConnection.
  143.      *
  144.      * @param req    the request this stream is to be associated with
  145.      * @param os     the underlying output stream to write our data to, or null
  146.      *               if we should write to a ByteArrayOutputStream instead.
  147.      * @param con_to connection timeout to use in sendRequest()
  148.      */
  149.     void goAhead(Request req, OutputStream os, int con_to)
  150.     {
  151. this.req    = req;
  152. this.os     = os;
  153. this.con_to = con_to;
  154. if (os == null)
  155.     bos = new ByteArrayOutputStream();
  156. Log.write(Log.CONN, "OutS:  Stream ready for writing");
  157. if (bos != null)
  158.     Log.write(Log.CONN, "OutS:  Buffering all data before sending " +
  159.         "request");
  160.     }
  161.     /**
  162.      * Setup this stream to dump the data to the great bit-bucket in the sky.
  163.      * This is needed for when a module handles the request directly.
  164.      *
  165.      * @param req the request this stream is to be associated with
  166.      */
  167.     void ignoreData(Request req)
  168.     {
  169. this.req = req;
  170. ignore = true;
  171.     }
  172.     /**
  173.      * Return the response we got from sendRequest(). This waits until
  174.      * the request has actually been sent.
  175.      *
  176.      * @return the response returned by sendRequest()
  177.      */
  178.     synchronized Response getResponse()
  179.     {
  180. while (resp == null)
  181.     try { wait(); } catch (InterruptedException ie) { }
  182. return resp;
  183.     }
  184.     /**
  185.      * Returns the number of bytes this stream is willing to accept, or -1
  186.      * if it is unbounded.
  187.      *
  188.      * @return the number of bytes
  189.      */
  190.     public int getLength()
  191.     {
  192. return length;
  193.     }
  194.     /**
  195.      * Gets the trailers which were set with <code>setTrailers()</code>.
  196.      *
  197.      * @return an array of header fields
  198.      * @see #setTrailers(HTTPClient.NVPair[])
  199.      */
  200.     public NVPair[] getTrailers()
  201.     {
  202. return trailers;
  203.     }
  204.     /**
  205.      * Sets the trailers to be sent if the output is sent with the
  206.      * chunked transfer encoding. These must be set before the output
  207.      * stream is closed for them to be sent.
  208.      *
  209.      * <P>Any trailers set here <strong>should</strong> be mentioned
  210.      * in a <var>Trailer</var> header in the request (see section 14.40
  211.      * of draft-ietf-http-v11-spec-rev-06.txt).
  212.      *
  213.      * <P>This method (and its related <code>getTrailers()</code>)) are
  214.      * in this class and not in <var>Request</var> because setting
  215.      * trailers is something an application may want to do, not only
  216.      * modules.
  217.      *
  218.      * @param trailers an array of header fields
  219.      */
  220.     public void setTrailers(NVPair[] trailers)
  221.     {
  222. if (trailers != null)
  223.     this.trailers = trailers;
  224. else
  225.     this.trailers = empty;
  226.     }
  227.     /**
  228.      * Reset this output stream, so it may be reused in a retried request.
  229.      * This method may only be invoked by modules, and <strong>must</strong>
  230.      * never be invoked by an application.
  231.      */
  232.     public void reset()
  233.     {
  234. rcvd     = 0;
  235. req      = null;
  236. resp     = null;
  237. os       = null;
  238. bos      = null;
  239. con_to   = 0;
  240. ignore   = false;
  241.     }
  242.     /**
  243.      * Writes a single byte on the stream. It is subject to the same rules
  244.      * as <code>write(byte[], int, int)</code>.
  245.      *
  246.      * @param b the byte to write
  247.      * @exception IOException if any exception is thrown by the socket
  248.      * @see #write(byte[], int, int)
  249.      */
  250.     public void write(int b)  throws IOException, IllegalAccessError
  251.     {
  252. byte[] tmp = { (byte) b };
  253. write(tmp, 0, 1);
  254.     }
  255.     /**
  256.      * Writes an array of bytes on the stream. This method may not be used
  257.      * until this stream has been passed to one of the methods in
  258.      * HTTPConnection (i.e. until it has been associated with a request).
  259.      *
  260.      * @param buf an array containing the data to write
  261.      * @param off the offset of the data whithin the buffer
  262.      * @param len the number bytes (starting at <var>off</var>) to write
  263.      * @exception IOException if any exception is thrown by the socket, or
  264.      *            if writing <var>len</var> bytes would cause more bytes to
  265.      *            be written than this stream is willing to accept.
  266.      * @exception IllegalAccessError if this stream has not been associated
  267.      *            with a request yet
  268.      */
  269.     public synchronized void write(byte[] buf, int off, int len)
  270.     throws IOException, IllegalAccessError
  271.     {
  272. if (req == null)
  273.     throw new IllegalAccessError("Stream not associated with a request");
  274. if (ignore) return;
  275. if (length != -1  &&  rcvd+len > length)
  276. {
  277.     IOException ioe =
  278. new IOException("Tried to write too many bytes (" + (rcvd+len) +
  279. " > " + length + ")");
  280.     req.getConnection().closeDemux(ioe, false);
  281.     req.getConnection().outputFinished();
  282.     throw ioe;
  283. }
  284. try
  285. {
  286.     if (bos != null)
  287. bos.write(buf, off, len);
  288.     else if (length != -1)
  289. os.write(buf, off, len);
  290.     else
  291. os.write(Codecs.chunkedEncode(buf, off, len, null, false));
  292. }
  293. catch (IOException ioe)
  294. {
  295.     req.getConnection().closeDemux(ioe, true);
  296.     req.getConnection().outputFinished();
  297.     throw ioe;
  298. }
  299. rcvd += len;
  300.     }
  301.     /**
  302.      * Closes the stream and causes the data to be sent if it has not already
  303.      * been done so. This method <strong>must</strong> be invoked when all
  304.      * data has been written.
  305.      *
  306.      * @exception IOException if any exception is thrown by the underlying
  307.      *            socket, or if too few bytes were written.
  308.      * @exception IllegalAccessError if this stream has not been associated
  309.      *            with a request yet.
  310.      */
  311.     public synchronized void close()  throws IOException, IllegalAccessError
  312.     {
  313. if (req == null)
  314.     throw new IllegalAccessError("Stream not associated with a request");
  315. if (ignore) return;
  316. if (bos != null)
  317. {
  318.     req.setData(bos.toByteArray());
  319.     req.setStream(null);
  320.     if (trailers.length > 0)
  321.     {
  322. NVPair[] hdrs = req.getHeaders();
  323. // remove any Trailer header field
  324. int len = hdrs.length;
  325. for (int idx=0; idx<len; idx++)
  326. {
  327.     if (hdrs[idx].getName().equalsIgnoreCase("Trailer"))
  328.     {
  329. System.arraycopy(hdrs, idx+1, hdrs, idx, len-idx-1);
  330. len--;
  331.     }
  332. }
  333. // add the trailers to the headers
  334. hdrs = Util.resizeArray(hdrs, len+trailers.length);
  335. System.arraycopy(trailers, 0, hdrs, len, trailers.length);
  336. req.setHeaders(hdrs);
  337.     }
  338.     Log.write(Log.CONN, "OutS:  Sending request");
  339.     try
  340. { resp = req.getConnection().sendRequest(req, con_to); }
  341.     catch (ModuleException me)
  342. { throw new IOException(me.toString()); }
  343.     notify();
  344. }
  345. else
  346. {
  347.     if (rcvd < length)
  348.     {
  349. IOException ioe =
  350.     new IOException("Premature close: only " + rcvd +
  351.     " bytes written instead of the " +
  352.     "expected " + length);
  353. req.getConnection().closeDemux(ioe, false);
  354. req.getConnection().outputFinished();
  355. throw ioe;
  356.     }
  357.     try
  358.     {
  359. if (length == -1)
  360. {
  361.     if (Log.isEnabled(Log.CONN)  &&  trailers.length > 0)
  362.     {
  363. Log.write(Log.CONN, "OutS:  Sending trailers:");
  364. for (int idx=0; idx<trailers.length; idx++)
  365.     Log.write(Log.CONN, "       " +
  366. trailers[idx].getName() + ": " +
  367. trailers[idx].getValue());
  368.     }
  369.     os.write(Codecs.chunkedEncode(null, 0, 0, trailers, true));
  370. }
  371. os.flush();
  372. Log.write(Log.CONN, "OutS:  All data sent");
  373.     }
  374.     catch (IOException ioe)
  375.     {
  376. req.getConnection().closeDemux(ioe, true);
  377. throw ioe;
  378.     }
  379.     finally
  380.     {
  381. req.getConnection().outputFinished();
  382.     }
  383. }
  384.     }
  385.     /**
  386.      * produces a string describing this stream.
  387.      *
  388.      * @return a string containing the name and the length
  389.      */
  390.     public String toString()
  391.     {
  392. return getClass().getName() + "[length=" + length + "]";
  393.     }
  394. }