This document describes the If-Match header support that has been added to all update endpoints in the AEPC bookstore example.
The If-Match header provides optimistic concurrency control for update operations. When provided, the server validates that the current resource matches the expected ETag before performing the update. If the ETags don't match, the update is rejected with a 412 Precondition Failed status.
The If-Match header is supported for all update operations:
UpdateBookUpdatePublisherUpdateStoreUpdateItem
ETags are generated by:
- Serializing the protobuf message using
proto.Marshal - Computing an MD5 hash of the serialized data
- Encoding the hash as a hexadecimal string
- Wrapping in quotes (e.g.,
"a1b2c3d4...")
The grpc-gateway is configured to forward the If-Match HTTP header to gRPC metadata:
- HTTP header:
If-Match: "etag-value" - gRPC metadata:
grpcgateway-if-match: "etag-value"
# Get a resource to obtain its current state
GET /publishers/1/books/1
# Update with If-Match header
PATCH /publishers/1/books/1
If-Match: "current-etag-value"
Content-Type: application/json
{
"book": {
"price": 30,
"published": true,
"edition": 2
}
}200 OK: Update successful with valid If-Match header412 Precondition Failed: If-Match header value doesn't match current resource ETag404 Not Found: Resource doesn't exist- No If-Match header: Update proceeds normally (backwards compatible)
The If-Match header is automatically extracted from gRPC metadata by the service methods. No additional client configuration is required when using grpc-gateway.
-
ETag Generation (
types.go):GenerateETag(msg proto.Message): Creates ETag from protobuf messageValidateETag(provided, current string): Compares ETags with quote handling
-
Header Extraction (
service.go):extractIfMatchHeader(ctx context.Context): Extracts If-Match from gRPC metadata
-
Gateway Configuration (
gateway.go):- Custom header matcher forwards
If-Matchheader togrpcgateway-if-matchmetadata
- Custom header matcher forwards
-
Update Methods: All update methods now:
- Extract If-Match header from context
- Fetch current resource if header is present
- Generate ETag for current resource
- Validate provided ETag against current ETag
- Reject with
FailedPreconditionif validation fails - Proceed with normal update logic if validation passes
- Missing Resource: Returns
NotFoundwhen trying to validate ETag for non-existent resource - ETag Mismatch: Returns
FailedPreconditionwhen If-Match header doesn't match current ETag - ETag Generation Failure: Returns
Internalif ETag generation fails - No If-Match Header: Proceeds normally for backwards compatibility
The implementation includes comprehensive unit tests:
TestUpdateBookWithIfMatchHeader: Tests successful and failed ETag validationTestUpdatePublisherWithIfMatchHeader: Tests publisher-specific ETag handlingTestETagGeneration: Tests ETag generation and validation logic
Tests verify:
- ✅ Updates succeed with correct If-Match header
- ✅ Updates fail with incorrect If-Match header (412 status)
- ✅ Updates succeed without If-Match header (backwards compatibility)
- ✅ Updates fail for non-existent resources (404 status)
- ✅ ETag generation produces consistent results for identical content
- ✅ ETag generation produces different results for different content
- ✅ ETag validation handles quoted and unquoted ETags correctly
An integration test script is provided (test_if_match_integration.sh) that demonstrates:
- End-to-end HTTP API functionality
- Proper error codes for failed preconditions
- Complete workflow from resource creation to ETag-validated updates
- ETags are deterministic based on resource content
- ETags do not expose sensitive information (they are content hashes)
- No additional authentication is required beyond existing API security
- ETag validation happens before database operations, preventing unnecessary writes
- ETag generation requires serializing and hashing the resource
- Validation requires fetching the current resource before updating
- Impact is minimal for typical update operations
- No additional database operations beyond the standard Get/Update pattern
- ETags are computed on-demand and not stored in the database
The If-Match header support is fully backwards compatible:
- Existing clients without If-Match headers continue to work unchanged
- No changes to existing API contracts or response formats
- No new required fields in resources themselves
- All functionality is implemented via HTTP headers and gRPC sidechannel metadata