Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions middleware/request_logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,50 @@ func TestRequestLoggerOK(t *testing.T) {
assert.Equal(t, expect, logAttrs)
}

func TestRequestLoggerAutoHandleHEADStatus(t *testing.T) {
for _, scope := range []string{"global", "group", "route"} {
for _, method := range []string{http.MethodGet, http.MethodHead} {
t.Run(scope+"/"+method, func(t *testing.T) {
e := echo.NewWithConfig(echo.Config{
Router: echo.NewRouter(echo.RouterConfig{AutoHandleHEAD: true}),
})
var loggedStatus int
logger := RequestLoggerWithConfig(RequestLoggerConfig{
LogStatus: true,
LogValuesFunc: func(c *echo.Context, values RequestLoggerValues) error {
loggedStatus = values.Status
return nil
},
})
handler := func(c *echo.Context) error {
return c.String(http.StatusNotFound, "missing")
}
switch scope {
case "global":
e.Use(logger)
e.GET("/test", handler)
case "group":
e.Group("", logger).GET("/test", handler)
case "route":
e.GET("/test", handler, logger)
}

rec := httptest.NewRecorder()
e.ServeHTTP(rec, httptest.NewRequest(method, "/test", nil))

assert.Equal(t, http.StatusNotFound, rec.Code)
assert.Equal(t, http.StatusNotFound, loggedStatus)
if method == http.MethodHead {
assert.Empty(t, rec.Body.String())
assert.Equal(t, "7", rec.Header().Get(echo.HeaderContentLength))
} else {
assert.Equal(t, "missing", rec.Body.String())
}
})
}
}
}

func TestRequestLoggerError(t *testing.T) {
old := slog.Default()
t.Cleanup(func() {
Expand Down
1 change: 1 addition & 0 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func (w *headResponseWriter) WriteHeader(code int) {
w.wroteStatus = true
w.status = code
if r, err := UnwrapResponse(w.rw); err == nil {
r.Status = code
r.Committed = true
}
}
Expand Down
10 changes: 8 additions & 2 deletions response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,21 @@ func TestWrapHeadHandler_HandlerPanic_OriginalWriterRestored(t *testing.T) {
assert.Equal(t, original, c.Response()) // defer must have run
}

func TestHeadResponseWriter_WriteHeader_SetsCommittedOnUnderlying(t *testing.T) {
func TestHeadResponseWriter_WriteHeader_SetsStatusAndCommittedOnUnderlying(t *testing.T) {
e := New()
rec := httptest.NewRecorder()
underlying := NewResponse(rec, e.Logger)
w := &headResponseWriter{rw: underlying}

w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusAccepted)

assert.True(t, underlying.Committed)
assert.Equal(t, http.StatusAccepted, underlying.Status)
assert.False(t, rec.Flushed)
assert.Equal(t, http.StatusOK, rec.Code)

w.WriteHeader(http.StatusBadRequest)
assert.Equal(t, http.StatusAccepted, underlying.Status)
}

func TestWrapHeadHandler_RouteLevelMiddlewareSeesTrueCommitted(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ type RouterConfig struct {
// the route, but the response body is suppressed in accordance with
// HTTP semantics. Headers (e.g., Content-Length, Content-Type) are
// preserved as if a GET request was made.
// The status written by the handler is available to route and group middleware even
// though sending the headers is delayed until the handler returns.
//
// Security considerations: the GET handler is fully executed for every
// HEAD request, including all side effects:
Expand Down
Loading