com.barchart.http.server.PooledServerResponse Maven / Gradle / Ivy
/**
* Copyright (C) 2011-2013 Barchart, Inc.
*
* All rights reserved. Licensed under the OSI BSD License.
*
* http://www.opensource.org/licenses/bsd-license.php
*/
package com.barchart.http.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.util.CharsetUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.barchart.http.request.RequestHandler;
import com.barchart.http.request.ServerResponse;
/**
* Not thread safe.
*/
public class PooledServerResponse extends DefaultFullHttpResponse implements
ServerResponse {
private static final Logger log = LoggerFactory
.getLogger(PooledServerResponse.class);
final ServerMessagePool pool;
private final Collection cookies = new HashSet();
private HttpRequestChannelHandler channelHandler;
private ChannelHandlerContext context;
private RequestHandler handler;
private PooledServerRequest request;
private OutputStream out;
private Writer writer;
private Charset charSet = CharsetUtil.UTF_8;
private boolean suspended = false;
private boolean started = false;
private boolean finished = false;
private long requestTime = 0;
private RequestLogger logger;
public PooledServerResponse(final ServerMessagePool pool_) {
super(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
pool = pool_;
}
void init(final ChannelHandlerContext context_,
final HttpRequestChannelHandler channelHandler_,
final RequestHandler handler_, final PooledServerRequest request_,
final RequestLogger logger_) {
// Clear outbound ByteBuf
data().clear();
// Reference count increment so underlying ByteBuf is not collected
// between requests
retain();
context = context_;
channelHandler = channelHandler_;
handler = handler_;
request = request_;
logger = logger_;
charSet = CharsetUtil.UTF_8;
finished = false;
suspended = false;
started = false;
out = new ByteBufOutputStream(data());
writer = new OutputStreamWriter(out, charSet);
requestTime = System.currentTimeMillis();
}
@Override
public OutputStream getOutputStream() {
return out;
}
@Override
public Writer getWriter() {
return writer;
}
@Override
public void setCookie(final Cookie cookie) {
cookies.add(cookie);
}
@Override
public void setCookie(final String name, final String value) {
cookies.add(new DefaultCookie(name, value));
}
@Override
public void sendRedirect(final String location) {
headers().set(HttpHeaders.Names.LOCATION, location);
}
@Override
public PooledServerResponse setProtocolVersion(final HttpVersion version) {
super.setProtocolVersion(version);
return this;
}
@Override
public PooledServerResponse setStatus(final HttpResponseStatus status) {
super.setStatus(status);
return this;
}
@Override
public void setCharacterEncoding(final String charSet_) {
charSet = Charset.forName(charSet_);
writer = new OutputStreamWriter(out, charSet);
}
@Override
public Charset getCharacterEncoding() {
return charSet;
}
@Override
public void setContentLength(final int length) {
HttpHeaders.setContentLength(this, length);
}
@Override
public void setContentType(final String mimeType) {
headers().set(HttpHeaders.Names.CONTENT_TYPE, mimeType);
}
@Override
public boolean isChunkedEncoding() {
return HttpHeaders.isTransferEncodingChunked(this);
}
@Override
public void setChunkedEncoding(final boolean chunked) {
if (chunked != isChunkedEncoding()) {
if (chunked) {
HttpHeaders.setTransferEncodingChunked(this);
out = new HttpChunkOutputStream(context);
writer = new OutputStreamWriter(out, charSet);
} else {
HttpHeaders.removeTransferEncodingChunked(this);
out = new ByteBufOutputStream(data());
writer = new OutputStreamWriter(out, charSet);
}
}
}
@Override
public void write(final String data) throws IOException {
write(data.getBytes());
}
@Override
public void write(final byte[] data) throws IOException {
checkFinished();
out.write(data);
out.flush();
}
@Override
public void write(final byte[] data, final int offset, final int length)
throws IOException {
checkFinished();
out.write(data, offset, length);
out.flush();
}
@Override
public long writtenBytes() {
if (out instanceof ByteBufOutputStream) {
return ((ByteBufOutputStream) out).writtenBytes();
} else if (out instanceof HttpChunkOutputStream) {
return ((HttpChunkOutputStream) out).writtenBytes();
}
return 0;
}
@Override
public void suspend() {
checkFinished();
suspended = true;
}
@Override
public boolean isSuspended() {
return suspended;
}
private ChannelFuture startResponse() {
checkFinished();
if (started) {
throw new IllegalStateException("Response already started");
}
// Set headers
headers().set(HttpHeaders.Names.SET_COOKIE,
ServerCookieEncoder.encode(cookies));
if (!isChunkedEncoding()) {
setContentLength(data().readableBytes());
}
if (HttpHeaders.isKeepAlive(request)) {
headers().set(HttpHeaders.Names.CONNECTION,
HttpHeaders.Values.KEEP_ALIVE);
}
started = true;
return context.write(this);
}
@Override
public ChannelFuture finish() throws IOException {
checkFinished();
ChannelFuture writeFuture = null;
// Handlers might call finish() on a cancelled/closed
// channel, don't cause unnecessary pipeline exceptions
if (context.channel().isOpen()) {
if (isChunkedEncoding()) {
if (!started) {
log.debug("Warning, empty response");
startResponse();
}
writeFuture = context.write(LastHttpContent.EMPTY_LAST_CONTENT);
} else {
writeFuture = startResponse();
}
}
close();
if (writeFuture != null && !HttpHeaders.isKeepAlive(request)) {
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
// Record to access log
logger.access(request, this, System.currentTimeMillis() - requestTime);
// Keep alive, need to tell channel handler it can return us to the pool
if (HttpHeaders.isKeepAlive(request)) {
channelHandler.freeHandlers(context);
}
return writeFuture;
}
private void checkFinished() {
if (finished) {
throw new IllegalStateException(
"ServerResponse has already finished");
}
}
@Override
public boolean isFinished() {
return finished;
}
@Override
public void flush() throws IOException {
writer.flush();
out.flush();
}
/**
* Closes this request to future interaction.
*/
void close() {
// Mark finished before resetting suspended to avoid synchronization
// issue with channel handler auto-finish logic
finished = true;
suspended = false;
}
PooledServerRequest request() {
return request;
}
RequestHandler handler() {
return handler;
}
/**
* Writes messages as HttpChunk objects to the client.
*/
private class HttpChunkOutputStream extends OutputStream {
private final ByteBuf content = Unpooled.buffer();
private final ChannelHandlerContext context;
private long writtenBytes = 0;
HttpChunkOutputStream(final ChannelHandlerContext context_) {
context = context_;
}
/**
* Adds a single byte to the output buffer.
*/
@Override
public void write(final int b) throws IOException {
content.writeByte(b);
writtenBytes++;
}
public long writtenBytes() {
return writtenBytes;
}
@Override
public void flush() {
if (!started) {
startResponse();
}
final HttpContent chunk = new DefaultHttpContent(content);
context.write(chunk);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy