All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.barchart.http.server.PooledServerResponse Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
/**
 * 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