echo icon indicating copy to clipboard operation
echo copied to clipboard

`/hello/:helloID/world` is matching `/hello//world` when I was not expecting that

Open fagner-koho opened this issue 3 years ago • 2 comments

Issue Description

Route /hello/:helloID/world is matching /hello//world when I was not expecting that.

Checklist

  • [X] Dependencies installed
  • [X] No typos
  • [X] Searched existing issues and docs

Expected behaviour

I was expecting to get 404 like it happens when I call hello3/ and the closest available route I have is /hello3/:helloID.

Actual behaviour

/hello//world is reaching route /hello/:helloID/world.

Steps to reproduce

See example below.

Working code to debug

package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"sync"
	"time"

	"github.com/labstack/echo/v4"
)

func main() {
	e := echo.New()
	e.GET("/hello/:helloID/world", func(c echo.Context) error {
		return c.String(http.StatusOK, fmt.Sprintf("Hello, World %v!", c.Param("helloID")))
	})

	g := e.Group("/test")
	g.GET("/hello2/:helloID/world", func(c echo.Context) error {
		return c.String(http.StatusOK, fmt.Sprintf("Hello2, World %v!", c.Param("helloID2")))
	})

	e.GET("/hello3/:helloID", func(c echo.Context) error {
		return c.String(http.StatusOK, fmt.Sprintf("Hello, World %v!", c.Param("helloID")))
	})

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println(e.Start(":1323"))
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(time.Second * 1)

		test("http://localhost:1323/hello//world")
		test("http://localhost:1323/test/hello2//world")
		test("http://localhost:1323/hello3/")
		if err := e.Shutdown(context.Background()); err != nil {
			fmt.Println(err)
		}
	}()
	wg.Wait()
	fmt.Println("Finished!")
}

func test(url string) {
	var client http.Client
	response, _ := client.Get(url)

	body, _ := io.ReadAll(response.Body)
	fmt.Println(string(body))
}

Output:

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.7.2
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1323
Hello, World !
Hello2, World !
{"message":"Not Found"}

http: Server closed
Finished!

Program exited.

Playground.

Version/commit

Imported github.com/labstack/echo/v4 into my program.

fagner-koho avatar Apr 27 '22 18:04 fagner-koho

This is quite implementation related and changing how it works probably would break applications that have route pairs likes

  • /users/ <-- usually these routes list something
  • /users/:id <-- these routes show details about that entity

making second one match first one would break existing stuff

some notes about Echo router internals. When these routes are added we create routing three likes that

1. "/" (static)
1.1. "hello" (static)
1.1.1. "/" (static)
1.1.1.1. ":" (param) (isHandler)

1.1.2. "3/" (static)
1.1.2.1. ":" (param) (isHandler)

1.2. "test/hello2/"
1.2.1. ":" (param)
1.2.1.1. "/world" (static) (isHandler)

          ┌───┐
          │ / ├─────────┐
          └─┬─┘         │
            │           │
 ┌──────────▼─┐        ┌▼────┐
 │test/hello2/│        │hello│
 └┬───────────┘        └┬────┤
  │                     │    │
 ┌▼┐                   ┌▼┐   │ ┌──┐
 │:│                   │/│   └─►3/│
 └┬┘                   └┬┘     └┬─┘
  │                     │       │
 ┌▼─────┐              ┌▼┐     ┌▼┐
 │/world│              │:│     │:│
 └──────┘              └─┘     └┬┘
                                │
                               ┌▼─────┐
                               │/world│
                               └──────┘

Only those with isHandler have handler attached are able to serve handler functions (unless there is some middleware that can alter the chain).
When "param" is at the end of route path we do not consider empty string `` as match When "param" is not at the end of the route we consider empty string as match for parameter. This relates how we move down in the three and search matches for request path. When we reach at the end of the request path and we are currently in three at some node that is not handler we do not check is there is child node that could accept empty string as match (any/param routes)

I assume this param behavior is not intentional, but it is the way it works.

aldas avatar Apr 27 '22 19:04 aldas

Until there is no resolution for this issue I recommend adding another route /hello3/ that has same handler function has /hello3/:helloID route

aldas avatar Apr 27 '22 19:04 aldas

Closing, as no further comments came up.

lammel avatar Dec 02 '22 21:12 lammel