Skip to content

RouteNotFound handler does not falls back to root one #2485

Open
@endorama

Description

@endorama

Issue Description

I think I found a bug in how RouteNotFound handler is propagated to route groups.

When using groups with a dedicated middleware there is no fallback to the root RouteNotFound handler, while that happens when no middleware is specified.

Checklist

  • Dependencies installed (not sure what this means)
  • No typos
  • Searched existing issues and docs

Expected behaviour

The root RouteNotFound handler is used.

Actual behaviour

An internal(?) handler is used

Steps to reproduce

Put the code below in a test file, run go test ./...

Working code to debug

This is a test file that reproduce the issue.

package main_test

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/require"
)

func server() http.Handler {
	e := echo.New()
	e.HideBanner = true
	e.HidePort = true

	// expect this handler is used as fallback unless a more specific is present
	e.RouteNotFound("/*", missingRouteHandler)

	// addig a middleware to the group overrides the RouteNotFound handler
	v0 := e.Group("/v0", func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			return next(c)
		}
	})
	// adding this would make it work
	// v0.RouteNotFound("/*", missingRouteHandler)
	v0.POST("/resource", handler)

	// the same does not happen when no middleware is set
	v1 := e.Group("/v1")
	v1.POST("/resource", handler)

	return e
}

func missingRouteHandler(c echo.Context) error {
	return c.NoContent(http.StatusNotFound)
}

func handler(c echo.Context) error {
	return nil
}

func TestMissing(t *testing.T) {
	srv := httptest.NewServer(server())

	routes := []string{
		// this is successful
		"/foo",
		// this fails
        //   Error Trace:    /home/endorama/code/REDACTED/main_test.go:68
        //   Error:          "{"message":"Not Found"}
        //                   " should have 0 item(s), but has 24
        //   Test:           TestMissing//v0/foo
		"/v0/foo",
		// this is successful
		"/v1/foo",
	}

	for _, path := range routes {
		t.Run(path, func(t *testing.T) {
			req, _ := http.NewRequest("POST", srv.URL+path, nil)

			resp, err := srv.Client().Do(req)
			require.NoError(t, err)
			defer resp.Body.Close()

			b, err := io.ReadAll(resp.Body)
			require.NoError(t, err)

			require.Equal(t, http.StatusNotFound, resp.StatusCode)
			require.Len(t, b, 0)
			require.Equal(t, "0", resp.Header.Get("Content-Length"))
		})
	}
}

Version/commit

github.com/labstack/echo/v4 v4.11.1

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions