Skip to content

Commit 9646326

Browse files
Only send a 304 when the request was a GET (#957)
The 304 branch in `connectUnaryHandlerConn.Close` works out whether it's answering a GET by counting query parameters, and `Peer.Query` gets filled in for every method. So any POST that happens to carry a query string gets the conditional-GET treatment. Same handler returning the same `NewNotModifiedError`, one extra param on the URL: ``` POST /svc/Method -> 500 application/json {"code":"unknown","message":"not modified"} POST /svc/Method?foo=bar -> 304 no body ``` The message and any error details go with the body, and a connect-go client turns the second one into `unknown: 304 Not Modified`. Thats not hard to land on - a base URL with a param baked into it, or a proxy tacking on cache-busting ones. `mergeResponseHeader` a few lines down already checks `request.Method`, and the client side only honours a 304 for GETs, so Im fairly confident the method is what the query count was standing in for. The test bolts the query string on in a wrapper handler rather than through the client, beacuse a connect-go client won't put one on a POST by itself. `TestGetNotModified` still covers the GET side. Signed-off-by: Iain <iain1970@protonmail.com>
1 parent 2a02fea commit 9646326

2 files changed

Lines changed: 31 additions & 1 deletion

File tree

client_ext_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,25 @@ func TestGetNotModified(t *testing.T) {
202202
assert.Equal(t, http.MethodGet, unaryReq.HTTPMethod())
203203
}
204204

205+
func TestNotModifiedOnlyForGet(t *testing.T) {
206+
t.Parallel()
207+
208+
mux := http.NewServeMux()
209+
mux.Handle(pingv1connect.NewPingServiceHandler(&alwaysNotModifiedPingServer{}))
210+
server := memhttptest.NewServer(t, http.HandlerFunc(func(respWriter http.ResponseWriter, req *http.Request) {
211+
req.URL.RawQuery = "cache-buster=1"
212+
mux.ServeHTTP(respWriter, req)
213+
}))
214+
client := pingv1connect.NewPingServiceClient(server.Client(), server.URL())
215+
216+
_, err := client.Ping(t.Context(), connect.NewRequest(&pingv1.PingRequest{}))
217+
assert.NotNil(t, err)
218+
assert.Equal(t, connect.CodeOf(err), connect.CodeUnknown)
219+
var connectErr *connect.Error
220+
assert.True(t, errors.As(err, &connectErr))
221+
assert.Equal(t, connectErr.Message(), "not modified")
222+
}
223+
205224
func TestGetNoContentHeaders(t *testing.T) {
206225
t.Parallel()
207226

@@ -863,6 +882,17 @@ func (s *notModifiedPingServer) Ping(
863882
return resp, nil
864883
}
865884

885+
type alwaysNotModifiedPingServer struct {
886+
pingv1connect.UnimplementedPingServiceHandler
887+
}
888+
889+
func (*alwaysNotModifiedPingServer) Ping(
890+
_ context.Context,
891+
_ *connect.Request[pingv1.PingRequest],
892+
) (*connect.Response[pingv1.PingResponse], error) {
893+
return nil, connect.NewNotModifiedError(nil)
894+
}
895+
866896
type assertPeerInterceptor struct {
867897
tb testing.TB
868898
}

protocol_connect.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ func (hc *connectUnaryHandlerConn) Close(err error) error {
728728
hc.mergeResponseHeader(err)
729729
// If the handler received a GET request and the resource hasn't changed,
730730
// return a 304.
731-
if len(hc.peer.Query) > 0 && IsNotModifiedError(err) {
731+
if hc.request.Method == http.MethodGet && IsNotModifiedError(err) {
732732
hc.responseWriter.WriteHeader(http.StatusNotModified)
733733
return hc.request.Body.Close()
734734
}

0 commit comments

Comments
 (0)