gcsemu -- handle HEAD requests like GET - #415
Open
crocodele wants to merge 1 commit into
Open
Conversation
The JSON API answers HEAD on both the object metadata and media paths.
gcsemu only dispatched DELETE/GET/PATCH/POST/PUT and fell through to
405 for HEAD.
This breaks clients that probe for existence with HEAD. For example
google-cloud-php implements StorageObject::exists() as a HEAD against
/storage/v1/b/{bucket}/o/{object} as of 2.3.0; against the emulator it
gets a 405, which is not a NotFoundException, so exists() throws
instead of returning false.
No separate handler is needed. net/http discards handler body writes
for HEAD while preserving the status code and Content-Length, and the
metadata path already returns 404 for a missing object or bucket.
Co-Authored-By: Claude Opus 5 (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.
Problem
GcsEmu.Handlerdispatches onDELETE/GET/PATCH/POST/PUTand falls through to405 Method Not AllowedforHEAD. The real JSON API answersHEADon both the object metadata and media paths.This breaks clients that probe for existence with
HEAD. Concretely,google-cloud-phpchangedStorageObject::exists()from aGETto aHEADagainst/storage/v1/b/{bucket}/o/{object}in 2.3.0. Against gcsemu it gets a 405, which is not aNotFoundException, soexists()throws aServiceExceptioninstead of returningfalse. The exception message is empty, becausenet/httpdiscards the body of aHEADresponse — so the failure is fairly opaque from the client side.Fix
Widen the
GETcase tocase "GET", "HEAD":.No separate handler is needed:
net/httpeats handler body writes forHEAD(chunkWriter.Write) while preserving the status code andContent-Length, so the existingGEThandlers already produce correctHEADresponses.handleGcsMetadataRequestalready returns 404 for a missing object or bucket, which is the caseexists()actually depends on.Tests
Four cases added to the
testRawHttptable, appended at the end so the batch subtest'stcs[1:6]slice is unchanged:rawHeadMetarawHeadMeta-ObjectNotFoundrawHeadMeta-BucketNotFoundrawHeadObject(?alt=media)Content-Length == len(v1)All four fail on master with 405 and pass with the change.
Since
testRawHttpis shared, these also run against real GCS viaTestRealStore. I ran that withBUCKET_IDset against a real bucket and all four pass there too, so the emulator and production agree on every case asserted here.Two notes from that run:
HEADover HTTP/2 and sends noContent-Length, while the emulator (HTTP/1.1) does. I deliberately do not assertContent-Lengthon the metadata case for that reason. The media case asserts it, since real GCS does set it there.TestRealStore/List_BucketsandTestRealStore/CopyBasicsfail against real GCS, but they fail identically on unmodified master, so they are unrelated to this change. (List_Bucketsskips onbucket == ""rather thanproject == "", so it runs withoutPROJECT_ID;CopyBasicshits aFinalizedtimestamp mismatch atgcsemu_test.go:580.)make -C storage cipasses.Note
Low Risk
Single dispatch-line change in the test emulator with new HTTP tests; no production auth or data-path changes.
Overview
gcsemu now treats HTTP
HEADlikeGETon the JSON API paths (object metadata and?alt=mediadownloads), instead of returning 405 Method Not Allowed. That aligns the emulator with real GCS and fixes clients such as google-cloud-phpStorageObject::exists(), which probe existence withHEADand expect 404 when an object is missing.Existing GET handlers are reused;
net/httpstrips response bodies forHEADwhile keeping status codes and headers (e.g.Content-Lengthon media).Tests: four raw HTTP cases in
testRawHttpcover successful metadata/mediaHEAD, missing object, and missing bucket.Reviewed by Cursor Bugbot for commit 08c86bb. Bugbot is set up for automated code reviews on this repo. Configure here.