Description
Go version
go version go1.23.0 linux/amd64
Output of go env
in your module/workspace:
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED */go-build'
GOENV='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED */go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED */pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED *'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED *'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED */pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.23.0'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='* REMOVED TO PREVENT LOCAL PATHS FROM BEING LEAKED *'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='servemuxpoc/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build618692677=/tmp/go-build -gno-record-gcc-switches'
What did you do?
https://go.dev/play/p/_3usVyrcn1V
In the above Go Playground example, I'm registering a single http.HandleFunc
with a http.ServeMux
and leveraging
the new pattern format to require both the path, /hello
, and method, HTTP GET
to match for the request to be handled.
Once registered, I create an http.Server
and run it in a goroutine. Afterward, I begin sending 2 requests to the server.
The first request contains //
at the beginning of it's path and the second request does not. Note, both requests are sent
with the HTTP POST
method.
What did you see happen?
with duplicated slash in url path
request POST http://[::]:29142//hello
received request POST //hello
writing response 301
received request GET /hello
writing response 500
response 500 GET
without duplicated slash in url path
request POST http://[::]:29142/hello
received request POST /hello
writing response 405
response 405 Method Not Allowed
The first request actually executes the HTTP GET
pattern handler which results in a HTTP 500
status code being returned.
The second request did not execute the HTTP GET
pattern handler and received a HTTP 405
status code.
What did you expect to see?
For the both requests, I expected to receive a HTTP 405 Method Not Allowed
to be returned since each request method is HTTP POST
.
After investigating, I came across the following:
- Issue 69044
- Issue 60769
- Issue 69063
- and lastly, the findHandler source code
which along with the logs seen in the output of the Go Playground example all lead to the first request being matched to http.RedirectHandler
.
Now, I understand that http.RedirectHandler
is expected behaviour but I believe this scenario to be a particular edge case
that leads to 2 expectations to collide. That being the request method matching of http.ServeMux
and the http.RedirectHandler
behviour. As a user, I was geniunely surprised to not receive a 405
for POST //hello
since cleaning //hello
makes it /hello
.
I think in this situation the HTTP method should be validated before http.RedirectHandler
is returned but I'd love to hear others
thoughts on this behaviour.
NOTE: this is an edge case that only happens for patterns whose method for matching happens to be HTTP GET
since
http.RedirectHandler
redirects via HTTP GET
i.e. incoming HTTP POST //hello
-> redirect to HTTP GET /hello