Skip to content

Commit 58f8903

Browse files
committed
AutoHandleHEAD enables automatic handling of HTTP HEAD requests by falling back to the corresponding GET route
1 parent 6bab72d commit 58f8903

5 files changed

Lines changed: 717 additions & 216 deletions

File tree

echo.go

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ import (
5454
"os"
5555
"os/signal"
5656
"path/filepath"
57-
"strconv"
5857
"strings"
5958
"sync"
6059
"sync/atomic"
@@ -101,7 +100,6 @@ type Echo struct {
101100

102101
// formParseMaxMemory is passed to Context for multipart form parsing (See http.Request.ParseMultipartForm)
103102
formParseMaxMemory int64
104-
AutoHead bool
105103
}
106104

107105
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
@@ -290,11 +288,6 @@ type Config struct {
290288
// FormParseMaxMemory is default value for memory limit that is used
291289
// when parsing multipart forms (See (*http.Request).ParseMultipartForm)
292290
FormParseMaxMemory int64
293-
294-
// AutoHead enables automatic registration of HEAD routes for GET routes.
295-
// When enabled, a HEAD request to a GET-only path will be handled automatically
296-
// using the same handler as GET, with the response body suppressed.
297-
AutoHead bool
298291
}
299292

300293
// NewWithConfig creates an instance of Echo with given configuration.
@@ -333,9 +326,6 @@ func NewWithConfig(config Config) *Echo {
333326
if config.FormParseMaxMemory > 0 {
334327
e.formParseMaxMemory = config.FormParseMaxMemory
335328
}
336-
if config.AutoHead {
337-
e.AutoHead = config.AutoHead
338-
}
339329
return e
340330
}
341331

@@ -431,67 +421,6 @@ func DefaultHTTPErrorHandler(exposeError bool) HTTPErrorHandler {
431421
}
432422
}
433423

434-
// headResponseWriter wraps an http.ResponseWriter and suppresses the response body
435-
// while preserving headers and status code. Used for automatic HEAD route handling.
436-
// It counts the bytes that would have been written so we can set Content-Length accurately.
437-
type headResponseWriter struct {
438-
http.ResponseWriter
439-
bytesWritten int64
440-
statusCode int
441-
wroteHeader bool
442-
}
443-
444-
// Write intercepts writes to the response body and counts bytes without actually writing them.
445-
func (hw *headResponseWriter) Write(b []byte) (int, error) {
446-
if !hw.wroteHeader {
447-
hw.statusCode = http.StatusOK
448-
hw.wroteHeader = true
449-
}
450-
hw.bytesWritten += int64(len(b))
451-
// Return success without actually writing the body for HEAD requests
452-
return len(b), nil
453-
}
454-
455-
// WriteHeader intercepts the status code but still writes it to the underlying ResponseWriter.
456-
func (hw *headResponseWriter) WriteHeader(statusCode int) {
457-
if !hw.wroteHeader {
458-
hw.statusCode = statusCode
459-
hw.wroteHeader = true
460-
hw.ResponseWriter.WriteHeader(statusCode)
461-
}
462-
}
463-
464-
// Unwrap returns the underlying http.ResponseWriter for compatibility with echo.Response unwrapping.
465-
func (hw *headResponseWriter) Unwrap() http.ResponseWriter {
466-
return hw.ResponseWriter
467-
}
468-
469-
func wrapHeadHandler(handler HandlerFunc) HandlerFunc {
470-
return func(c *Context) error {
471-
if c.Request().Method != http.MethodHead {
472-
return handler(c)
473-
}
474-
originalWriter := c.Response()
475-
headWriter := &headResponseWriter{ResponseWriter: originalWriter}
476-
477-
c.SetResponse(headWriter)
478-
defer func() {
479-
c.SetResponse(originalWriter)
480-
}()
481-
err := handler(c)
482-
483-
if headWriter.bytesWritten > 0 {
484-
originalWriter.Header().Set("Content-Length", strconv.FormatInt(headWriter.bytesWritten, 10))
485-
}
486-
487-
if !headWriter.wroteHeader && headWriter.statusCode > 0 {
488-
originalWriter.WriteHeader(headWriter.statusCode)
489-
}
490-
491-
return err
492-
}
493-
}
494-
495424
// Pre adds middleware to the chain which is run before router tries to find matching route.
496425
// Meaning middleware is executed even for 404 (not found) cases.
497426
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
@@ -705,20 +634,6 @@ func (e *Echo) add(route Route) (RouteInfo, error) {
705634
if paramsCount > e.contextPathParamAllocSize.Load() {
706635
e.contextPathParamAllocSize.Store(paramsCount)
707636
}
708-
709-
// Auto-register HEAD route for GET if AutoHead is enabled
710-
if e.AutoHead && route.Method == http.MethodGet {
711-
headRoute := Route{
712-
Method: http.MethodHead,
713-
Path: route.Path,
714-
Handler: wrapHeadHandler(route.Handler),
715-
Middlewares: route.Middlewares,
716-
Name: route.Name,
717-
}
718-
// Attempt to add HEAD route, but ignore errors if an explicit HEAD route already exists
719-
_, _ = e.router.Add(headRoute)
720-
}
721-
722637
return ri, nil
723638
}
724639

0 commit comments

Comments
 (0)