Package com.barchart.http.server

Source Code of com.barchart.http.server.PooledServerResponse$HttpChunkOutputStream

/**
* Copyright (C) 2011-2013 Barchart, Inc. <http://www.barchart.com/>
*
* 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.logging.RequestLogger;
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<Cookie> cookies = new HashSet<Cookie>();

  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_) {

    // Reset default request values if this is a recycled handler
    if (finished) {
      headers().clear();
      data().clear();
      setStatus(HttpResponseStatus.OK);
    }

    // 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);

      // FIXME Why is this closing the connection in netty 4.0.0.Beta3?
      context.write(chunk);

    }

  }

}
TOP

Related Classes of com.barchart.http.server.PooledServerResponse$HttpChunkOutputStream

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.