org.eclipse.jetty.server.HttpOutput Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.BlockingCallback;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
/**
* {@link HttpOutput} implements {@link ServletOutputStream}
* as required by the Servlet specification.
* {@link HttpOutput} buffers content written by the application until a
* further write will overflow the buffer, at which point it triggers a commit
* of the response.
* {@link HttpOutput} can be closed and reopened, to allow requests included
* via {@link RequestDispatcher#include(ServletRequest, ServletResponse)} to
* close the stream, to be reopened after the inclusion ends.
*/
public class HttpOutput extends ServletOutputStream
{
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel _channel;
private boolean _closed;
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
public HttpOutput(HttpChannel channel)
{
_channel = channel;
_bufferSize = _channel.getHttpConfiguration().getOutputBufferSize();
}
public boolean isWritten()
{
return _written > 0;
}
public long getWritten()
{
return _written;
}
public void reset()
{
_written = 0;
reopen();
}
public void reopen()
{
_closed = false;
}
/** Called by the HttpChannel if the output was closed
* externally (eg by a 500 exception handling).
*/
void closed()
{
if (!_closed)
{
_closed = true;
try
{
_channel.getResponse().closeOutput();
}
catch(IOException e)
{
_channel.failed();
LOG.ignore(e);
}
releaseBuffer();
}
}
@Override
public void close()
{
if (!isClosed())
{
try
{
if (BufferUtil.hasContent(_aggregate))
_channel.write(_aggregate, !_channel.getResponse().isIncluding());
else
_channel.write(BufferUtil.EMPTY_BUFFER, !_channel.getResponse().isIncluding());
}
catch(IOException e)
{
_channel.failed();
LOG.ignore(e);
}
}
closed();
}
private void releaseBuffer()
{
if (_aggregate != null)
{
_channel.getConnector().getByteBufferPool().release(_aggregate);
_aggregate = null;
}
}
public boolean isClosed()
{
return _closed;
}
@Override
public void flush() throws IOException
{
if (isClosed())
return;
if (BufferUtil.hasContent(_aggregate))
_channel.write(_aggregate, false);
else
_channel.write(BufferUtil.EMPTY_BUFFER, false);
}
public boolean isAllContentWritten()
{
Response response=_channel.getResponse();
return response.isAllContentWritten(_written);
}
public void closeOutput() throws IOException
{
_channel.getResponse().closeOutput();
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
if (isClosed())
throw new EofException("Closed");
_written+=len;
boolean complete=_channel.getResponse().isAllContentWritten(_written);
int capacity = getBufferSize();
// Should we aggregate?
if (!complete && len<=capacity/4)
{
if (_aggregate == null)
_aggregate = _channel.getByteBufferPool().acquire(capacity, false);
// YES - fill the aggregate with content from the buffer
int filled = BufferUtil.fill(_aggregate, b, off, len);
// return if we are not complete, not full and filled all the content
if (!complete && filled == len && !BufferUtil.isFull(_aggregate))
return;
// adjust offset/length
off += filled;
len -= filled;
}
// flush any content from the aggregate
if (BufferUtil.hasContent(_aggregate))
{
_channel.write(_aggregate, complete && len==0);
// should we fill aggregate again from the buffer?
if (len>0 && !complete && len<=_aggregate.capacity()/4)
{
BufferUtil.append(_aggregate, b, off, len);
return;
}
}
// write any remaining content in the buffer directly
if (len>0)
// pass as readonly to avoid space stealing optimisation in HttpConnection
_channel.write(ByteBuffer.wrap(b, off, len).asReadOnlyBuffer(), complete);
else if (complete)
_channel.write(BufferUtil.EMPTY_BUFFER,complete);
if (complete)
closed();
}
@Override
public void write(int b) throws IOException
{
if (isClosed())
throw new EOFException("Closed");
// Allocate an aggregate buffer.
// Never direct as it is slow to do little writes to a direct buffer.
if (_aggregate == null)
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
BufferUtil.append(_aggregate, (byte)b);
_written++;
boolean complete=_channel.getResponse().isAllContentWritten(_written);
// Check if all written or full
if (complete || BufferUtil.isFull(_aggregate))
{
BlockingCallback callback = _channel.getWriteBlockingCallback();
_channel.write(_aggregate, false, callback);
callback.block();
if (complete)
closed();
}
}
@Override
public void print(String s) throws IOException
{
if (isClosed())
throw new IOException("Closed");
write(s.getBytes(_channel.getResponse().getCharacterEncoding()));
}
/* ------------------------------------------------------------ */
/** Set headers and send content.
* @deprecated Use {@link Response#setHeaders(HttpContent)} and {@link #sendContent(HttpContent)} instead.
* @param content
* @throws IOException
*/
@Deprecated
public void sendContent(Object content) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
if (content instanceof HttpContent)
{
_channel.getResponse().setHeaders((HttpContent)content);
sendContent((HttpContent)content,callback);
}
else if (content instanceof Resource)
{
Resource resource = (Resource)content;
_channel.getResponse().getHttpFields().putDateField(HttpHeader.LAST_MODIFIED, resource.lastModified());
ReadableByteChannel in=((Resource)content).getReadableByteChannel();
if (in!=null)
sendContent(in,callback);
else
sendContent(resource.getInputStream(),callback);
}
else if (content instanceof ByteBuffer)
{
sendContent((ByteBuffer)content,callback);
}
else if (content instanceof ReadableByteChannel)
{
sendContent((ReadableByteChannel)content,callback);
}
else if (content instanceof InputStream)
{
sendContent((InputStream)content,callback);
}
else
callback.failed(new IllegalArgumentException("unknown content type "+content.getClass()));
callback.block();
}
/* ------------------------------------------------------------ */
/** Blocking send of content.
* @param content The content to send.
* @throws IOException
*/
public void sendContent(ByteBuffer content) throws IOException
{
final BlockingCallback callback =_channel.getWriteBlockingCallback();
if (content.hasArray()&&content.limit()