Skip to content

Support for pre-compressed and ETag in download#222

Merged
mathieucarbou merged 4 commits into
ESP32Async:mainfrom
JosePineiro:feature/etag-caching-downloads
Jul 10, 2025
Merged

Support for pre-compressed and ETag in download#222
mathieucarbou merged 4 commits into
ESP32Async:mainfrom
JosePineiro:feature/etag-caching-downloads

Conversation

@JosePineiro

Copy link
Copy Markdown

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
Copy Markdown
Author

Implemented as #219

Copilot AI left a comment

Copy link
Copy Markdown

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;

Comment thread src/AsyncWebServerRequest.cpp Outdated
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;

Copilot AI Jul 9, 2025

Copy link

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.
Comment thread src/AsyncWebServerRequest.cpp Outdated
Comment thread src/WebResponses.cpp
Comment thread src/AsyncWebServerRequest.cpp
@mathieucarbou

Copy link
Copy Markdown
Member

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

JosePineiro and others added 2 commits July 9, 2025 18:38
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@JosePineiro

Copy link
Copy Markdown
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.

3 participants