private CompletableFuture<AggregatedHttpFile> doAggregate(Executor fileReadExecutor,
                                                              @Nullable ByteBufAllocator alloc) {
        return readAttributes(fileReadExecutor).thenCompose(attrs -> {
            if (attrs == null) {
                return NON_EXISTENT_FILE_FUTURE;
            }

            if (attrs.length() > Integer.MAX_VALUE) {
                return UnmodifiableFuture.exceptionallyCompletedFuture(
                        new IOException("too large to aggregate: " + attrs.length() + " bytes"));
            }

            final T in;
            try {
                in = newStream();
            } catch (IOException e) {
                return Exceptions.throwUnsafely(e);
            }

            if (in == null) {
                return NON_EXISTENT_FILE_FUTURE;
            }

            boolean submitted = false;
            try {
                final CompletableFuture<AggregatedHttpFile> future = new EventLoopCheckingFuture<>();
                fileReadExecutor.execute(() -> {
                    final int length = (int) attrs.length();
                    final byte[] array;
                    final ByteBuf buf;
                    if (alloc != null) {
                        array = null;
                        buf = alloc.buffer(length);
                    } else {
                        array = new byte[length];
                        buf = Unpooled.wrappedBuffer(array).clear();
                    }

                    boolean success = false;
                    try {
                        for (int offset = 0;;) {
                            final int readBytes = read(in, buf);
                            if (readBytes < 0) {
                                // Should not reach here because we only read up to the end of the stream.
                                // If reached, it may mean the stream has been truncated.
                                throw new EOFException();
                            }

                            offset += readBytes;
                            if (offset == length) {
                                break;
                            }
                        }

                        final HttpData data = (array != null ? HttpData.wrap(array)
                                                             : HttpData.wrap(buf)).withEndOfStream();
                        final AggregatedHttpFileBuilder builder =
                                AggregatedHttpFile.builder(data, attrs.lastModifiedMillis())
                                                  .date(isDateEnabled())
                                                  .lastModified(isLastModifiedEnabled());

                        if (contentType() != null) {
                            builder.contentType(contentType());
                        }

                        final String etag = generateEntityTag(attrs);
                        if (etag != null) {
                            builder.entityTag((unused1, unused2) -> etag);
                        } else {
                            builder.entityTag(false);
                        }

                        builder.setHeaders(additionalHeaders());
                        success = future.complete(builder.build());
                    } catch (Exception e) {
                        future.completeExceptionally(e);
                    } finally {
                        close(in);
                        if (!success) {
                            buf.release();
                        }
                    }
                });

                submitted = true;
                return future;
            } finally {
                if (!submitted) {
                    close(in);
                }
            }
        });
    }
Creates a new instance.

@param contentType the {@link MediaType} of the file which will be used as the {@code "content-type"}
                   header value. {@code null} to disable setting the {@code "content-type"} header.
@param clock the {@link Clock} which provides the current date and time
@param dateEnabled whether to set the {@code "date"} header automatically
@param lastModifiedEnabled whether to add the {@code "last-modified"} header automatically
@param entityTagFunction the {@link BiFunction} that generates an entity tag from the file's attributes.
                         {@code null} to disable setting the {@code "etag"} header.
@param headers the additional headers to set
core/src/main/java/com/linecorp/armeria/server/file/StreamingHttpFile.java