@@ -1233,6 +1233,159 @@ func TestDefaultHTTPErrorHandler_CommitedResponse(t *testing.T) {
12331233 assert .Equal (t , http .StatusOK , resp .Code )
12341234}
12351235
1236+ func TestAutoHeadRoute (t * testing.T ) {
1237+ tests := []struct {
1238+ name string
1239+ autoHead bool
1240+ method string
1241+ wantBody bool
1242+ wantCode int
1243+ wantCLen bool // expect Content-Length header
1244+ }{
1245+ {
1246+ name : "AutoHead disabled - HEAD returns 405" ,
1247+ autoHead : false ,
1248+ method : http .MethodHead ,
1249+ wantCode : http .StatusMethodNotAllowed ,
1250+ wantBody : false ,
1251+ },
1252+ {
1253+ name : "AutoHead enabled - HEAD returns 200 with Content-Length" ,
1254+ autoHead : true ,
1255+ method : http .MethodHead ,
1256+ wantCode : http .StatusOK ,
1257+ wantBody : false ,
1258+ wantCLen : true ,
1259+ },
1260+ {
1261+ name : "GET request works normally with AutoHead enabled" ,
1262+ autoHead : true ,
1263+ method : http .MethodGet ,
1264+ wantCode : http .StatusOK ,
1265+ wantBody : true ,
1266+ },
1267+ }
1268+
1269+ for _ , tt := range tests {
1270+ t .Run (tt .name , func (t * testing.T ) {
1271+ // Create Echo instance with AutoHead configuration
1272+ e := New ()
1273+ e .AutoHead = tt .autoHead
1274+
1275+ // Register a simple GET route
1276+ testBody := "Hello, World!"
1277+ e .GET ("/hello" , func (c * Context ) error {
1278+ return c .String (http .StatusOK , testBody )
1279+ })
1280+
1281+ // Create request and response
1282+ req := httptest .NewRequest (tt .method , "/hello" , nil )
1283+ rec := httptest .NewRecorder ()
1284+
1285+ // Serve the request
1286+ e .ServeHTTP (rec , req )
1287+
1288+ // Verify status code
1289+ if rec .Code != tt .wantCode {
1290+ t .Errorf ("expected status %d, got %d" , tt .wantCode , rec .Code )
1291+ }
1292+
1293+ // Verify response body
1294+ if tt .wantBody {
1295+ if rec .Body .String () != testBody {
1296+ t .Errorf ("expected body %q, got %q" , testBody , rec .Body .String ())
1297+ }
1298+ } else {
1299+ if rec .Body .String () != "" {
1300+ t .Errorf ("expected empty body for HEAD, got %q" , rec .Body .String ())
1301+ }
1302+ }
1303+
1304+ // Verify Content-Length header for HEAD
1305+ if tt .wantCLen && tt .method == http .MethodHead {
1306+ clen := rec .Header ().Get ("Content-Length" )
1307+ if clen == "" {
1308+ t .Error ("expected Content-Length header for HEAD request" )
1309+ }
1310+ }
1311+ })
1312+ }
1313+ }
1314+
1315+ func TestAutoHeadExplicitHeadTakesPrecedence (t * testing.T ) {
1316+ e := New ()
1317+ e .AutoHead = true
1318+
1319+ // Register explicit HEAD route FIRST with custom behavior
1320+ e .HEAD ("/api/users" , func (c * Context ) error {
1321+ c .Response ().Header ().Set ("X-Custom-Header" , "explicit-head" )
1322+ return c .NoContent (http .StatusOK )
1323+ })
1324+
1325+ // Then register GET route - AutoHead will try to add a HEAD route but fail silently
1326+ // since one already exists
1327+ e .GET ("/api/users" , func (c * Context ) error {
1328+ return c .JSON (http .StatusOK , map [string ]string {"name" : "John" })
1329+ })
1330+
1331+ // Test that the explicit HEAD route behavior is preserved
1332+ req := httptest .NewRequest (http .MethodHead , "/api/users" , nil )
1333+ rec := httptest .NewRecorder ()
1334+ e .ServeHTTP (rec , req )
1335+
1336+ if rec .Code != http .StatusOK {
1337+ t .Errorf ("expected status 200, got %d" , rec .Code )
1338+ }
1339+
1340+ if rec .Header ().Get ("X-Custom-Header" ) != "explicit-head" {
1341+ t .Error ("expected explicit HEAD route to be used" )
1342+ }
1343+
1344+ // Verify body is empty
1345+ if rec .Body .String () != "" {
1346+ t .Errorf ("expected empty body for HEAD, got %q" , rec .Body .String ())
1347+ }
1348+ }
1349+
1350+ func TestAutoHeadWithMiddleware (t * testing.T ) {
1351+ e := New ()
1352+ e .AutoHead = true
1353+
1354+ // Add request logger middleware
1355+ middlewareExecuted := false
1356+ e .Use (func (next HandlerFunc ) HandlerFunc {
1357+ return func (c * Context ) error {
1358+ middlewareExecuted = true
1359+ c .Response ().Header ().Set ("X-Middleware" , "executed" )
1360+ return next (c )
1361+ }
1362+ })
1363+
1364+ // Register GET route
1365+ e .GET ("/test" , func (c * Context ) error {
1366+ return c .String (http .StatusOK , "test response" )
1367+ })
1368+
1369+ // Test HEAD request goes through middleware
1370+ req := httptest .NewRequest (http .MethodHead , "/test" , nil )
1371+ rec := httptest .NewRecorder ()
1372+
1373+ middlewareExecuted = false
1374+ e .ServeHTTP (rec , req )
1375+
1376+ if ! middlewareExecuted {
1377+ t .Error ("middleware should execute for automatic HEAD route" )
1378+ }
1379+
1380+ if rec .Header ().Get ("X-Middleware" ) != "executed" {
1381+ t .Error ("middleware header not set" )
1382+ }
1383+
1384+ if rec .Body .String () != "" {
1385+ t .Errorf ("expected empty body for HEAD, got %q" , rec .Body .String ())
1386+ }
1387+ }
1388+
12361389func benchmarkEchoRoutes (b * testing.B , routes []testRoute ) {
12371390 e := New ()
12381391 req := httptest .NewRequest (http .MethodGet , "/" , nil )
@@ -1278,3 +1431,23 @@ func BenchmarkEchoGitHubAPIMisses(b *testing.B) {
12781431func BenchmarkEchoParseAPI (b * testing.B ) {
12791432 benchmarkEchoRoutes (b , parseAPI )
12801433}
1434+
1435+ func BenchmarkAutoHeadRoute (b * testing.B ) {
1436+ e := New ()
1437+ e .AutoHead = true
1438+
1439+ e .GET ("/bench" , func (c * Context ) error {
1440+ return c .String (http .StatusOK , "benchmark response body" )
1441+ })
1442+
1443+ req := httptest .NewRequest (http .MethodHead , "/bench" , nil )
1444+ rec := httptest .NewRecorder ()
1445+
1446+ b .ReportAllocs ()
1447+ b .ResetTimer ()
1448+
1449+ for i := 0 ; i < b .N ; i ++ {
1450+ rec .Body .Reset ()
1451+ e .ServeHTTP (rec , req )
1452+ }
1453+ }
0 commit comments