/**
   * Read bytes.
   *
   * @return If the filter does request length control, this value is significant; it should be the
   *     number of bytes consumed from the buffer, up until the end of the current request body, or
   *     the buffer length, whichever is greater. If the filter does not do request body length
   *     control, the returned value should be -1.
   */
  public int doRead(ByteChunk chunk, Request req) throws IOException {

    if (endChunk) return -1;

    if (needCRLFParse) {
      needCRLFParse = false;
      parseCRLF();
    }

    if (remaining <= 0) {
      if (!parseChunkHeader()) {
        throw new IOException("Invalid chunk header");
      }
      if (endChunk) {
        parseEndChunk();
        return -1;
      }
    }

    int result = 0;

    if (pos >= lastValid) {
      readBytes();
    }

    if (remaining > (lastValid - pos)) {
      result = lastValid - pos;
      remaining = remaining - result;
      chunk.setBytes(buf, pos, result);
      pos = lastValid;
    } else {
      result = remaining;
      chunk.setBytes(buf, pos, remaining);
      pos = pos + remaining;
      remaining = 0;
      // we need a CRLF
      if ((pos + 1) >= lastValid) {
        // if we call parseCRLF we overrun the buffer here
        // so we defer it to the next call BZ 11117
        needCRLFParse = true;
      } else {
        parseCRLF(); // parse the CRLF immediately
      }
    }

    return result;
  }
  /**
   * Customize the error pahe
   *
   * @param req The {@link Request} object
   * @param res The {@link Response} object
   * @throws Exception
   */
  protected void customizedErrorPage(Request req, Response res) throws Exception {

    /** With Grizzly, we just return a 404 with a simple error message. */
    res.setMessage("Not Found");
    res.setStatus(404);
    ByteBuffer bb = HtmlHelper.getErrorPage("Not Found", "HTTP/1.1 404 Not Found\r\n", "Grizzly");
    res.setContentLength(bb.limit());
    res.setContentType("text/html");
    res.flushHeaders();
    if (res.getChannel() != null) {
      res.getChannel().write(bb);
      req.setNote(14, "SkipAfterService");
    } else {
      byte b[] = new byte[bb.limit()];
      bb.get(b);
      ByteChunk chunk = new ByteChunk();
      chunk.setBytes(b, 0, b.length);
      res.doWrite(chunk);
    }
  }
  /**
   * Lookup a resource based on the request URI, and send it using send file.
   *
   * @param uri The request URI
   * @param req the {@link Request}
   * @param res the {@link Response}
   * @throws Exception
   */
  protected void service(String uri, Request req, final Response res) throws Exception {
    FileInputStream fis = null;
    try {
      initWebDir();

      boolean found = false;
      File resource = null;

      for (File webDir : fileFolders) {
        // local file
        resource = cache.get(uri);
        if (resource == null) {
          resource = new File(webDir, uri);
          if (resource.exists() && resource.isDirectory()) {
            final File f = new File(resource, "/index.html");
            if (f.exists()) {
              resource = f;
              found = true;
              break;
            }
          }
        }

        if (resource.isDirectory() || !resource.exists()) {
          found = false;
        } else {
          found = true;
          break;
        }
      }

      cache.put(uri, resource);
      if (!found) {
        if (logger.isLoggable(Level.FINE)) {
          logger.log(Level.FINE, "File not found  " + resource);
        }
        res.setStatus(404);
        if (commitErrorResponse) {
          customizedErrorPage(req, res);
        }
        return;
      }

      res.setStatus(200);
      String substr;
      int dot = uri.lastIndexOf(".");
      if (dot < 0) {
        substr = resource.toString();
        dot = substr.lastIndexOf(".");
      } else {
        substr = uri;
      }
      if (dot > 0) {
        String ext = substr.substring(dot + 1);
        String ct = MimeType.get(ext, defaultContentType);
        if (ct != null) {
          res.setContentType(ct);
        }
      } else {
        res.setContentType(defaultContentType);
      }

      long length = resource.length();
      res.setContentLengthLong(length);

      // Send the header, and flush the bytes as we will now move to use
      // send file.
      res.sendHeaders();

      if (req.method().toString().equalsIgnoreCase("HEAD")) {
        return;
      }

      fis = new FileInputStream(resource);
      OutputBuffer outputBuffer = res.getOutputBuffer();

      if (useSendFile
          && (outputBuffer instanceof FileOutputBuffer)
          && ((FileOutputBuffer) outputBuffer).isSupportFileSend()) {
        res.flush();

        long nWrite = 0;
        while (nWrite < length) {
          nWrite +=
              ((FileOutputBuffer) outputBuffer).sendFile(fis.getChannel(), nWrite, length - nWrite);
        }
      } else {
        byte b[] = new byte[8192];
        ByteChunk chunk = new ByteChunk();
        int rd;
        while ((rd = fis.read(b)) > 0) {
          chunk.setBytes(b, 0, rd);
          res.doWrite(chunk);
        }
      }
    } finally {
      if (fis != null) {
        try {
          fis.close();
        } catch (IOException ignored) {
        }
      }
    }
  }
 static {
   ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length());
 }