8000 feat: add HTTP-date parsing, ETag utilities, and conditional request middleware by kawasima · Pull Request #118 · enkan/enkan · GitHub
[go: up one dir, main page]

Skip to content

feat: add HTTP-date parsing, ETag utilities, and conditional request middleware - #118

Merged
kawasima merged 3 commits into
developfrom
feature/conditional-requests
Mar 28, 2026
Merged

kawasima merged 3 commits into
developfrom
feature/conditional-requests

Conversation

@kawasima
Copy link
Copy Markdown
Collaborator

Summary

Three interconnected additions for RFC 9110 conditional request support:

1. HttpDateFormat.parse() (Issue #106)

  • Parses all three HTTP-date formats: IMF-fixdate, RFC 850, asctime
  • Ported from kotowari-restful's HttpDateParser to avoid duplication
  • Returns Optional<Instant> — empty for null/blank/malformed/non-GMT input

2. ETagUtils (new utility class)

  • generateWeakETag(body, contentEncoding) — SHA-256 based, Content-Encoding aware (§8.8.3.3)
  • strongMatch / weakMatch — comparison per §8.8.3.2
  • matchesHeader — parses comma-separated ETag lists and * wildcard
  • Designed for reuse by kotowari-restful's ResourceEngine

3. ConditionalMiddleware (Issue #105)

  • Evaluates preconditions in RFC 9110 §13.2.2 precedence order:
    1. If-Match → 412 (strong comparison)
    2. If-Unmodified-Since → 412 (when no If-Match)
    3. If-None-Match → 304 GET/HEAD, 412 others (weak comparison)
    4. If-Modified-Since → 304 (GET/HEAD, when no If-None-Match)
  • Auto-generates weak ETags for String bodies
  • 304 preserves only §15.4.5 required headers (ETag, Date, Vary, Cache-Control, Expires, Content-Location)
  • Skips evaluation for CONNECT/OPTIONS/TRACE (§13.2.1) and non-2xx responses

Closes #105
Closes #106

Test plan

  • HttpDateFormat: 8 parse tests (3 formats + null/blank/malformed/non-GMT/invalid-date)
  • ETagUtils: 16 tests (generation, strong/weak comparison, header matching, edge cases)
  • ConditionalMiddleware: 21 tests covering all RFC §13.2.2 steps, edge cases, and 304 header requirements
  • All enkan-web tests pass (45 total including existing)

🤖 Generated with Claude Code

kawasima and others added 3 commits March 28, 2026 23:06
…middleware

HttpDateFormat: add parse(String) → Optional<Instant> supporting
IMF-fixdate, RFC 850, and asctime formats (RFC 9110 §5.6.7). Ported
from kotowari-restful's HttpDateParser to avoid duplication.

ETagUtils: new utility class for ETag generation (weak, SHA-256 based,
Content-Encoding aware per §8.8.3.3) and comparison (strong/weak per
§8.8.3.2). Shared foundation for both ConditionalMiddleware and
kotowari-restful's ResourceEngine.

ConditionalMiddleware: evaluates If-Match, If-Unmodified-Since,
If-None-Match, and If-Modified-Since in the precedence order defined
by RFC 9110 §13.2.2. Returns 304 Not Modified or 412 Precondition
Failed as appropriate. Auto-generates weak ETags for String bodies.
304 responses preserve only the headers required by §15.4.5.

Closes #105
Closes #106

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HttpDateFormat:
- Whitespace stripping test
- RFC 850 two-digit year boundary (49→2049, 50→1950)

ETagUtils:
- matchesHeader with wildcard "*" and null ETag
- matchesHeader with whitespace around tags

ConditionalMiddleware:
- HEAD method returns 304 for If-None-Match and If-Modified-Since
- Equal timestamps: If-Modified-Since returns 304 (§13.1.3)
- Equal timestamps: If-Unmodified-Since passes through (§13.1.4)
- If-Match "*" wildcard passes through
- Malformed If-Modified-Since date is ignored (§13.1.3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ETagUtils:
- Use ThreadLocal<MessageDigest> to avoid per-call getInstance overhead
- Cache HexFormat instance as static field
- Replace String.split in matchesHeader with indexOf loop to avoid
  array allocation

ConditionalMiddleware:
- Extract isModifiedAfter/isNotModifiedSince/parseLastModified helpers
  to deduplicate date comparison logic between Step 2 and Step 4
- Fix bug: !isModifiedAfter returned true for unparseable dates,
  causing malformed If-Modified-Since to incorrectly return 304
- Remove unused BeanBuilder import, use direct setStatus calls

Test:
- Fix charsetNegotiatedWhenConfigured: explicitly set q values to
  avoid tie with iso-8859-1's default q=1.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kawasima
kawasima merged commit 7fb2351 into develop Mar 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0