/**************************************************************************
* Parts copyright (c) 2001 by Punch Telematix. All rights reserved. *
* Parts copyright (c) 2005, 2011 by Chris Gray, /k/ Embedded Java *
* Solutions. All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions*
* nor the names of other contributors may be used to endorse or promote*
* products derived from this software without specific prior written *
* permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER *
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package wonka.net.http;
import java.io.IOException;
import java.io.OutputStream;
class HttpOutputStream extends OutputStream {
private static final byte[] CHUNKED = "Transfer-Encoding: chunked\r\n\r\n".getBytes();
private static final byte[] NEWLINE = new byte[]{(byte)'\r',(byte)'\n'};
/**
** The default size of the buffer used. Chunking is applied if the content
** length is unknown and more data than this is written to the
** HttpOutputStream, or if <code>flush()</code> is called.
*/
private static int default_bufsize;
/**
** Get <var>default_bufsize</var> from system property
** <code>wonka.http.request.buffer.size</code>; default is 4096 bytes.
*/
static {
default_bufsize = Integer.getInteger("wonka.http.request.buffer.size", 4096).intValue();
}
/**
** The OutputStream around which this HttpOutputStream is wrapped.
*/
private OutputStream out;
/**
** The number of bytes written to this HttpOutputStream and not yet
** passed on to <var>out</var>.
*/
private int count;
/**
** The buffer used to transfer data from this HttpOutputStream to <var>out</var>.
*/
private byte[] buffer;
/**
** True iff this HttpOutputStream has already been closed.
*/
private boolean closed;
/**
** True iff this HttpOutputStream is using chunked mode.
*/
private boolean chunked;
private boolean contentLengthSent;
private boolean headersSent;
/**
** Construct a HttpOutputStream which wraps <var>out</var>.
*/
HttpOutputStream(OutputStream out, int contentLength) throws IOException {
this.out = out;
if (contentLength < 0) {
buffer = new byte[default_bufsize];
}
else {
contentLengthSent = true;
}
}
/**
** If the connection is not already closed, flush the data to <var>out</var>
** and close <var>out</out>. Process the response.
*/
public void close() throws IOException {
if(closed) {
return;
}
closed = true;
if(chunked){
if(count > 0){
flushBuffer(buffer,0);
}
out.write('0');
out.write(NEWLINE,0,2);
out.write(NEWLINE,0,2);
}
else {
finishHeadersAndFlushBuffer();
}
out.flush();
}
private void checkHeadersSent() throws IOException {
if (headersSent) {
return;
}
if (!contentLengthSent) {
out.write(("Content-Length: "+count).getBytes());
out.write(NEWLINE,0,2);
contentLengthSent = true;
}
out.write(NEWLINE,0,2);
headersSent = true;
}
private void finishHeadersAndFlushBuffer() throws IOException {
checkHeadersSent();
if (buffer != null) {
out.write(buffer,0,count);
count = 0;
buffer = null;
}
}
/**
** Variant of flush() used by BasicHttpURLConnection.
** If operating in chunked mode, flush the current contents of <var>buffer</var> to <var>out</var>.
** If not operating in chunked mode and data is available, send the data.
** Otherwise do nothing.
*/
void flush_internal() throws IOException {
if(closed){
throw new IOException("Stream is closed");
}
if (chunked) {
if (count > 0){
flushBuffer(buffer, 0);
}
}
else {
finishHeadersAndFlushBuffer();
out.flush();
}
}
/**
** Flush the current contents of <var>buffer</var> to <var>out</var>,
** forcing chunked mode.
*/
public void flush() throws IOException {
if(closed){
throw new IOException("Stream is closed");
}
if (count > 0){
flushBuffer(buffer, 0);
}
}
/**
** Add one byte to the buffer. If the buffer is already full, flush it first
** (thereby forcing "chunked" mode).
*/
public void write(int b) throws IOException {
if(closed){
throw new IOException("Stream is closed");
}
if (buffer == null) {
checkHeadersSent();
out.write(b);
return;
}
if(count == buffer.length){
flushBuffer(buffer, 0);
}
buffer[count++] = (byte)b;
}
/**
** Add bytes to the buffer, if there is room. Otherwise first flush the
** buffer to <var>out</var> (thereby forcing "chunked" mode), and then
** write the new chars directly to <var>out</var> as a separate chunk.
*/
public void write(byte[] bytes, int off, int length) throws IOException {
if(closed){
throw new IOException("Stream is closed");
}
if (buffer == null) {
checkHeadersSent();
out.write(bytes, off, length);
return;
}
if(length > (buffer.length - count)){
if(count > 0){
flushBuffer(buffer, 0);
}
if(length > buffer.length){
count = length;
flushBuffer(bytes, off);
return;
}
}
System.arraycopy(bytes,off,buffer,count,length);
count += length;
}
/**
** Flush the given buffer (which may be our internal <var>buffer</var>
** or an external one) to <var>out</var>. Enables "chunked" mode if not
** already enabled.
*/
private void flushBuffer(byte[] buf, int off) throws IOException {
if(!chunked){
chunked = true;
out.write(CHUNKED,0,CHUNKED.length);
}
out.write((Integer.toHexString(count)).getBytes());
out.write(NEWLINE,0,2);
out.write(buf,off, count);
count = 0;
out.write(NEWLINE,0,2);
out.flush();
}
}