@Override
    public long lastModifiedMillis() {
      final long newLastModifiedMillis = e.lastModifiedMillis();
      if (newLastModifiedMillis != cachedLastModifiedMillis) {
        cachedLastModifiedMillis = newLastModifiedMillis;
        destroyContent();
      }

      return newLastModifiedMillis;
    }
    @Override
    public synchronized ByteBuf readContent(ByteBufAllocator alloc) throws IOException {
      if (cachedContent == null) {
        final ByteBuf newContent = e.readContent(alloc);
        if (newContent.readableBytes() > maxCacheEntrySizeBytes) {
          // Do not cache if the content is too large.
          return newContent;
        }
        cachedContent = newContent;
      }

      return cachedContent.duplicate().retain();
    }
  @Override
  public void invoke(
      ServiceInvocationContext ctx, Executor blockingTaskExecutor, Promise<Object> promise)
      throws Exception {

    final HttpRequest req = ctx.originalRequest();
    if (req.method() != HttpMethod.GET) {
      respond(
          ctx,
          promise,
          HttpResponseStatus.METHOD_NOT_ALLOWED,
          0,
          ERROR_MIME_TYPE,
          Unpooled.wrappedBuffer(CONTENT_METHOD_NOT_ALLOWED));
      return;
    }

    final String path = normalizePath(ctx.mappedPath());
    if (path == null) {
      respond(
          ctx,
          promise,
          HttpResponseStatus.NOT_FOUND,
          0,
          ERROR_MIME_TYPE,
          Unpooled.wrappedBuffer(CONTENT_NOT_FOUND));
      return;
    }

    Entry entry = getEntry(path);
    long lastModifiedMillis;
    if ((lastModifiedMillis = entry.lastModifiedMillis()) == 0) {
      boolean found = false;
      if (path.charAt(path.length() - 1) == '/') {
        // Try index.html if it was a directory access.
        entry = getEntry(path + "index.html");
        if ((lastModifiedMillis = entry.lastModifiedMillis()) != 0) {
          found = true;
        }
      }

      if (!found) {
        respond(
            ctx,
            promise,
            HttpResponseStatus.NOT_FOUND,
            0,
            ERROR_MIME_TYPE,
            Unpooled.wrappedBuffer(CONTENT_NOT_FOUND));
        return;
      }
    }

    long ifModifiedSinceMillis = Long.MIN_VALUE;
    try {
      ifModifiedSinceMillis =
          req.headers().getTimeMillis(HttpHeaderNames.IF_MODIFIED_SINCE, Long.MIN_VALUE);
    } catch (Exception e) {
      // Ignore the ParseException, which is raised on malformed date.
      //noinspection ConstantConditions
      if (!(e instanceof ParseException)) {
        throw e;
      }
    }

    // HTTP-date does not have subsecond-precision; add 999ms to it.
    if (ifModifiedSinceMillis > Long.MAX_VALUE - 999) {
      ifModifiedSinceMillis = Long.MAX_VALUE;
    } else {
      ifModifiedSinceMillis += 999;
    }

    if (lastModifiedMillis < ifModifiedSinceMillis) {
      respond(
          ctx,
          promise,
          HttpResponseStatus.NOT_MODIFIED,
          lastModifiedMillis,
          entry.mimeType(),
          Unpooled.EMPTY_BUFFER);
      return;
    }

    respond(
        ctx,
        promise,
        HttpResponseStatus.OK,
        lastModifiedMillis,
        entry.mimeType(),
        entry.readContent(ctx.alloc()));
  }
 @Override
 public String toString() {
   return e.toString();
 }
 @Override
 public String mimeType() {
   return e.mimeType();
 }
 CachedEntry(Entry e, int maxCacheEntrySizeBytes) {
   this.e = e;
   this.maxCacheEntrySizeBytes = maxCacheEntrySizeBytes;
   cachedLastModifiedMillis = e.lastModifiedMillis();
 }