@Override
  protected void write(SelectionKey selectionKey) throws IOException {
    if (!checkTimeout()) return;

    if (outputStream.getBuffer().hasRemaining()) {
      // If we have data, write what we can now...
      int count = socketChannel.write(outputStream.getBuffer());

      if (logger.isTraceEnabled())
        logger.trace(
            "Wrote "
                + count
                + " bytes, remaining: "
                + outputStream.getBuffer().remaining()
                + " for "
                + socketChannel.socket());
    } else {
      if (logger.isTraceEnabled()) logger.trace("Wrote no bytes for " + socketChannel.socket());
    }

    // If there's more to write but we didn't write it, we'll take that to
    // mean that we're done here. We don't clear or reset anything. We leave
    // our buffer state where it is and try our luck next time.
    if (outputStream.getBuffer().hasRemaining()) return;

    resetStreams();

    // If we're not streaming writes, signal the Selector that we're
    // ready to read the next request.
    selectionKey.interestOps(SelectionKey.OP_READ);
  }
  @Override
  protected void read(SelectionKey selectionKey) throws IOException {
    if (!checkTimeout()) return;

    int count = 0;

    if ((count = socketChannel.read(inputStream.getBuffer())) == -1)
      throw new EOFException("EOF for " + socketChannel.socket());

    if (logger.isTraceEnabled()) traceInputBufferState("Read " + count + " bytes");

    if (count == 0) return;

    // Take note of the position after we read the bytes. We'll need it in
    // case of incomplete reads later on down the method.
    final int position = inputStream.getBuffer().position();

    // Flip the buffer, set our limit to the current position and then set
    // the position to 0 in preparation for reading in the RequestHandler.
    inputStream.getBuffer().flip();

    // uses a local variable to point to request to prevent racing condition
    // when atomicNullOutClientRequest() is called by another thread
    ClientRequest<?> request = clientRequest;

    if (request != null) {

      if (!request.isCompleteResponse(inputStream.getBuffer())) {
        // Ouch - we're missing some data for a full request, so handle
        // that and return.
        handleIncompleteRequest(position);
        return;
      }

      // At this point we have the full request (and it's not streaming),
      // so rewind the buffer for reading and execute the request.
      inputStream.getBuffer().rewind();

      if (logger.isTraceEnabled()) logger.trace("Starting read for " + socketChannel.socket());

      request.parseResponse(new DataInputStream(inputStream));

      // At this point we've completed a full stand-alone request. So
      // clear our input buffer and prepare for outputting back to the
      // client.
      if (logger.isTraceEnabled()) logger.trace("Finished read for " + socketChannel.socket());

      resetStreams();
      selectionKey.interestOps(0);
    }
    ClientRequest<?> originalRequest = completeClientRequest();

    if (originalRequest == null && logger.isEnabledFor(Level.WARN))
      logger.warn("No client associated with " + socketChannel.socket());
  }