Package org.deftserver.web.http

Source Code of org.deftserver.web.http.HttpResponse

package org.deftserver.web.http;

import static org.deftserver.web.http.HttpServerDescriptor.WRITE_BUFFER_SIZE;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;

import org.deftserver.io.IOLoop;
import org.deftserver.io.buffer.DynamicByteBuffer;
import org.deftserver.util.Closeables;
import org.deftserver.util.DateUtil;
import org.deftserver.util.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Charsets;

public class HttpResponse {
 
  private final static Logger logger = LoggerFactory.getLogger(HttpResponse.class);
 
  private final HttpProtocol protocol;
  private final SelectionKey key;
 
  private int statusCode = 200// default response status code
 
  private final Map<String, String> headers = new HashMap<String, String>();
  private boolean headersCreated = false;
  private DynamicByteBuffer responseData = DynamicByteBuffer.allocate(WRITE_BUFFER_SIZE);
 
  public HttpResponse(HttpProtocol protocol, SelectionKey key, boolean keepAlive) {
    this.protocol = protocol;
    this.key = key;
    headers.put("Server", "Deft/0.3.0");
    headers.put("Date", DateUtil.getCurrentAsString());
    headers.put("Connection", keepAlive ? "Keep-Alive" : "Close");
  }
 
  public void setStatusCode(int sc) {
    statusCode = sc;
  }
 
  public void setHeader(String header, String value) {
    headers.put(header, value);
  }

  /**
   * The given data data will be sent as the HTTP response upon next flush or when the response is finished.
   *
   * @return this for chaining purposes.
   */
  public HttpResponse write(String data) {
    byte[] bytes = data.getBytes(Charsets.UTF_8);
    responseData.put(bytes);
    return this;
  }

  /**
   * Explicit flush.
   *
   * @return the number of bytes that were actually written as the result of this flush.
   */
  public long flush() {
    if (!headersCreated) {
      String initial = createInitalLineAndHeaders();     
      responseData.prepend(initial);
      headersCreated = true;
    }

    SocketChannel channel = (SocketChannel) key.channel();
    responseData.flip()// prepare for write
    try {
      channel.write(responseData.getByteBuffer());
    } catch (IOException e) {
      logger.error("ClosedChannelException during channel.write(): {}", e.getMessage());
      Closeables.closeQuietly(key.channel());
    }
    long bytesFlushed = responseData.position();
    if (IOLoop.INSTANCE.hasKeepAliveTimeout(channel)) {
      protocol.prolongKeepAliveTimeout(channel);
    }
    if (responseData.hasRemaining()) {
      responseData.compact()// make room for more data be "read" in
      try {
        key.channel().register(key.selector(), SelectionKey.OP_WRITE);
      } catch (ClosedChannelException e) {
        logger.error("ClosedChannelException during flush(): {}", e.getMessage());
        Closeables.closeQuietly(key.channel());
      }
      key.attach(responseData);
    } else {
      responseData.clear();
    }
    return bytesFlushed;
  }
 
  /**
   * Should only be invoked by third party asynchronous request handlers
   * (or by the Deft framework for synchronous request handlers).
   * If no previous (explicit) flush is invoked, the "Content-Length" and "Etag" header will be calculated and
   * inserted to the HTTP response.
   *
   */
  public long finish() {
    long bytesWritten = 0;
    SocketChannel clientChannel = (SocketChannel) key.channel();
    if (clientChannel.isOpen()) {
      if (!headersCreated) {
        setEtagAndContentLength();
      }
      bytesWritten = flush();
    }
   
    // close (or register for read) iff
    // (a) DBB is attached but all data is sent to wire (hasRemaining == false)
    // (b) no DBB is attached (never had to register for write)
    if (key.attachment() instanceof DynamicByteBuffer) {
      DynamicByteBuffer dbb = (DynamicByteBuffer) key.attachment();
      if (!(dbb).hasRemaining()) {
        protocol.closeOrRegisterForRead(key);
      }
    } else {
      protocol.closeOrRegisterForRead(key);
    }
    return bytesWritten;
  }
 
  private void setEtagAndContentLength() {
    if (responseData.position() > 0) {
      setHeader("Etag", HttpUtil.getEtag(responseData.array()));
    }
    setHeader("Content-Length", String.valueOf(responseData.position()));
  }
 
  private String createInitalLineAndHeaders() {
    StringBuilder sb = new StringBuilder(HttpUtil.createInitialLine(statusCode));
    for (Map.Entry<String, String> header : headers.entrySet()) {
      sb.append(header.getKey());
      sb.append(": ");
      sb.append(header.getValue());
      sb.append("\r\n");
    }
   
    sb.append("\r\n");
    return sb.toString();
  }
 
  /**
   * Experimental support.
   * Before use, read https://github.com/rschildmeijer/deft/issues/75
   */
  public long write(File file) {
    //setHeader("Etag", HttpUtil.getEtag(file));
    setHeader("Content-Length", String.valueOf(file.length()));
    long bytesWritten = 0;
    flush(); // write initial line + headers
    try {
      bytesWritten = new RandomAccessFile(file, "r").getChannel().transferTo(0, file.length(), (SocketChannel) key.channel());
      logger.debug("sent file, bytes sent: {}", bytesWritten);
    } catch (IOException e) {
      logger.error("Error writing (static file) response: {}", e.getMessage());
    }
    return bytesWritten;
  }
 
}
TOP

Related Classes of org.deftserver.web.http.HttpResponse

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.