private void init() {
    if (isInitialized) return;

    synchronized (this) {
      if (isInitialized) return;

      try {
        group =
            AsynchronousChannelGroup.withThreadPool(
                new ThreadPoolExecutor(
                    config.getAsynchronousCorePoolSize(),
                    config.getAsynchronousMaximumPoolSize(),
                    config.getAsynchronousPoolKeepAliveTime(),
                    TimeUnit.MILLISECONDS,
                    new LinkedTransferQueue<Runnable>(),
                    new ThreadFactory() {

                      @Override
                      public Thread newThread(Runnable r) {
                        return new Thread(r, "firefly asynchronous server thread");
                      }
                    }));
        log.info(
            "create asychronous I/O thread pool. core pool size: {}, max pool size: {}, pool keep alive time: {}ms",
            config.getAsynchronousCorePoolSize(),
            config.getAsynchronousMaximumPoolSize(),
            config.getAsynchronousPoolKeepAliveTime());
        EventManager eventManager = new DefaultEventManager(config);
        worker = new AsynchronousTcpWorker(config, eventManager);
      } catch (IOException e) {
        log.error("initialization server channel group error", e);
      }
      isInitialized = true;
    }
  }
Exemplo n.º 2
0
 @Override
 protected BeanDefinition getBeanDefinition(Class<?> c) {
   if (c.isAnnotationPresent(Controller.class) || c.isAnnotationPresent(Component.class)) {
     log.info("classes [{}]", c.getName());
     return componentParser(c);
   } else if (c.isAnnotationPresent(Interceptor.class)) {
     log.info("classes [{}]", c.getName());
     return interceptorParser(c);
   } else return null;
 }
Exemplo n.º 3
0
  private void loadParam() {
    if (!loadParam) {
      try {
        loadParam(queryString);

        String contentType = getContentType();
        if (method.equals("POST")
            && contentType != null
            && contentType.startsWith("application/x-www-form-urlencoded")) {
          int contentLength = getContentLength();
          byte[] data = new byte[contentLength];
          byte[] buf = new byte[1024];
          ServletInputStream input = getInputStream();
          try {
            int readBytes = 0;
            for (int len = 0; (len = input.read(buf)) != -1; ) {
              System.arraycopy(buf, 0, data, readBytes, len);
              readBytes += len;
              if (readBytes >= contentLength) break;
            }
            loadParam(new String(data, characterEncoding));
          } finally {
            input.close();
          }
        }
      } catch (Throwable t) {
        log.error("load param error", t);
      }
      loadParam = true;
    }
  }
 @Override
 public void start(String host, int port) {
   if (config == null) throw new NetException("server config is null");
   init();
   listen(bind(host, port));
   log.info("server start. host: {}, port: {}", host, port);
 }
Exemplo n.º 5
0
 private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) {
   super(400, String.format("Illegal character 0x%X", ch));
   // Bug #460642 - don't reveal buffers to end user
   log.warn(
       String.format(
           "Illegal character 0x%X in state=%s for buffer %s",
           ch, state, BufferUtils.toDetailString(buffer)));
 }
 private AsynchronousServerSocketChannel bind(String host, int port) {
   AsynchronousServerSocketChannel serverSocketChannel = null;
   try {
     serverSocketChannel = AsynchronousServerSocketChannel.open(group);
     serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
     serverSocketChannel.bind(new InetSocketAddress(host, port), BACKLOG);
   } catch (Exception e) {
     log.error("ServerSocket bind error", e);
   }
   return serverSocketChannel;
 }
Exemplo n.º 7
0
 public static String systemPageTemplate(int status, String content) {
   StringBuilder ret = new StringBuilder();
   try {
     ret.append("<!DOCTYPE html><html><head><title>firefly</title></head><body><h2>HTTP ERROR ")
         .append(status)
         .append("</h2><div>")
         .append(StringUtils.escapeXML(URLDecoder.decode(content, "UTF-8")))
         .append("</div><hr/><i><small>firefly framework</small></i></body></html>");
   } catch (UnsupportedEncodingException e) {
     log.error("url decode error", e);
   }
   return ret.toString();
 }
Exemplo n.º 8
0
 /** if http method is POST or PUT, when the business process finish, it must close piped stream */
 void releaseInputStreamData() {
   try {
     if (getContentLength() > 0) {
       if (bodyPipedStream != null) {
         bodyPipedStream.close();
       }
       if (multipartFormData != null) {
         multipartFormData.close();
       }
     }
   } catch (Throwable t) {
     log.error("release input stream error", t);
   }
 }
Exemplo n.º 9
0
  public void reset() {
    if (DEBUG) log.debug("reset {}", this);

    // reset state
    if (_state == State.CLOSE || _state == State.CLOSED) return;

    setState(State.START);
    _endOfContent = EndOfContent.UNKNOWN_CONTENT;
    _contentLength = -1;
    _contentPosition = 0;
    _responseStatus = 0;
    _contentChunk = null;
    _headerBytes = 0;
    _host = false;
  }
Exemplo n.º 10
0
 public static void responseSystemPage(
     HttpServletRequest request,
     HttpServletResponse response,
     String charset,
     int status,
     String content) {
   response.setStatus(status);
   response.setCharacterEncoding(charset);
   response.setHeader("Content-Type", "text/html; charset=" + charset);
   PrintWriter writer = null;
   try {
     try {
       writer = response.getWriter();
     } catch (Throwable t) {
       log.error("responseSystemPage error", t);
     }
     writer.print(systemPageTemplate(status, content));
   } finally {
     if (writer != null) writer.close();
   }
 }
Exemplo n.º 11
0
  private void setWebBeanDefinition(AnnotationBeanDefinition beanDefinition, Class<?> c) {
    beanDefinition.setClassName(c.getName());

    String id = getId(c);
    beanDefinition.setId(id);

    String[] names = ReflectUtils.getInterfaceNames(c);
    beanDefinition.setInterfaceNames(names);

    List<Field> fields = getInjectField(c);
    beanDefinition.setInjectFields(fields);

    List<Method> methods = getInjectMethod(c);
    beanDefinition.setInjectMethods(methods);

    try {
      Object object = c.newInstance();
      beanDefinition.setObject(object);
    } catch (Throwable t) {
      log.error("set web bean error", t);
    }
  }
Exemplo n.º 12
0
  /*
   * Quick lookahead for the start state looking for a request method or a
   * HTTP version, otherwise skip white space until something else to parse.
   */
  private boolean quickStart(ByteBuffer buffer) {
    if (_requestHandler != null) {
      _method = HttpMethod.lookAheadGet(buffer);
      if (_method != null) {
        _methodString = _method.asString();
        buffer.position(buffer.position() + _methodString.length() + 1);

        setState(State.SPACE1);
        return false;
      }
    } else if (_responseHandler != null) {
      _version = HttpVersion.lookAheadGet(buffer);
      if (_version != null) {
        buffer.position(buffer.position() + _version.asString().length() + 1);
        setState(State.SPACE1);
        return false;
      }
    }

    // Quick start look
    while (_state == State.START && buffer.hasRemaining()) {
      int ch = next(buffer);

      if (ch > SPACE) {
        _string.setLength(0);
        _string.append((char) ch);
        setState(_requestHandler != null ? State.METHOD : State.RESPONSE_VERSION);
        return false;
      } else if (ch == 0) break;
      else if (ch < 0) throw new BadMessageException();

      // count this white space as a header byte to avoid DOS
      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        log.warn("padding is too large > {}", _maxHeaderBytes);
        throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
      }
    }
    return false;
  }
 @Override
 public void close() throws IOException {
   try {
     flush();
     if (!keepAlive) session.close();
   } finally {
     if (!hasSavedAccessLog) {
       access.info(
           "{}|{}|{}|{}|{}|{}|{}|{}|{}|{}",
           session.getSessionId(),
           Monitor.CONN_COUNT.get(),
           request.getHeader("X-Forwarded-For"),
           request.getRemoteAddr(),
           response.getStatus(),
           request.getProtocol(),
           request.getMethod(),
           request.getRequestURI(),
           request.getQueryString(),
           request.getTimeDifference());
       hasSavedAccessLog = true;
     }
   }
 }
Exemplo n.º 14
0
  /*
   * Parse a request or response line
   */
  private boolean parseLine(ByteBuffer buffer) {
    boolean handle = false;

    // Process headers
    while (_state.ordinal() < State.HEADER.ordinal() && buffer.hasRemaining() && !handle) {
      // process each character
      byte ch = next(buffer);
      if (ch == 0) break;

      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        if (_state == State.URI) {
          log.warn("URI is too large > {}", _maxHeaderBytes);
          throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
        } else {
          if (_requestHandler != null) log.warn("request is too large > {}", _maxHeaderBytes);
          else log.warn("response is too large > {}", _maxHeaderBytes);
          throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
        }
      }

      switch (_state) {
        case METHOD:
          if (ch == SPACE) {
            _length = _string.length();
            _methodString = takeString();
            HttpMethod method = HttpMethod.CACHE.get(_methodString);
            if (method != null && !_strict) _methodString = method.asString();
            setState(State.SPACE1);
          } else if (ch < SPACE) {
            if (ch == LINE_FEED) throw new BadMessageException("No URI");
            else throw new IllegalCharacterException(_state, ch, buffer);
          } else _string.append((char) ch);
          break;

        case RESPONSE_VERSION:
          if (ch == HttpTokens.SPACE) {
            _length = _string.length();
            String version = takeString();
            _version = HttpVersion.CACHE.get(version);
            if (_version == null)
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version");
            setState(State.SPACE1);
          } else if (ch < HttpTokens.SPACE) throw new IllegalCharacterException(_state, ch, buffer);
          else _string.append((char) ch);
          break;

        case SPACE1:
          if (ch > HttpTokens.SPACE || ch < 0) {
            if (_responseHandler != null) {
              setState(State.STATUS);
              setResponseStatus(ch - '0');
            } else {
              _uri.reset();
              setState(State.URI);
              // quick scan for space or EoBuffer
              if (buffer.hasArray()) {
                byte[] array = buffer.array();
                int p = buffer.arrayOffset() + buffer.position();
                int l = buffer.arrayOffset() + buffer.limit();
                int i = p;
                while (i < l && array[i] > HttpTokens.SPACE) i++;

                int len = i - p;
                _headerBytes += len;

                if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
                  log.warn("URI is too large > {}", _maxHeaderBytes);
                  throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
                }
                _uri.append(array, p - 1, len + 1);
                buffer.position(i - buffer.arrayOffset());
              } else _uri.append(ch);
            }
          } else if (ch < HttpTokens.SPACE) {
            throw new BadMessageException(
                HttpStatus.BAD_REQUEST_400, _requestHandler != null ? "No URI" : "No Status");
          }
          break;

        case STATUS:
          if (ch == HttpTokens.SPACE) {
            setState(State.SPACE2);
          } else if (ch >= '0' && ch <= '9') {
            _responseStatus = _responseStatus * 10 + (ch - '0');
          } else if (ch < HttpTokens.SPACE && ch >= 0) {
            handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle;
            setState(State.HEADER);
          } else {
            throw new BadMessageException();
          }
          break;

        case URI:
          if (ch == HttpTokens.SPACE) {
            setState(State.SPACE2);
          } else if (ch < HttpTokens.SPACE && ch >= 0) {
            // HTTP/0.9
            throw new BadMessageException("HTTP/0.9 not supported");
          } else {
            _uri.append(ch);
          }
          break;

        case SPACE2:
          if (ch > HttpTokens.SPACE) {
            _string.setLength(0);
            _string.append((char) ch);
            if (_responseHandler != null) {
              _length = 1;
              setState(State.REASON);
            } else {
              setState(State.REQUEST_VERSION);

              // try quick look ahead for HTTP Version
              HttpVersion version;
              if (buffer.position() > 0 && buffer.hasArray())
                version =
                    HttpVersion.lookAheadGet(
                        buffer.array(),
                        buffer.arrayOffset() + buffer.position() - 1,
                        buffer.arrayOffset() + buffer.limit());
              else version = HttpVersion.CACHE.getBest(buffer, 0, buffer.remaining());

              if (version != null) {
                int pos = buffer.position() + version.asString().length() - 1;
                if (pos < buffer.limit()) {
                  byte n = buffer.get(pos);
                  if (n == HttpTokens.CARRIAGE_RETURN) {
                    _cr = true;
                    _version = version;
                    _string.setLength(0);
                    buffer.position(pos + 1);
                  } else if (n == HttpTokens.LINE_FEED) {
                    _version = version;
                    _string.setLength(0);
                    buffer.position(pos);
                  }
                }
              }
            }
          } else if (ch == HttpTokens.LINE_FEED) {
            if (_responseHandler != null) {
              handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle;
              setState(State.HEADER);
            } else {
              // HTTP/0.9
              throw new BadMessageException("HTTP/0.9 not supported");
            }
          } else if (ch < 0) throw new BadMessageException();
          break;

        case REQUEST_VERSION:
          if (ch == HttpTokens.LINE_FEED) {
            if (_version == null) {
              _length = _string.length();
              _version = HttpVersion.CACHE.get(takeString());
            }
            if (_version == null)
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version");

            // Should we try to cache header fields?
            if (_connectionFields == null
                && _version.getVersion() >= HttpVersion.HTTP_1_1.getVersion()
                && _handler.getHeaderCacheSize() > 0) {
              int header_cache = _handler.getHeaderCacheSize();
              _connectionFields = new ArrayTernaryTrie<>(header_cache);
            }

            setState(State.HEADER);

            handle =
                _requestHandler.startRequest(_methodString, _uri.toString(), _version) || handle;
            continue;
          } else if (ch >= HttpTokens.SPACE) _string.append((char) ch);
          else throw new BadMessageException();

          break;

        case REASON:
          if (ch == HttpTokens.LINE_FEED) {
            String reason = takeString();

            setState(State.HEADER);
            handle = _responseHandler.startResponse(_version, _responseStatus, reason) || handle;
            continue;
          } else if (ch >= HttpTokens.SPACE) {
            _string.append((char) ch);
            if (ch != ' ' && ch != '\t') _length = _string.length();
          } else throw new BadMessageException();
          break;

        default:
          throw new IllegalStateException(_state.toString());
      }
    }

    return handle;
  }
Exemplo n.º 15
0
  /**
   * Parse until next Event.
   *
   * @param buffer the buffer to parse
   * @return True if an {@link RequestHandler} method was called and it returned true;
   */
  public boolean parseNext(ByteBuffer buffer) {
    if (DEBUG) log.debug("parseNext s={} {}", _state, BufferUtils.toDetailString(buffer));
    try {
      // Start a request/response
      if (_state == State.START) {
        _version = null;
        _method = null;
        _methodString = null;
        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
        _header = null;
        if (quickStart(buffer)) return true;
      }

      // Request/response line
      if (_state.ordinal() >= State.START.ordinal() && _state.ordinal() < State.HEADER.ordinal()) {
        if (parseLine(buffer)) return true;
      }

      // parse headers
      if (_state.ordinal() >= State.HEADER.ordinal()
          && _state.ordinal() < State.CONTENT.ordinal()) {
        if (parseHeaders(buffer)) return true;
      }

      // parse content
      if (_state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal()) {
        // Handle HEAD response
        if (_responseStatus > 0 && _headResponse) {
          setState(State.END);
          return _handler.messageComplete();
        } else {
          if (parseContent(buffer)) return true;
        }
      }

      // handle end states
      if (_state == State.END) {
        // eat white space
        while (buffer.remaining() > 0 && buffer.get(buffer.position()) <= HttpTokens.SPACE)
          buffer.get();
      } else if (_state == State.CLOSE) {
        // Seeking EOF
        if (BufferUtils.hasContent(buffer)) {
          // Just ignore data when closed
          _headerBytes += buffer.remaining();
          BufferUtils.clear(buffer);
          if (_maxHeaderBytes > 0 && _headerBytes > _maxHeaderBytes) {
            // Don't want to waste time reading data of a closed
            // request
            throw new IllegalStateException("too much data seeking EOF");
          }
        }
      } else if (_state == State.CLOSED) {
        BufferUtils.clear(buffer);
      }

      // Handle EOF
      if (_eof && !buffer.hasRemaining()) {
        switch (_state) {
          case CLOSED:
            break;

          case START:
            setState(State.CLOSED);
            _handler.earlyEOF();
            break;

          case END:
          case CLOSE:
            setState(State.CLOSED);
            break;

          case EOF_CONTENT:
            setState(State.CLOSED);
            return _handler.messageComplete();

          case CONTENT:
          case CHUNKED_CONTENT:
          case CHUNK_SIZE:
          case CHUNK_PARAMS:
          case CHUNK:
            setState(State.CLOSED);
            _handler.earlyEOF();
            break;

          default:
            if (DEBUG) log.debug("{} EOF in {}", this, _state);
            setState(State.CLOSED);
            _handler.badMessage(400, null);
            break;
        }
      }
    } catch (BadMessageException e) {
      BufferUtils.clear(buffer);

      Throwable cause = e.getCause();
      boolean stack =
          log.isDebugEnable()
              || (!(cause instanceof NumberFormatException)
                  && (cause instanceof RuntimeException || cause instanceof Error));

      if (stack)
        log.warn(
            "bad HTTP parsed: "
                + e._code
                + (e.getReason() != null ? " " + e.getReason() : "")
                + " for "
                + _handler,
            e);
      else
        log.warn(
            "bad HTTP parsed: "
                + e._code
                + (e.getReason() != null ? " " + e.getReason() : "")
                + " for "
                + _handler);
      setState(State.CLOSE);
      _handler.badMessage(e.getCode(), e.getReason());
    } catch (NumberFormatException | IllegalStateException e) {
      BufferUtils.clear(buffer);
      log.warn("parse exception: {} in {} for {}", e.toString(), _state, _handler);
      if (DEBUG) log.debug("parse exception", e);

      switch (_state) {
        case CLOSED:
          break;
        case CLOSE:
          _handler.earlyEOF();
          break;
        default:
          setState(State.CLOSE);
          _handler.badMessage(400, null);
      }
    } catch (Exception | Error e) {
      BufferUtils.clear(buffer);

      log.warn("parse exception: " + e.toString() + " for " + _handler, e);

      switch (_state) {
        case CLOSED:
          break;
        case CLOSE:
          _handler.earlyEOF();
          break;
        default:
          setState(State.CLOSE);
          _handler.badMessage(400, null);
      }
    }
    return false;
  }
Exemplo n.º 16
0
  /*
   * Parse the message headers and return true if the handler has signaled for
   * a return
   */
  protected boolean parseHeaders(ByteBuffer buffer) {
    boolean handle = false;

    // Process headers
    while (_state.ordinal() < State.CONTENT.ordinal() && buffer.hasRemaining() && !handle) {
      // process each character
      byte ch = next(buffer);
      if (ch == 0) break;

      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        log.warn("Header is too large > {}", _maxHeaderBytes);
        throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
      }

      switch (_state) {
        case HEADER:
          switch (ch) {
            case HttpTokens.COLON:
            case HttpTokens.SPACE:
            case HttpTokens.TAB:
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Continuation");

            case HttpTokens.LINE_FEED:
              {
                _contentPosition = 0;

                // End of headers!

                // Was there a required host header?
                if (!_host && _version == HttpVersion.HTTP_1_1 && _requestHandler != null) {
                  throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "No Host");
                }

                // is it a response that cannot have a body?
                if (_responseHandler != null
                    && // response
                    (_responseStatus == 304
                        || // not-modified response
                        _responseStatus == 204
                        || // no-content
                        // response
                        _responseStatus < 200)) // 1xx response
                _endOfContent = EndOfContent.NO_CONTENT; // ignore any
                // other
                // headers
                // set

                // else if we don't know framing
                else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) {
                  if (_responseStatus == 0 // request
                      || _responseStatus == 304 // not-modified
                      // response
                      || _responseStatus == 204 // no-content response
                      || _responseStatus < 200) // 1xx response
                  _endOfContent = EndOfContent.NO_CONTENT;
                  else _endOfContent = EndOfContent.EOF_CONTENT;
                }

                // How is the message ended?
                switch (_endOfContent) {
                  case EOF_CONTENT:
                    setState(State.EOF_CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;

                  case CHUNKED_CONTENT:
                    setState(State.CHUNKED_CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;

                  case NO_CONTENT:
                    handle = _handler.headerComplete() || handle;
                    setState(State.END);
                    handle = _handler.messageComplete() || handle;
                    return handle;

                  default:
                    setState(State.CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;
                }
              }

            default:
              {
                // now handle the ch
                if (ch <= HttpTokens.SPACE) throw new BadMessageException();

                if (buffer.hasRemaining()) {
                  // Try a look ahead for the known header name and value.
                  HttpField field =
                      _connectionFields == null
                          ? null
                          : _connectionFields.getBest(buffer, -1, buffer.remaining());
                  if (field == null) field = CACHE.getBest(buffer, -1, buffer.remaining());

                  if (field != null) {
                    final String n;
                    final String v;

                    if (_strict) {
                      // Have to get the fields exactly from the
                      // buffer to match case
                      String fn = field.getName();
                      String fv = field.getValue();
                      n =
                          BufferUtils.toString(
                              buffer,
                              buffer.position() - 1,
                              fn.length(),
                              StandardCharsets.US_ASCII);
                      if (fv == null) v = null;
                      else {
                        v =
                            BufferUtils.toString(
                                buffer,
                                buffer.position() + fn.length() + 1,
                                fv.length(),
                                StandardCharsets.ISO_8859_1);
                        field = new HttpField(field.getHeader(), n, v);
                      }
                    } else {
                      n = field.getName();
                      v = field.getValue();
                    }

                    _header = field.getHeader();
                    _headerString = n;

                    if (v == null) {
                      // Header only
                      setState(State.HEADER_VALUE);
                      _string.setLength(0);
                      _length = 0;
                      buffer.position(buffer.position() + n.length() + 1);
                      break;
                    } else {
                      // Header and value
                      int pos = buffer.position() + n.length() + v.length() + 1;
                      byte b = buffer.get(pos);

                      if (b == HttpTokens.CARRIAGE_RETURN || b == HttpTokens.LINE_FEED) {
                        _field = field;
                        _valueString = v;
                        setState(State.HEADER_IN_VALUE);

                        if (b == HttpTokens.CARRIAGE_RETURN) {
                          _cr = true;
                          buffer.position(pos + 1);
                        } else buffer.position(pos);
                        break;
                      } else {
                        setState(State.HEADER_IN_VALUE);
                        setString(v);
                        buffer.position(pos);
                        break;
                      }
                    }
                  }
                }

                // New header
                setState(State.HEADER_IN_NAME);
                _string.setLength(0);
                _string.append((char) ch);
                _length = 1;
              }
          }
          break;

        case HEADER_IN_NAME:
          if (ch == HttpTokens.COLON) {
            if (_headerString == null) {
              _headerString = takeString();
              _header = HttpHeader.CACHE.get(_headerString);
            }
            _length = -1;

            setState(State.HEADER_VALUE);
            break;
          }

          if (ch > HttpTokens.SPACE) {
            if (_header != null) {
              setString(_header.asString());
              _header = null;
              _headerString = null;
            }

            _string.append((char) ch);
            if (ch > HttpTokens.SPACE) _length = _string.length();
            break;
          }

          throw new IllegalCharacterException(_state, ch, buffer);

        case HEADER_VALUE:
          if (ch > HttpTokens.SPACE || ch < 0) {
            _string.append((char) (0xff & ch));
            _length = _string.length();
            setState(State.HEADER_IN_VALUE);
            break;
          }

          if (ch == HttpTokens.SPACE || ch == HttpTokens.TAB) break;

          if (ch == HttpTokens.LINE_FEED) {
            _value = null;
            _string.setLength(0);
            _valueString = null;
            _length = -1;

            parsedHeader();
            setState(State.HEADER);
            break;
          }
          throw new IllegalCharacterException(_state, ch, buffer);

        case HEADER_IN_VALUE:
          if (ch >= HttpTokens.SPACE || ch < 0 || ch == HttpTokens.TAB) {
            if (_valueString != null) {
              setString(_valueString);
              _valueString = null;
              _field = null;
            }
            _string.append((char) (0xff & ch));
            if (ch > HttpTokens.SPACE || ch < 0) _length = _string.length();
            break;
          }

          if (ch == HttpTokens.LINE_FEED) {
            if (_length > 0) {
              _value = null;
              _valueString = takeString();
              _length = -1;
            }
            parsedHeader();
            setState(State.HEADER);
            break;
          }

          throw new IllegalCharacterException(_state, ch, buffer);

        default:
          throw new IllegalStateException(_state.toString());
      }
    }

    return handle;
  }
Exemplo n.º 17
0
 protected void setState(State state) {
   if (DEBUG) log.debug("{} --> {}", _state, state);
   _state = state;
 }
Exemplo n.º 18
0
 /** Request that the associated data source be closed */
 public void close() {
   if (DEBUG) log.debug("close {}", this);
   setState(State.CLOSE);
 }
Exemplo n.º 19
0
 /** Signal that the associated data source is at EOF */
 public void atEOF() {
   if (DEBUG) log.debug("atEOF {}", this);
   _eof = true;
 }
  @Override
  public void dispatcher(HttpServletRequest request, HttpServletResponse response) {
    String encoding = webContext.getEncoding();
    try {
      request.setCharacterEncoding(encoding);
    } catch (Throwable t) {
      log.error("dispatcher error", t);
    }
    response.setCharacterEncoding(encoding);

    String uri = request.getRequestURI();
    String prePath = request.getContextPath() + request.getServletPath();
    String invokeUri = uri.substring(prePath.length());
    String key = request.getMethod() + "@" + invokeUri;
    String beforeIntercept = "b#" + invokeUri;
    String afterIntercept = "a#" + invokeUri;
    Set<MvcMetaInfo> beforeSet = webContext.getBean(beforeIntercept);
    Set<MvcMetaInfo> afterSet = webContext.getBean(afterIntercept);

    log.debug("uri map [{}]", key);
    MvcMetaInfo mvcMetaInfo = webContext.getBean(key);
    if (mvcMetaInfo != null) {
      Object ret = null;
      Object beforeRet = null; // 前置拦截器的返回值
      MvcMetaInfo lastBefore = null; // 最后得到的前置拦截器
      Object afterRet = null; // 后置拦截器的返回值
      MvcMetaInfo lastAfter = null; // 最后得到的后置拦截器

      // 前置拦截栈调用
      if (beforeSet != null) {
        for (MvcMetaInfo before : beforeSet) {
          Object[] beforeP = getParams(request, response, before, null);
          beforeRet = before.invoke(beforeP);
          if (beforeRet != null) {
            lastBefore = before;
            break;
          }
        }
      }

      if (beforeRet == null) {
        // controller调用
        Object[] p = getParams(request, response, mvcMetaInfo, null);
        ret = mvcMetaInfo.invoke(p);

        // 后置拦截栈调用
        if (afterSet != null) {
          for (MvcMetaInfo after : afterSet) {
            Object[] afterP = getParams(request, response, after, ret);
            afterRet = after.invoke(afterP);
            if (afterRet != null) {
              lastAfter = after;
              break;
            }
          }
        }
      }

      // 视图渲染
      try {
        if (afterRet != null) {
          lastAfter.getViewHandle().render(request, response, afterRet);
        } else if (beforeRet != null) {
          lastBefore.getViewHandle().render(request, response, beforeRet);
        } else {
          mvcMetaInfo.getViewHandle().render(request, response, ret);
        }
      } catch (Throwable t) {
        log.error("dispatcher error", t);
      }
    } else {
      String msg = request.getRequestURI() + " not register";
      SystemHtmlPage.responseSystemPage(
          request, response, webContext.getEncoding(), HttpServletResponse.SC_NOT_FOUND, msg);
    }
  }
Exemplo n.º 21
0
/**
 * A Parser for 1.0 and 1.1 as defined by RFC7230
 *
 * <p>This parser parses HTTP client and server messages from buffers passed in the {@link
 * #parseNext(ByteBuffer)} method. The parsed elements of the HTTP message are passed as event calls
 * to the {@link HttpHandler} instance the parser is constructed with. If the passed handler is a
 * {@link RequestHandler} then server side parsing is performed and if it is a {@link
 * ResponseHandler}, then client side parsing is done.
 *
 * <p>The contract of the {@link HttpHandler} API is that if a call returns true then the call to
 * {@link #parseNext(ByteBuffer)} will return as soon as possible also with a true response.
 * Typically this indicates that the parsing has reached a stage where the caller should process the
 * events accumulated by the handler. It is the preferred calling style that handling such as
 * calling a servlet to process a request, should be done after a true return from {@link
 * #parseNext(ByteBuffer)} rather than from within the scope of a call like {@link
 * RequestHandler#messageComplete()}
 *
 * <p>For performance, the parse is heavily dependent on the {@link Trie#getBest(ByteBuffer, int,
 * int)} method to look ahead in a single pass for both the structure ( : and CRLF ) and semantic
 * (which header and value) of a header. Specifically the static {@link HttpHeader#CACHE} is used to
 * lookup common combinations of headers and values (eg. "Connection: close" ), or just header names
 * (eg. "Connection:" ). For headers who's value is not known statically (eg. Host, COOKIE) then a
 * per parser dynamic Trie of {@link HttpFields} from previous parsed messages is used to help the
 * parsing of subsequent messages.
 *
 * <p>If the system property "org.fireflysource.http.HttpParser.STRICT" is set to true, then the
 * parser will strictly pass on the exact strings received for methods and header fields. Otherwise
 * a fast case insensitive string lookup is used that may alter the case of the method and/or
 * headers
 *
 * <p>
 *
 * @see <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>
 */
public class HttpParser {
  private static Log log = LogFactory.getInstance().getLog("firefly-system");
  public static final boolean __STRICT =
      Boolean.getBoolean("org.fireflysource.http.HttpParser.STRICT");
  public static final int INITIAL_URI_LENGTH = 256;

  /**
   * Cache of common {@link HttpField}s including:
   *
   * <UL>
   *   <LI>Common static combinations such as:
   *       <UL>
   *         <li>Connection: close
   *         <li>Accept-Encoding: gzip
   *         <li>Content-Length: 0
   *       </ul>
   *   <li>Combinations of Content-Type header for common mime types by common charsets
   *   <li>Most common headers with null values so that a lookup will at least determine the header
   *       name even if the name:value combination is not cached
   * </ul>
   */
  public static final Trie<HttpField> CACHE = new ArrayTrie<>(2048);

  // States
  public enum State {
    START,
    METHOD,
    RESPONSE_VERSION,
    SPACE1,
    STATUS,
    URI,
    SPACE2,
    REQUEST_VERSION,
    REASON,
    PROXY,
    HEADER,
    HEADER_IN_NAME,
    HEADER_VALUE,
    HEADER_IN_VALUE,
    CONTENT,
    EOF_CONTENT,
    CHUNKED_CONTENT,
    CHUNK_SIZE,
    CHUNK_PARAMS,
    CHUNK,
    CHUNK_END,
    END,
    CLOSE, // The associated stream/endpoint should be closed
    CLOSED // The associated stream/endpoint is at EOF
  }

  private static final EnumSet<State> __idleStates =
      EnumSet.of(State.START, State.END, State.CLOSE, State.CLOSED);
  private static final EnumSet<State> __completeStates =
      EnumSet.of(State.END, State.CLOSE, State.CLOSED);

  private final boolean DEBUG = log.isDebugEnable(); // Cache debug to help
  // branch prediction
  private final HttpHandler _handler;
  private final RequestHandler _requestHandler;
  private final ResponseHandler _responseHandler;
  private final int _maxHeaderBytes;
  private final boolean _strict;
  private HttpField _field;
  private HttpHeader _header;
  private String _headerString;
  private HttpHeaderValue _value;
  private String _valueString;
  private int _responseStatus;
  private int _headerBytes;
  private boolean _host;

  private volatile State _state = State.START;
  private volatile boolean _eof;
  private HttpMethod _method;
  private String _methodString;
  private HttpVersion _version;
  private Utf8StringBuilder _uri = new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune?
  private EndOfContent _endOfContent;
  private long _contentLength;
  private long _contentPosition;
  private int _chunkLength;
  private int _chunkPosition;
  private boolean _headResponse;
  private boolean _cr;
  private ByteBuffer _contentChunk;
  private Trie<HttpField> _connectionFields;

  private int _length;
  private final StringBuilder _string = new StringBuilder();

  static {
    CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE));
    CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE));
    CACHE.put(new HttpField(HttpHeader.CONNECTION, HttpHeaderValue.UPGRADE));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip, deflate"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING, "gzip,deflate,sdch"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE, "en-GB,en-US;q=0.8,en;q=0.6"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT, "*/*"));
    CACHE.put(new HttpField(HttpHeader.ACCEPT, "image/png,image/*;q=0.8,*/*;q=0.5"));
    CACHE.put(
        new HttpField(
            HttpHeader.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
    CACHE.put(new HttpField(HttpHeader.PRAGMA, "no-cache"));
    CACHE.put(
        new HttpField(
            HttpHeader.CACHE_CONTROL, "private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
    CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL, "no-cache"));
    CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH, "0"));
    CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "gzip"));
    CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING, "deflate"));
    CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING, "chunked"));
    CACHE.put(new HttpField(HttpHeader.EXPIRES, "Fri, 01 Jan 1990 00:00:00 GMT"));

    // Add common Content types as fields
    for (String type :
        new String[] {
          "text/plain",
          "text/html",
          "text/xml",
          "text/json",
          "application/json",
          "application/x-www-form-urlencoded"
        }) {
      HttpField field = new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type);
      CACHE.put(field);

      for (String charset : new String[] {"utf-8", "iso-8859-1"}) {
        CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + ";charset=" + charset));
        CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE, type + "; charset=" + charset));
        CACHE.put(
            new PreEncodedHttpField(
                HttpHeader.CONTENT_TYPE, type + ";charset=" + charset.toUpperCase()));
        CACHE.put(
            new PreEncodedHttpField(
                HttpHeader.CONTENT_TYPE, type + "; charset=" + charset.toUpperCase()));
      }
    }

    // Add headers with null values so HttpParser can avoid looking up name
    // again for unknown values
    for (HttpHeader h : HttpHeader.values())
      if (!CACHE.put(new HttpField(h, (String) null)))
        throw new IllegalStateException("CACHE FULL");
    // Add some more common headers
    CACHE.put(new HttpField(HttpHeader.REFERER, (String) null));
    CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE, (String) null));
    CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH, (String) null));
    CACHE.put(new HttpField(HttpHeader.AUTHORIZATION, (String) null));
    CACHE.put(new HttpField(HttpHeader.COOKIE, (String) null));
  }

  public HttpParser(RequestHandler handler) {
    this(handler, -1, __STRICT);
  }

  public HttpParser(ResponseHandler handler) {
    this(handler, -1, __STRICT);
  }

  public HttpParser(RequestHandler handler, int maxHeaderBytes) {
    this(handler, maxHeaderBytes, __STRICT);
  }

  public HttpParser(ResponseHandler handler, int maxHeaderBytes) {
    this(handler, maxHeaderBytes, __STRICT);
  }

  public HttpParser(RequestHandler handler, int maxHeaderBytes, boolean strict) {
    _handler = handler;
    _requestHandler = handler;
    _responseHandler = null;
    _maxHeaderBytes = maxHeaderBytes;
    _strict = strict;
  }

  public HttpParser(ResponseHandler handler, int maxHeaderBytes, boolean strict) {
    _handler = handler;
    _requestHandler = null;
    _responseHandler = handler;
    _maxHeaderBytes = maxHeaderBytes;
    _strict = strict;
  }

  public long getContentLength() {
    return _contentLength;
  }

  public long getContentRead() {
    return _contentPosition;
  }

  /**
   * Set if a HEAD response is expected
   *
   * @param head true if head response is expected
   */
  public void setHeadResponse(boolean head) {
    _headResponse = head;
  }

  protected void setResponseStatus(int status) {
    _responseStatus = status;
  }

  public State getState() {
    return _state;
  }

  public boolean inContentState() {
    return _state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal();
  }

  public boolean inHeaderState() {
    return _state.ordinal() < State.CONTENT.ordinal();
  }

  public boolean isChunking() {
    return _endOfContent == EndOfContent.CHUNKED_CONTENT;
  }

  public boolean isStart() {
    return isState(State.START);
  }

  public boolean isClose() {
    return isState(State.CLOSE);
  }

  public boolean isClosed() {
    return isState(State.CLOSED);
  }

  public boolean isIdle() {
    return __idleStates.contains(_state);
  }

  public boolean isComplete() {
    return __completeStates.contains(_state);
  }

  public boolean isState(State state) {
    return _state == state;
  }

  enum CharState {
    ILLEGAL,
    CR,
    LF,
    LEGAL
  }

  private static final CharState[] __charState;

  static {
    // token = 1*tchar
    // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
    // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
    // / DIGIT / ALPHA
    // ; any VCHAR, except delimiters
    // quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
    // qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
    // obs-text = %x80-FF
    // comment = "(" *( ctext / quoted-pair / comment ) ")"
    // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
    // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )

    __charState = new CharState[256];
    Arrays.fill(__charState, CharState.ILLEGAL);
    __charState[LINE_FEED] = CharState.LF;
    __charState[CARRIAGE_RETURN] = CharState.CR;
    __charState[TAB] = CharState.LEGAL;
    __charState[SPACE] = CharState.LEGAL;

    __charState['!'] = CharState.LEGAL;
    __charState['#'] = CharState.LEGAL;
    __charState['$'] = CharState.LEGAL;
    __charState['%'] = CharState.LEGAL;
    __charState['&'] = CharState.LEGAL;
    __charState['\''] = CharState.LEGAL;
    __charState['*'] = CharState.LEGAL;
    __charState['+'] = CharState.LEGAL;
    __charState['-'] = CharState.LEGAL;
    __charState['.'] = CharState.LEGAL;
    __charState['^'] = CharState.LEGAL;
    __charState['_'] = CharState.LEGAL;
    __charState['`'] = CharState.LEGAL;
    __charState['|'] = CharState.LEGAL;
    __charState['~'] = CharState.LEGAL;

    __charState['"'] = CharState.LEGAL;

    __charState['\\'] = CharState.LEGAL;
    __charState['('] = CharState.LEGAL;
    __charState[')'] = CharState.LEGAL;
    Arrays.fill(__charState, 0x21, 0x27 + 1, CharState.LEGAL);
    Arrays.fill(__charState, 0x2A, 0x5B + 1, CharState.LEGAL);
    Arrays.fill(__charState, 0x5D, 0x7E + 1, CharState.LEGAL);
    Arrays.fill(__charState, 0x80, 0xFF + 1, CharState.LEGAL);
  }

  private byte next(ByteBuffer buffer) {
    byte ch = buffer.get();

    CharState s = __charState[0xff & ch];
    switch (s) {
      case ILLEGAL:
        throw new IllegalCharacterException(_state, ch, buffer);

      case LF:
        _cr = false;
        break;

      case CR:
        if (_cr) throw new BadMessageException("Bad EOL");

        _cr = true;
        if (buffer.hasRemaining()) {
          if (_maxHeaderBytes > 0 && _state.ordinal() < State.END.ordinal()) _headerBytes++;
          return next(buffer);
        }

        // Can return 0 here to indicate the need for more characters,
        // because a real 0 in the buffer would cause a BadMessage below
        return 0;

      case LEGAL:
        if (_cr) throw new BadMessageException("Bad EOL");
    }

    return ch;
  }

  /*
   * Quick lookahead for the start state looking for a request method or a
   * HTTP version, otherwise skip white space until something else to parse.
   */
  private boolean quickStart(ByteBuffer buffer) {
    if (_requestHandler != null) {
      _method = HttpMethod.lookAheadGet(buffer);
      if (_method != null) {
        _methodString = _method.asString();
        buffer.position(buffer.position() + _methodString.length() + 1);

        setState(State.SPACE1);
        return false;
      }
    } else if (_responseHandler != null) {
      _version = HttpVersion.lookAheadGet(buffer);
      if (_version != null) {
        buffer.position(buffer.position() + _version.asString().length() + 1);
        setState(State.SPACE1);
        return false;
      }
    }

    // Quick start look
    while (_state == State.START && buffer.hasRemaining()) {
      int ch = next(buffer);

      if (ch > SPACE) {
        _string.setLength(0);
        _string.append((char) ch);
        setState(_requestHandler != null ? State.METHOD : State.RESPONSE_VERSION);
        return false;
      } else if (ch == 0) break;
      else if (ch < 0) throw new BadMessageException();

      // count this white space as a header byte to avoid DOS
      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        log.warn("padding is too large > {}", _maxHeaderBytes);
        throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
      }
    }
    return false;
  }

  private void setString(String s) {
    _string.setLength(0);
    _string.append(s);
    _length = s.length();
  }

  private String takeString() {
    _string.setLength(_length);
    String s = _string.toString();
    _string.setLength(0);
    _length = -1;
    return s;
  }

  /*
   * Parse a request or response line
   */
  private boolean parseLine(ByteBuffer buffer) {
    boolean handle = false;

    // Process headers
    while (_state.ordinal() < State.HEADER.ordinal() && buffer.hasRemaining() && !handle) {
      // process each character
      byte ch = next(buffer);
      if (ch == 0) break;

      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        if (_state == State.URI) {
          log.warn("URI is too large > {}", _maxHeaderBytes);
          throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
        } else {
          if (_requestHandler != null) log.warn("request is too large > {}", _maxHeaderBytes);
          else log.warn("response is too large > {}", _maxHeaderBytes);
          throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
        }
      }

      switch (_state) {
        case METHOD:
          if (ch == SPACE) {
            _length = _string.length();
            _methodString = takeString();
            HttpMethod method = HttpMethod.CACHE.get(_methodString);
            if (method != null && !_strict) _methodString = method.asString();
            setState(State.SPACE1);
          } else if (ch < SPACE) {
            if (ch == LINE_FEED) throw new BadMessageException("No URI");
            else throw new IllegalCharacterException(_state, ch, buffer);
          } else _string.append((char) ch);
          break;

        case RESPONSE_VERSION:
          if (ch == HttpTokens.SPACE) {
            _length = _string.length();
            String version = takeString();
            _version = HttpVersion.CACHE.get(version);
            if (_version == null)
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version");
            setState(State.SPACE1);
          } else if (ch < HttpTokens.SPACE) throw new IllegalCharacterException(_state, ch, buffer);
          else _string.append((char) ch);
          break;

        case SPACE1:
          if (ch > HttpTokens.SPACE || ch < 0) {
            if (_responseHandler != null) {
              setState(State.STATUS);
              setResponseStatus(ch - '0');
            } else {
              _uri.reset();
              setState(State.URI);
              // quick scan for space or EoBuffer
              if (buffer.hasArray()) {
                byte[] array = buffer.array();
                int p = buffer.arrayOffset() + buffer.position();
                int l = buffer.arrayOffset() + buffer.limit();
                int i = p;
                while (i < l && array[i] > HttpTokens.SPACE) i++;

                int len = i - p;
                _headerBytes += len;

                if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
                  log.warn("URI is too large > {}", _maxHeaderBytes);
                  throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
                }
                _uri.append(array, p - 1, len + 1);
                buffer.position(i - buffer.arrayOffset());
              } else _uri.append(ch);
            }
          } else if (ch < HttpTokens.SPACE) {
            throw new BadMessageException(
                HttpStatus.BAD_REQUEST_400, _requestHandler != null ? "No URI" : "No Status");
          }
          break;

        case STATUS:
          if (ch == HttpTokens.SPACE) {
            setState(State.SPACE2);
          } else if (ch >= '0' && ch <= '9') {
            _responseStatus = _responseStatus * 10 + (ch - '0');
          } else if (ch < HttpTokens.SPACE && ch >= 0) {
            handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle;
            setState(State.HEADER);
          } else {
            throw new BadMessageException();
          }
          break;

        case URI:
          if (ch == HttpTokens.SPACE) {
            setState(State.SPACE2);
          } else if (ch < HttpTokens.SPACE && ch >= 0) {
            // HTTP/0.9
            throw new BadMessageException("HTTP/0.9 not supported");
          } else {
            _uri.append(ch);
          }
          break;

        case SPACE2:
          if (ch > HttpTokens.SPACE) {
            _string.setLength(0);
            _string.append((char) ch);
            if (_responseHandler != null) {
              _length = 1;
              setState(State.REASON);
            } else {
              setState(State.REQUEST_VERSION);

              // try quick look ahead for HTTP Version
              HttpVersion version;
              if (buffer.position() > 0 && buffer.hasArray())
                version =
                    HttpVersion.lookAheadGet(
                        buffer.array(),
                        buffer.arrayOffset() + buffer.position() - 1,
                        buffer.arrayOffset() + buffer.limit());
              else version = HttpVersion.CACHE.getBest(buffer, 0, buffer.remaining());

              if (version != null) {
                int pos = buffer.position() + version.asString().length() - 1;
                if (pos < buffer.limit()) {
                  byte n = buffer.get(pos);
                  if (n == HttpTokens.CARRIAGE_RETURN) {
                    _cr = true;
                    _version = version;
                    _string.setLength(0);
                    buffer.position(pos + 1);
                  } else if (n == HttpTokens.LINE_FEED) {
                    _version = version;
                    _string.setLength(0);
                    buffer.position(pos);
                  }
                }
              }
            }
          } else if (ch == HttpTokens.LINE_FEED) {
            if (_responseHandler != null) {
              handle = _responseHandler.startResponse(_version, _responseStatus, null) || handle;
              setState(State.HEADER);
            } else {
              // HTTP/0.9
              throw new BadMessageException("HTTP/0.9 not supported");
            }
          } else if (ch < 0) throw new BadMessageException();
          break;

        case REQUEST_VERSION:
          if (ch == HttpTokens.LINE_FEED) {
            if (_version == null) {
              _length = _string.length();
              _version = HttpVersion.CACHE.get(takeString());
            }
            if (_version == null)
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Unknown Version");

            // Should we try to cache header fields?
            if (_connectionFields == null
                && _version.getVersion() >= HttpVersion.HTTP_1_1.getVersion()
                && _handler.getHeaderCacheSize() > 0) {
              int header_cache = _handler.getHeaderCacheSize();
              _connectionFields = new ArrayTernaryTrie<>(header_cache);
            }

            setState(State.HEADER);

            handle =
                _requestHandler.startRequest(_methodString, _uri.toString(), _version) || handle;
            continue;
          } else if (ch >= HttpTokens.SPACE) _string.append((char) ch);
          else throw new BadMessageException();

          break;

        case REASON:
          if (ch == HttpTokens.LINE_FEED) {
            String reason = takeString();

            setState(State.HEADER);
            handle = _responseHandler.startResponse(_version, _responseStatus, reason) || handle;
            continue;
          } else if (ch >= HttpTokens.SPACE) {
            _string.append((char) ch);
            if (ch != ' ' && ch != '\t') _length = _string.length();
          } else throw new BadMessageException();
          break;

        default:
          throw new IllegalStateException(_state.toString());
      }
    }

    return handle;
  }

  private void parsedHeader() {
    // handler last header if any. Delayed to here just in case there was a
    // continuation line (above)
    if (_headerString != null || _valueString != null) {
      // Handle known headers
      if (_header != null) {
        boolean add_to_connection_trie = false;
        switch (_header) {
          case CONTENT_LENGTH:
            if (_endOfContent != EndOfContent.CHUNKED_CONTENT) {
              try {
                _contentLength = Long.parseLong(_valueString);
              } catch (NumberFormatException e) {
                throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Content-Length");
              }
              if (_contentLength <= 0) _endOfContent = EndOfContent.NO_CONTENT;
              else _endOfContent = EndOfContent.CONTENT_LENGTH;
            }
            break;

          case TRANSFER_ENCODING:
            if (_value == HttpHeaderValue.CHUNKED) _endOfContent = EndOfContent.CHUNKED_CONTENT;
            else {
              if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
                _endOfContent = EndOfContent.CHUNKED_CONTENT;
              else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString())) {
                throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad chunking");
              }
            }
            break;

          case HOST:
            _host = true;
            if (!(_field instanceof HostPortHttpField)) {
              _field =
                  new HostPortHttpField(
                      _header, _strict ? _headerString : _header.asString(), _valueString);
              add_to_connection_trie = _connectionFields != null;
            }
            break;

          case CONNECTION:
            // Don't cache if not persistent
            if (_valueString != null && _valueString.contains("close")) _connectionFields = null;

            break;

          case AUTHORIZATION:
          case ACCEPT:
          case ACCEPT_CHARSET:
          case ACCEPT_ENCODING:
          case ACCEPT_LANGUAGE:
          case COOKIE:
          case CACHE_CONTROL:
          case USER_AGENT:
            add_to_connection_trie = _connectionFields != null && _field == null;
            break;

          default:
            break;
        }

        if (add_to_connection_trie
            && !_connectionFields.isFull()
            && _header != null
            && _valueString != null) {
          if (_field == null)
            _field =
                new HttpField(_header, _strict ? _headerString : _header.asString(), _valueString);
          _connectionFields.put(_field);
        }
      }
      _handler.parsedHeader(
          _field != null ? _field : new HttpField(_header, _headerString, _valueString));
    }

    _headerString = _valueString = null;
    _header = null;
    _value = null;
    _field = null;
  }

  /*
   * Parse the message headers and return true if the handler has signaled for
   * a return
   */
  protected boolean parseHeaders(ByteBuffer buffer) {
    boolean handle = false;

    // Process headers
    while (_state.ordinal() < State.CONTENT.ordinal() && buffer.hasRemaining() && !handle) {
      // process each character
      byte ch = next(buffer);
      if (ch == 0) break;

      if (_maxHeaderBytes > 0 && ++_headerBytes > _maxHeaderBytes) {
        log.warn("Header is too large > {}", _maxHeaderBytes);
        throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
      }

      switch (_state) {
        case HEADER:
          switch (ch) {
            case HttpTokens.COLON:
            case HttpTokens.SPACE:
            case HttpTokens.TAB:
              throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "Bad Continuation");

            case HttpTokens.LINE_FEED:
              {
                _contentPosition = 0;

                // End of headers!

                // Was there a required host header?
                if (!_host && _version == HttpVersion.HTTP_1_1 && _requestHandler != null) {
                  throw new BadMessageException(HttpStatus.BAD_REQUEST_400, "No Host");
                }

                // is it a response that cannot have a body?
                if (_responseHandler != null
                    && // response
                    (_responseStatus == 304
                        || // not-modified response
                        _responseStatus == 204
                        || // no-content
                        // response
                        _responseStatus < 200)) // 1xx response
                _endOfContent = EndOfContent.NO_CONTENT; // ignore any
                // other
                // headers
                // set

                // else if we don't know framing
                else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT) {
                  if (_responseStatus == 0 // request
                      || _responseStatus == 304 // not-modified
                      // response
                      || _responseStatus == 204 // no-content response
                      || _responseStatus < 200) // 1xx response
                  _endOfContent = EndOfContent.NO_CONTENT;
                  else _endOfContent = EndOfContent.EOF_CONTENT;
                }

                // How is the message ended?
                switch (_endOfContent) {
                  case EOF_CONTENT:
                    setState(State.EOF_CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;

                  case CHUNKED_CONTENT:
                    setState(State.CHUNKED_CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;

                  case NO_CONTENT:
                    handle = _handler.headerComplete() || handle;
                    setState(State.END);
                    handle = _handler.messageComplete() || handle;
                    return handle;

                  default:
                    setState(State.CONTENT);
                    handle = _handler.headerComplete() || handle;
                    return handle;
                }
              }

            default:
              {
                // now handle the ch
                if (ch <= HttpTokens.SPACE) throw new BadMessageException();

                if (buffer.hasRemaining()) {
                  // Try a look ahead for the known header name and value.
                  HttpField field =
                      _connectionFields == null
                          ? null
                          : _connectionFields.getBest(buffer, -1, buffer.remaining());
                  if (field == null) field = CACHE.getBest(buffer, -1, buffer.remaining());

                  if (field != null) {
                    final String n;
                    final String v;

                    if (_strict) {
                      // Have to get the fields exactly from the
                      // buffer to match case
                      String fn = field.getName();
                      String fv = field.getValue();
                      n =
                          BufferUtils.toString(
                              buffer,
                              buffer.position() - 1,
                              fn.length(),
                              StandardCharsets.US_ASCII);
                      if (fv == null) v = null;
                      else {
                        v =
                            BufferUtils.toString(
                                buffer,
                                buffer.position() + fn.length() + 1,
                                fv.length(),
                                StandardCharsets.ISO_8859_1);
                        field = new HttpField(field.getHeader(), n, v);
                      }
                    } else {
                      n = field.getName();
                      v = field.getValue();
                    }

                    _header = field.getHeader();
                    _headerString = n;

                    if (v == null) {
                      // Header only
                      setState(State.HEADER_VALUE);
                      _string.setLength(0);
                      _length = 0;
                      buffer.position(buffer.position() + n.length() + 1);
                      break;
                    } else {
                      // Header and value
                      int pos = buffer.position() + n.length() + v.length() + 1;
                      byte b = buffer.get(pos);

                      if (b == HttpTokens.CARRIAGE_RETURN || b == HttpTokens.LINE_FEED) {
                        _field = field;
                        _valueString = v;
                        setState(State.HEADER_IN_VALUE);

                        if (b == HttpTokens.CARRIAGE_RETURN) {
                          _cr = true;
                          buffer.position(pos + 1);
                        } else buffer.position(pos);
                        break;
                      } else {
                        setState(State.HEADER_IN_VALUE);
                        setString(v);
                        buffer.position(pos);
                        break;
                      }
                    }
                  }
                }

                // New header
                setState(State.HEADER_IN_NAME);
                _string.setLength(0);
                _string.append((char) ch);
                _length = 1;
              }
          }
          break;

        case HEADER_IN_NAME:
          if (ch == HttpTokens.COLON) {
            if (_headerString == null) {
              _headerString = takeString();
              _header = HttpHeader.CACHE.get(_headerString);
            }
            _length = -1;

            setState(State.HEADER_VALUE);
            break;
          }

          if (ch > HttpTokens.SPACE) {
            if (_header != null) {
              setString(_header.asString());
              _header = null;
              _headerString = null;
            }

            _string.append((char) ch);
            if (ch > HttpTokens.SPACE) _length = _string.length();
            break;
          }

          throw new IllegalCharacterException(_state, ch, buffer);

        case HEADER_VALUE:
          if (ch > HttpTokens.SPACE || ch < 0) {
            _string.append((char) (0xff & ch));
            _length = _string.length();
            setState(State.HEADER_IN_VALUE);
            break;
          }

          if (ch == HttpTokens.SPACE || ch == HttpTokens.TAB) break;

          if (ch == HttpTokens.LINE_FEED) {
            _value = null;
            _string.setLength(0);
            _valueString = null;
            _length = -1;

            parsedHeader();
            setState(State.HEADER);
            break;
          }
          throw new IllegalCharacterException(_state, ch, buffer);

        case HEADER_IN_VALUE:
          if (ch >= HttpTokens.SPACE || ch < 0 || ch == HttpTokens.TAB) {
            if (_valueString != null) {
              setString(_valueString);
              _valueString = null;
              _field = null;
            }
            _string.append((char) (0xff & ch));
            if (ch > HttpTokens.SPACE || ch < 0) _length = _string.length();
            break;
          }

          if (ch == HttpTokens.LINE_FEED) {
            if (_length > 0) {
              _value = null;
              _valueString = takeString();
              _length = -1;
            }
            parsedHeader();
            setState(State.HEADER);
            break;
          }

          throw new IllegalCharacterException(_state, ch, buffer);

        default:
          throw new IllegalStateException(_state.toString());
      }
    }

    return handle;
  }

  /**
   * Parse until next Event.
   *
   * @param buffer the buffer to parse
   * @return True if an {@link RequestHandler} method was called and it returned true;
   */
  public boolean parseNext(ByteBuffer buffer) {
    if (DEBUG) log.debug("parseNext s={} {}", _state, BufferUtils.toDetailString(buffer));
    try {
      // Start a request/response
      if (_state == State.START) {
        _version = null;
        _method = null;
        _methodString = null;
        _endOfContent = EndOfContent.UNKNOWN_CONTENT;
        _header = null;
        if (quickStart(buffer)) return true;
      }

      // Request/response line
      if (_state.ordinal() >= State.START.ordinal() && _state.ordinal() < State.HEADER.ordinal()) {
        if (parseLine(buffer)) return true;
      }

      // parse headers
      if (_state.ordinal() >= State.HEADER.ordinal()
          && _state.ordinal() < State.CONTENT.ordinal()) {
        if (parseHeaders(buffer)) return true;
      }

      // parse content
      if (_state.ordinal() >= State.CONTENT.ordinal() && _state.ordinal() < State.END.ordinal()) {
        // Handle HEAD response
        if (_responseStatus > 0 && _headResponse) {
          setState(State.END);
          return _handler.messageComplete();
        } else {
          if (parseContent(buffer)) return true;
        }
      }

      // handle end states
      if (_state == State.END) {
        // eat white space
        while (buffer.remaining() > 0 && buffer.get(buffer.position()) <= HttpTokens.SPACE)
          buffer.get();
      } else if (_state == State.CLOSE) {
        // Seeking EOF
        if (BufferUtils.hasContent(buffer)) {
          // Just ignore data when closed
          _headerBytes += buffer.remaining();
          BufferUtils.clear(buffer);
          if (_maxHeaderBytes > 0 && _headerBytes > _maxHeaderBytes) {
            // Don't want to waste time reading data of a closed
            // request
            throw new IllegalStateException("too much data seeking EOF");
          }
        }
      } else if (_state == State.CLOSED) {
        BufferUtils.clear(buffer);
      }

      // Handle EOF
      if (_eof && !buffer.hasRemaining()) {
        switch (_state) {
          case CLOSED:
            break;

          case START:
            setState(State.CLOSED);
            _handler.earlyEOF();
            break;

          case END:
          case CLOSE:
            setState(State.CLOSED);
            break;

          case EOF_CONTENT:
            setState(State.CLOSED);
            return _handler.messageComplete();

          case CONTENT:
          case CHUNKED_CONTENT:
          case CHUNK_SIZE:
          case CHUNK_PARAMS:
          case CHUNK:
            setState(State.CLOSED);
            _handler.earlyEOF();
            break;

          default:
            if (DEBUG) log.debug("{} EOF in {}", this, _state);
            setState(State.CLOSED);
            _handler.badMessage(400, null);
            break;
        }
      }
    } catch (BadMessageException e) {
      BufferUtils.clear(buffer);

      Throwable cause = e.getCause();
      boolean stack =
          log.isDebugEnable()
              || (!(cause instanceof NumberFormatException)
                  && (cause instanceof RuntimeException || cause instanceof Error));

      if (stack)
        log.warn(
            "bad HTTP parsed: "
                + e._code
                + (e.getReason() != null ? " " + e.getReason() : "")
                + " for "
                + _handler,
            e);
      else
        log.warn(
            "bad HTTP parsed: "
                + e._code
                + (e.getReason() != null ? " " + e.getReason() : "")
                + " for "
                + _handler);
      setState(State.CLOSE);
      _handler.badMessage(e.getCode(), e.getReason());
    } catch (NumberFormatException | IllegalStateException e) {
      BufferUtils.clear(buffer);
      log.warn("parse exception: {} in {} for {}", e.toString(), _state, _handler);
      if (DEBUG) log.debug("parse exception", e);

      switch (_state) {
        case CLOSED:
          break;
        case CLOSE:
          _handler.earlyEOF();
          break;
        default:
          setState(State.CLOSE);
          _handler.badMessage(400, null);
      }
    } catch (Exception | Error e) {
      BufferUtils.clear(buffer);

      log.warn("parse exception: " + e.toString() + " for " + _handler, e);

      switch (_state) {
        case CLOSED:
          break;
        case CLOSE:
          _handler.earlyEOF();
          break;
        default:
          setState(State.CLOSE);
          _handler.badMessage(400, null);
      }
    }
    return false;
  }

  protected boolean parseContent(ByteBuffer buffer) {
    int remaining = buffer.remaining();
    if (remaining == 0 && _state == State.CONTENT) {
      long content = _contentLength - _contentPosition;
      if (content == 0) {
        setState(State.END);
        return _handler.messageComplete();
      }
    }

    // Handle _content
    byte ch;
    while (_state.ordinal() < State.END.ordinal() && remaining > 0) {
      switch (_state) {
        case EOF_CONTENT:
          _contentChunk = buffer.asReadOnlyBuffer();
          _contentPosition += remaining;
          buffer.position(buffer.position() + remaining);
          if (_handler.content(_contentChunk)) return true;
          break;

        case CONTENT:
          {
            long content = _contentLength - _contentPosition;
            if (content == 0) {
              setState(State.END);
              return _handler.messageComplete();
            } else {
              _contentChunk = buffer.asReadOnlyBuffer();

              // limit content by expected size
              if (remaining > content) {
                // We can cast remaining to an int as we know that it is
                // smaller than
                // or equal to length which is already an int.
                _contentChunk.limit(_contentChunk.position() + (int) content);
              }

              _contentPosition += _contentChunk.remaining();
              buffer.position(buffer.position() + _contentChunk.remaining());

              if (_handler.content(_contentChunk)) return true;

              if (_contentPosition == _contentLength) {
                setState(State.END);
                return _handler.messageComplete();
              }
            }
            break;
          }

        case CHUNKED_CONTENT:
          {
            ch = next(buffer);
            if (ch > HttpTokens.SPACE) {
              _chunkLength = TypeUtils.convertHexDigit(ch);
              _chunkPosition = 0;
              setState(State.CHUNK_SIZE);
            }

            break;
          }

        case CHUNK_SIZE:
          {
            ch = next(buffer);
            if (ch == 0) break;
            if (ch == HttpTokens.LINE_FEED) {
              if (_chunkLength == 0) setState(State.CHUNK_END);
              else setState(State.CHUNK);
            } else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
              setState(State.CHUNK_PARAMS);
            else _chunkLength = _chunkLength * 16 + TypeUtils.convertHexDigit(ch);
            break;
          }

        case CHUNK_PARAMS:
          {
            ch = next(buffer);
            if (ch == HttpTokens.LINE_FEED) {
              if (_chunkLength == 0) setState(State.CHUNK_END);
              else setState(State.CHUNK);
            }
            break;
          }

        case CHUNK:
          {
            int chunk = _chunkLength - _chunkPosition;
            if (chunk == 0) {
              setState(State.CHUNKED_CONTENT);
            } else {
              _contentChunk = buffer.asReadOnlyBuffer();

              if (remaining > chunk) _contentChunk.limit(_contentChunk.position() + chunk);
              chunk = _contentChunk.remaining();

              _contentPosition += chunk;
              _chunkPosition += chunk;
              buffer.position(buffer.position() + chunk);
              if (_handler.content(_contentChunk)) return true;
            }
            break;
          }

        case CHUNK_END:
          {
            // TODO handle chunk trailer
            ch = next(buffer);
            if (ch == 0) break;
            if (ch == HttpTokens.LINE_FEED) {
              setState(State.END);
              return _handler.messageComplete();
            }
            throw new IllegalCharacterException(_state, ch, buffer);
          }

        case CLOSED:
          {
            BufferUtils.clear(buffer);
            return false;
          }

        default:
          break;
      }

      remaining = buffer.remaining();
    }
    return false;
  }

  public boolean isAtEOF() {

    return _eof;
  }

  /** Signal that the associated data source is at EOF */
  public void atEOF() {
    if (DEBUG) log.debug("atEOF {}", this);
    _eof = true;
  }

  /** Request that the associated data source be closed */
  public void close() {
    if (DEBUG) log.debug("close {}", this);
    setState(State.CLOSE);
  }

  public void reset() {
    if (DEBUG) log.debug("reset {}", this);

    // reset state
    if (_state == State.CLOSE || _state == State.CLOSED) return;

    setState(State.START);
    _endOfContent = EndOfContent.UNKNOWN_CONTENT;
    _contentLength = -1;
    _contentPosition = 0;
    _responseStatus = 0;
    _contentChunk = null;
    _headerBytes = 0;
    _host = false;
  }

  protected void setState(State state) {
    if (DEBUG) log.debug("{} --> {}", _state, state);
    _state = state;
  }

  public Trie<HttpField> getFieldCache() {
    return _connectionFields;
  }

  /*	private String getProxyField(ByteBuffer buffer) {
  	_string.setLength(0);
  	_length = 0;

  	while (buffer.hasRemaining()) {
  		// process each character
  		byte ch = next(buffer);
  		if (ch <= ' ')
  			return _string.toString();
  		_string.append((char) ch);
  	}
  	throw new BadMessageException();
  }*/

  @Override
  public String toString() {
    return String.format(
        "%s{s=%s,%d of %d}", getClass().getSimpleName(), _state, _contentPosition, _contentLength);
  }

  /*
   * Event Handler interface These methods return true if the caller should
   * process the events so far received (eg return from parseNext and call
   * HttpChannel.handle). If multiple callbacks are called in sequence (eg
   * headerComplete then messageComplete) from the same point in the parsing
   * then it is sufficient for the caller to process the events only once.
   */
  public interface HttpHandler {
    public boolean content(ByteBuffer item);

    public boolean headerComplete();

    public boolean messageComplete();

    /**
     * This is the method called by parser when a HTTP Header name and value is found
     *
     * @param field The field parsed
     */
    public void parsedHeader(HttpField field);

    /**
     * Called to signal that an EOF was received unexpectedly during the parsing of a HTTP message
     */
    public void earlyEOF();

    /**
     * Called to signal that a bad HTTP message has been received.
     *
     * @param status The bad status to send
     * @param reason The textual reason for badness
     */
    public void badMessage(int status, String reason);

    /** @return the size in bytes of the per parser header cache */
    public int getHeaderCacheSize();
  }

  public interface RequestHandler extends HttpHandler {
    /**
     * This is the method called by parser when the HTTP request line is parsed
     *
     * @param method The method
     * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be
     *     changed until this parser is reset and reused.
     * @param version the http version in use
     * @return true if handling parsing should return.
     */
    public boolean startRequest(String method, String uri, HttpVersion version);
  }

  public interface ResponseHandler extends HttpHandler {
    /**
     * This is the method called by parser when the HTTP request line is parsed
     *
     * @param version the http version in use
     * @param status the response status
     * @param reason the response reason phrase
     * @return true if handling parsing should return
     */
    public boolean startResponse(HttpVersion version, int status, String reason);
  }

  @SuppressWarnings("serial")
  private static class IllegalCharacterException extends BadMessageException {
    private IllegalCharacterException(State state, byte ch, ByteBuffer buffer) {
      super(400, String.format("Illegal character 0x%X", ch));
      // Bug #460642 - don't reveal buffers to end user
      log.warn(
          String.format(
              "Illegal character 0x%X in state=%s for buffer %s",
              ch, state, BufferUtils.toDetailString(buffer)));
    }
  }
}