Package com.firefly.server.http

Source Code of com.firefly.server.http.HttpDecoder

package com.firefly.server.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;

import com.firefly.net.Decoder;
import com.firefly.net.Session;
import com.firefly.server.io.ByteArrayPipedStream;
import com.firefly.server.io.FilePipedStream;
import com.firefly.utils.StringUtils;
import com.firefly.utils.VerifyUtils;
import com.firefly.utils.log.Log;
import com.firefly.utils.log.LogFactory;

public class HttpDecoder implements Decoder {
  private static Log log = LogFactory.getInstance().getLog("firefly-system");
  private Config config;
  private AbstractHttpDecoder[] httpDecode = {new RequestLineDecoder(), new HeadDecoder(), new BodyDecoder()};
  private static final byte LINE_LIMITOR = '\n';

  public HttpDecoder(Config config) {
    this.config = config;
  }

  @Override
  public void decode(ByteBuffer buf, Session session) throws Throwable {
    ByteBuffer now = getBuffer(buf, session);
    HttpServletRequestImpl req = getHttpServletRequestImpl(session);
    httpDecode[req.status].decode0(now, session, req);
  }

  private ByteBuffer getBuffer(ByteBuffer buf, Session session) {
    SessionAttachment attachment = (SessionAttachment)session.getAttachment();
    ByteBuffer now = buf;
    ByteBuffer prev = attachment.byteBuffer;

    if (prev != null) {
      attachment.byteBuffer = null;
      now = (ByteBuffer) ByteBuffer
          .allocate(prev.remaining() + buf.remaining()).put(prev)
          .put(buf).flip();
    }
    return now;
  }

  private HttpServletRequestImpl getHttpServletRequestImpl(Session session) {
    SessionAttachment attachment = (SessionAttachment)session.getAttachment();
    if (attachment.req == null) {
      attachment.req = new HttpServletRequestImpl(session, config);
    }
    return attachment.req;
  }

  abstract private class AbstractHttpDecoder {
    private void decode0(ByteBuffer now, Session session, HttpServletRequestImpl req) throws Throwable {
      DecodeStatus status = decode(now, session, req);
      switch (status) {
      case INIT:
        init(now.slice(), session, req);
        break;
      case NEXT:
        next(now.slice(), session, req);
        break;
      case BUFFER_UNDERFLOW:
        save(now, session);
        break;
      case ERROR:
        responseError(session, req);
        break;
      case COMPLETE:
        decodeComplete(session, req);
        break;
      default:
        break;
      }
    }
   
    private void init(ByteBuffer buf, Session session, HttpServletRequestImpl req) throws Throwable {
      decodeComplete(session, req);
      HttpServletRequestImpl newReq = getHttpServletRequestImpl(session);
      httpDecode[newReq.status].decode0(buf, session, newReq);
    }
   
    private void next(ByteBuffer buf, Session session, HttpServletRequestImpl req) throws Throwable {
      req.status++;
      if (req.status < httpDecode.length) {
        req.offset = 0;
        httpDecode[req.status].decode0(buf, session, req);
      }
    }

    private void save(ByteBuffer buf, Session session) {
      SessionAttachment attachment = (SessionAttachment)session.getAttachment();
      if (buf.hasRemaining())
        attachment.byteBuffer = buf;
    }

    private void responseError(Session session, HttpServletRequestImpl req) {
      finish(session, req);
      req.response.scheduleSendError();
      req.decodeFinish();
    }

    private void decodeComplete(Session session, HttpServletRequestImpl req) {
      finish(session, req);
      req.decodeFinish();
    }
   
    private void finish(Session session, HttpServletRequestImpl req) {
      SessionAttachment attachment = (SessionAttachment)session.getAttachment();
      attachment.req = null;
      attachment.byteBuffer = null;
      req.status = httpDecode.length;
    }

    abstract protected DecodeStatus decode(ByteBuffer buf, Session session, HttpServletRequestImpl req) throws Throwable;
  }
 
  private enum DecodeStatus {
    NEXT, BUFFER_UNDERFLOW, INIT, COMPLETE, ERROR
  }

  private class RequestLineDecoder extends AbstractHttpDecoder {

    @Override
    public DecodeStatus decode(ByteBuffer buf, Session session, HttpServletRequestImpl req) throws Throwable {

      if (req.offset >= config.getMaxRequestLineLength()) {
        String msg = "request line length is " + req.offset
            + ", it more than " + config.getMaxRequestLineLength()
            + "|" + session.getRemoteAddress();
        log.error(msg);
        req.response.setStatus(414);
        req.response.systemResponseContent = msg;
        return DecodeStatus.ERROR;
      }

      int len = buf.remaining();
      for (; req.offset < len; req.offset++) {
        if (buf.get(req.offset) == LINE_LIMITOR) {
          byte[] data = new byte[req.offset + 1];
          buf.get(data);
          String requestLine = new String(data, config.getEncoding()).trim();
          if (VerifyUtils.isEmpty(requestLine)) {
            String msg = "request line length is 0|"
                + session.getRemoteAddress();
            log.error(msg);
            req.response.setStatus(400);
            req.response.systemResponseContent = msg;
            return DecodeStatus.ERROR;
          }

          String[] reqLine = StringUtils.split(requestLine, ' ');
          if (reqLine.length != 3) {
            String msg = "request line format error: "
                + requestLine + "|"
                + session.getRemoteAddress();
            log.error(msg);
            req.response.setStatus(400);
            req.response.systemResponseContent = msg;
            return DecodeStatus.ERROR;
          }

          int s = reqLine[1].indexOf('?');
          req.method = reqLine[0].toUpperCase();
          if (s > 0) {
            req.requestURI = reqLine[1].substring(0, s);
            req.queryString = reqLine[1].substring(s + 1, reqLine[1].length());
          } else {
            req.requestURI = reqLine[1];
          }
          req.protocol = reqLine[2];
          return DecodeStatus.NEXT;
        }
      }
      return DecodeStatus.BUFFER_UNDERFLOW;
    }

  }

  private class HeadDecoder extends AbstractHttpDecoder {

    @Override
    public DecodeStatus decode(ByteBuffer buf, Session session, HttpServletRequestImpl req) throws Throwable {
      int len = buf.remaining();

      for (int i = req.offset, p = 0; i < len; i++) {
        if (buf.get(i) == LINE_LIMITOR) {
          int parseLen = i - p + 1;
          req.headLength += parseLen;

          if (req.headLength >= config.getMaxRequestHeadLength()) {
            String msg = "request head length is " + req.headLength
                + ", it more than "
                + config.getMaxRequestHeadLength() + "|"
                + session.getRemoteAddress() + "|"
                + req.getRequestURI();
            log.error(msg);
            req.response.setStatus(400);
            req.response.systemResponseContent = msg;
            return DecodeStatus.ERROR;
          }

          byte[] data = new byte[parseLen];
          buf.get(data);
          String headLine = new String(data, config.getEncoding()).trim();
          p = i + 1;

          if (VerifyUtils.isEmpty(headLine)) { // http head decode finish
            if(Monitor.CONN_COUNT.get() > config.getMaxConnections()) {
              String msg = "connections number has been more than " + config.getMaxConnections();
              log.error(msg);
              req.response.setHeader("Retry-After", "60");
              req.response.setStatus(503);
              req.response.systemResponseContent = msg;
              return DecodeStatus.ERROR;
            }
           
            if (req.getMethod().equals("POST") || req.getMethod().equals("PUT")) {
              return DecodeStatus.NEXT;
            }
           
            return buf.hasRemaining() ? DecodeStatus.INIT : DecodeStatus.COMPLETE;
          }
         
          int h = headLine.indexOf(':');
          if (h <= 0) {
            String msg = "head line format error: " + headLine
                + "|" + session.getRemoteAddress() + "|"
                + req.getRequestURI();
            log.error(msg);
            req.response.setStatus(400);
            req.response.systemResponseContent = msg;
            return DecodeStatus.ERROR;
          }

          String name = headLine.substring(0, h).toLowerCase().trim();
          String value = headLine.substring(h + 1).trim();
          req.headMap.put(name, value);
          req.offset = len - i - 1;

          if (name.equals("expect") && value.startsWith("100-") && req.getProtocol().equals("HTTP/1.1"))
            response100Continue(session);
        }
      }
      return DecodeStatus.BUFFER_UNDERFLOW;
    }

    private void response100Continue(Session session) throws UnsupportedEncodingException {
      session.encode(ByteBuffer.wrap("HTTP/1.1 100 Continue\r\n\r\n".getBytes(config.getEncoding())));
    }
  }

  private class BodyDecoder extends AbstractHttpDecoder {

    @Override
    public DecodeStatus decode(ByteBuffer buf, Session session,
        HttpServletRequestImpl req) throws Throwable {
      int contentLength = req.getContentLength();
      if (contentLength > 0) {
        if (contentLength > config.getMaxUploadLength()) {
          String msg = "body length is " + contentLength
              + " , it more than " + config.getMaxUploadLength()
              + "|" + session.getRemoteAddress() + "|"
              + req.getRequestURI();
          log.error(msg);
          req.response.setStatus(400);
          req.response.systemResponseContent = msg;
          return DecodeStatus.ERROR;
        }

        if (req.bodyPipedStream == null) { // the first into body decode status, it neet create piped stream
          if(contentLength >= config.getHttpBodyThreshold()) {
            log.info("content length [{}] more than threshold [{}]", contentLength , config.getHttpBodyThreshold());
            req.bodyPipedStream = new FilePipedStream(config.getTempdir());
          } else {
            req.bodyPipedStream = new ByteArrayPipedStream(contentLength);
          }
        }
       
        req.offset += buf.remaining();
        byte[] data = new byte[buf.remaining()];
        buf.get(data);
        try {
          req.bodyPipedStream.getOutputStream().write(data);
        } catch(IOException e) {
          log.error("receive body data error", e);
          req.bodyPipedStream.getOutputStream().close();
        }

        if (req.offset >= contentLength) {
          req.bodyPipedStream.getOutputStream().close();
          return buf.hasRemaining() ? DecodeStatus.INIT : DecodeStatus.COMPLETE;
        }
      } else {
        return buf.hasRemaining() ? DecodeStatus.INIT : DecodeStatus.COMPLETE;
      }
      return DecodeStatus.BUFFER_UNDERFLOW;
    }

  }

}
TOP

Related Classes of com.firefly.server.http.HttpDecoder

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.