/** Destroy the connection. called by handle when handleNext returns false. */
  protected void destroy() {
    try {
      close();
    } catch (IOException e) {
      LogSupport.ignore(log, e);
    } catch (Exception e) {
      log.warn(LogSupport.EXCEPTION, e);
    }

    // Destroy request and response
    if (_request != null) _request.destroy();
    if (_response != null) _response.destroy();
    if (_inputStream != null) _inputStream.destroy();
    if (_outputStream != null) _outputStream.destroy();
    _inputStream = null;
    _outputStream = null;
    _request = null;
    _response = null;
    _handlingThread = null;

    if (_statsOn) {
      _tmpTime = System.currentTimeMillis();
      if (_reqTime > 0) _httpServer.statsEndRequest(_tmpTime - _reqTime, false);
      _httpServer.statsCloseConnection(_tmpTime - _openTime, _requests);
    }
  }
  /**
   * Handle next request off the connection. The service(request,response) method is called by
   * handle to service each request received on the connection. If the thread is a PoolThread, the
   * thread is set as inactive when waiting for a request.
   *
   * <p>If a HttpTunnel has been set on this connection, it's handle method is called and when that
   * completes, false is return from this method.
   *
   * <p>The Connection is set as a ThreadLocal of the calling thread and is available via the
   * getHttpConnection() method.
   *
   * @return true if the connection is still open and may provide more requests.
   */
  public boolean handleNext() {
    // Handle a HTTP tunnel
    if (_tunnel != null) {
      if (log.isDebugEnabled()) log.debug("Tunnel: " + _tunnel);
      _outputStream.resetObservers();
      _tunnel.handle(_inputStream.getInputStream(), _outputStream.getOutputStream());
      return false;
    }

    // Normal handling.
    HttpContext context = null;
    boolean stats = false;
    try {
      // Assume the connection is not persistent,
      // unless told otherwise.
      _persistent = false;
      _close = false;
      _keepAlive = false;
      _firstWrite = false;
      _completing = false;
      _dotVersion = 0;

      // Read requests
      readRequest();
      if (_listener == null || !_listener.isStarted()) {
        // dead connection
        _response.destroy();
        _response = null;
        _persistent = false;
        return false;
      }

      _listener.customizeRequest(this, _request);
      if (_request.getState() != HttpMessage.__MSG_RECEIVED)
        throw new HttpException(HttpResponse.__400_Bad_Request);

      // We have a valid request!
      statsRequestStart();
      stats = true;

      // Pick response version, we assume that _request.getVersion() == 1
      _dotVersion = _request.getDotVersion();

      if (_dotVersion > 1) {
        _dotVersion = 1;
      }

      // Common fields on the response
      _response.setVersion(HttpMessage.__HTTP_1_1);
      _response.setField(HttpFields.__Date, _request.getTimeStampStr());
      if (!Version.isParanoid()) _response.setField(HttpFields.__Server, Version.getDetail());

      // Handle Connection header field
      Enumeration connectionValues =
          _request.getFieldValues(HttpFields.__Connection, HttpFields.__separators);
      if (connectionValues != null) {
        while (connectionValues.hasMoreElements()) {
          String token = connectionValues.nextElement().toString();
          // handle close token
          if (token.equalsIgnoreCase(HttpFields.__Close)) {
            _close = true;
            _response.setField(HttpFields.__Connection, HttpFields.__Close);
          } else if (token.equalsIgnoreCase(HttpFields.__KeepAlive) && _dotVersion == 0)
            _keepAlive = true;

          // Remove headers for HTTP/1.0 requests
          if (_dotVersion == 0) _request.forceRemoveField(token);
        }
      }

      // Handle version specifics
      if (_dotVersion == 1) verifyHTTP_1_1();
      else if (_dotVersion == 0) verifyHTTP_1_0();
      else if (_dotVersion != -1)
        throw new HttpException(HttpResponse.__505_HTTP_Version_Not_Supported);

      if (log.isDebugEnabled()) log.debug("REQUEST from " + _listener + ":\n" + _request);

      // handle HttpListener handlers
      if (!_request.isHandled() && _listener.getHttpHandler() != null)
        _listener.getHttpHandler().handle("", null, _request, _response);

      // service the request
      if (!_request.isHandled()) context = service(_request, _response);
    } catch (HttpException e) {
      exception(e);
    } catch (IOException e) {
      if (_request.getState() != HttpMessage.__MSG_RECEIVED) {
        if (log.isDebugEnabled()) {
          if (log.isTraceEnabled()) log.trace(LogSupport.EXCEPTION, e);
          else if (log.isDebugEnabled()) log.debug(e.toString());
        }
        _response.destroy();
        _response = null;
      } else exception(e);
    } catch (Exception e) {
      exception(e);
    } catch (Error e) {
      exception(e);
    } finally {
      int bytes_written = 0;
      int content_length =
          _response == null ? -1 : _response.getIntField(HttpFields.__ContentLength);

      // Complete the request
      if (_persistent) {
        boolean no_continue_sent = false;
        try {
          if (_inputStream.getExpectContinues() != null) {
            _inputStream.setExpectContinues(null);
            no_continue_sent = true;
          } else {
            int remaining = _inputStream.getContentLength();
            if (remaining != 0)
              // Read remaining input
              while (_inputStream.skip(4096) > 0 || _inputStream.read() >= 0) ;
          }
        } catch (IOException e) {
          if (_inputStream.getContentLength() > 0) _inputStream.setContentLength(0);
          _persistent = false;
          LogSupport.ignore(log, e);
          exception(new HttpException(HttpResponse.__400_Bad_Request, "Missing Content"));
        }

        // Check for no more content
        if (!no_continue_sent && _inputStream.getContentLength() > 0) {
          _inputStream.setContentLength(0);
          _persistent = false;
          exception(new HttpException(HttpResponse.__400_Bad_Request, "Missing Content"));
        }

        // Commit the response
        try {
          _outputStream.close();
          bytes_written = _outputStream.getBytesWritten();
          _outputStream.resetStream();
          _outputStream.addObserver(this);
          _inputStream.resetStream();
        } catch (IOException e) {
          exception(e);
        }
      } else if (_response != null) // There was a request
      {
        // half hearted attempt to eat any remaining input
        try {
          if (_inputStream.getContentLength() > 0)
            while (_inputStream.skip(4096) > 0 || _inputStream.read() >= 0) ;
          _inputStream.resetStream();
        } catch (IOException e) {
          LogSupport.ignore(log, e);
        }

        // commit non persistent
        try {
          _outputStream.flush();
          _response.commit();
          bytes_written = _outputStream.getBytesWritten();
          _outputStream.close();
          _outputStream.resetStream();
        } catch (IOException e) {
          exception(e);
        }
      }

      // Check response length
      if (_response != null) {
        if (log.isDebugEnabled()) log.debug("RESPONSE:\n" + _response);
        if (_persistent
            && content_length >= 0
            && bytes_written > 0
            && content_length != bytes_written) {
          log.warn(
              "Invalid length: Content-Length="
                  + content_length
                  + " written="
                  + bytes_written
                  + " for "
                  + _request.getRequestURL());
          _persistent = false;
          try {
            _outputStream.close();
          } catch (IOException e) {
            log.warn(LogSupport.EXCEPTION, e);
          }
        }
      }

      // stats & logging
      if (stats) statsRequestEnd();
      if (context != null) context.log(_request, _response, bytes_written);
    }

    return (_tunnel != null) || _persistent;
  }