Skip to content

Support for pre-compressed and ETag in download #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

JosePineiro
Copy link

When downloading file request:

  1. Gzipped file serving:
    • Automatically detects and serves pre-compressed .gz files when uncompressed originals are missing
    • Properly sets Content-Encoding: gzip headers
    • Implements If-None-Match header comparison for 304 (Not Modified) responses (RFC 7232)
    • Implements ETag header using CRC-32 from gzip trailer (bytes 4-7 from end)
    • Optimize for speed Changes affect:
      void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) AsyncWebServerResponse *
      AsyncWebServerRequest::beginResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
      AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
      : AsyncAbstractResponse(callback)

When downloading file request:
1. **Gzipped file serving**:
   - Automatically detects and serves pre-compressed `.gz` files when uncompressed originals are missing
   - Properly sets `Content-Encoding: gzip` headers
   - Implements `If-None-Match` header comparison for 304 (Not Modified) responses (RFC 7232)
   - Implements `ETag` header using CRC-32 from gzip trailer (bytes 4-7 from end)
   - Optimize for speed
Changes affect:
void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) 
AsyncWebServerResponse *
  AsyncWebServerRequest::beginResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) 
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
  : AsyncAbstractResponse(callback)
@JosePineiro
Copy link
Author

Implemented as #219

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances file downloads by automatically serving pre-compressed .gz files when originals are missing, adding gzip headers, and implementing CRC32-based ETag handling for 304 responses.

  • Fallback to gzip-compressed file in AsyncFileResponse, setting Content-Encoding, ETag, and Cache-Control.
  • Refactor AsyncWebServerRequest::send() to check uncompressed first, handle compressed files, and respond 304 Not Modified when ETags match.
  • Simplify Content-Disposition logic for inline vs. download modes.

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/WebResponses.cpp Updated AsyncFileResponse constructor to support gzip fallback, ETag generation, and download/inline headers.
src/AsyncWebServerRequest.cpp Refactored send() to pre-check uncompressed files, open and validate .gz files, and implement ETag-based 304 logic.
Comments suppressed due to low confidence (1)

src/WebResponses.cpp:716

  • No tests cover the scenario where a corrupted or invalid gzip file triggers a 404 response; add unit tests to validate this path.
      _code = 404;

if (file && file.size() >= 18) { // 18 is the minimum size of valid gzip file
file.seek(file.size() - 8);
// Handle compressed version
const String gzPath = path + asyncsrv::T__gz;
Copy link
Preview

Copilot AI Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download flag is ignored when serving compressed files. You should skip serving the .gz version if download is true to respect the download mode.

Suggested change
const String gzPath = path + asyncsrv::T__gz;
const String gzPath = path + asyncsrv::T__gz;
// Skip serving compressed file if download is true
if (download) {
send(404);
return;
}

Copilot uses AI. Check for mistakes.

@mathieucarbou
Copy link
Member

Thanks @JosePineiro ! Let's get this PR merged, then I will cut a new release.

@JosePineiro
Copy link
Author

Thanks @JosePineiro ! Let's get this PR merged, then I will cut a new release.

Tomorrow I'll submit another PR with a speed improvement for the _setContentTypeFromPath function.
The improvement is between 2 and 5 times.
You may want to wait before releasing a new release.

@mathieucarbou mathieucarbou merged commit 7d1afa3 into ESP32Async:main Jul 10, 2025
33 checks passed
@JosePineiro JosePineiro deleted the feature/etag-caching-downloads branch July 10, 2025 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants