Repository navigation
feat: add HTTP-date parsing, ETag utilities, and conditional request middleware - #118
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three interconnected additions for RFC 9110 conditional request support:
1.
HttpDateFormat.parse()(Issue #106)kotowari-restful'sHttpDateParserto avoid duplicationOptional<Instant>— empty for null/blank/malformed/non-GMT input2.
ETagUtils(new utility class)generateWeakETag(body, contentEncoding)— SHA-256 based, Content-Encoding aware (§8.8.3.3)strongMatch/weakMatch— comparison per §8.8.3.2matchesHeader— parses comma-separated ETag lists and*wildcardkotowari-restful'sResourceEngine3.
ConditionalMiddleware(Issue #105)Closes #105
Closes #106
Test plan
🤖 Generated with Claude Code