-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
Go version
go version go1.22.2 windows/amd64
Output of go env
in your module/workspace:
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\PC\AppData\Local\go-build
set GOENV=C:\Users\PC\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\PC\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\PC\go
set GOPRIVATE=
set GOPROXY=direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=off
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.22.2
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=C:\GIT\GO\test\go.mod
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\MEDVED~1\AppData\Local\Temp\go-build3146306794=/tmp/go-build -gno-record-gcc-switches
What did you do?
package main
import (
"io"
"log"
"net/http"
)
func main() {
server := New()
log.Println("Start server", "localhost:55432")
if err := http.ListenAndServe("localhost:55432", server); err != nil {
panic(err)
}
}
// Server ...
type Server struct{}
// New ...
func New() *Server {
return &Server{}
}
// Handler ...
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
request, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
}
log.Println(request)
}
Description:
The server hangs when processing POST requests that have a Content-Length greater than zero but an empty body. This issue occurs because the io.ReadAll function waits to read the number of bytes specified in the Content-Length header, leading to a hang when the body is empty.
Steps to Reproduce:
-
Run the provided server code.
-
Send a POST request to localhost:55432 with a Content-Length header greater than zero and an empty body.
Example request:
POST /test HTTP/1.1
Host: localhost:55432
Content-Length: 1
The server hangs on the line request, err := io.ReadAll(r.Body), and after canceling the request, it logs an unexpected EOF error.
Alright, I thought such requests could be handled through context and discarded. However, I tried to do this and encountered another error. It is also related to the request hanging, but this time with a timeout:
package main
import (
"fmt"
"io"
"net/http"
"strconv"
"time"
)
// readWithTimeout reads from r with the specified timeout
func readWithTimeout(r io.Reader, timeout time.Duration) ([]byte, error) {
done := make(chan struct{})
var result []byte
var err error
go func() {
result, err = io.ReadAll(r)
close(done)
}()
select {
case <-done:
return result, err
case <-time.After(timeout):
return nil, fmt.Errorf("timeout reading body")
}
}
func handler(w http.ResponseWriter, r *http.Request) {
contentLength := r.Header.Get("Content-Length")
if contentLength != "" {
length, err := strconv.Atoi(contentLength)
if err != nil {
http.Error(w, "Invalid Content-Length header", http.StatusBadRequest)
return
}
// Set a reasonable timeout for reading the body
timeout := 2 * time.Second
// Read the body with a timeout
body, err := readWithTimeout(r.Body, timeout)
if err != nil {
http.Error(w, err.Error(), http.StatusRequestTimeout)
return
}
defer r.Body.Close()
if len(body) == 0 && length > 0 {
http.Error(w, "Content-Length header provided but body is empty", http.StatusBadRequest)
return
}
// Handle the body as needed
fmt.Fprintf(w, "Received body: %s", string(body))
} else {
http.Error(w, "Content-Length header is required", http.StatusBadRequest)
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":55432", nil)
}
The request hangs when attempting to call w.finishRequest() in net/http/server.go.
The call parameters are the same: an empty body and Content-Length > 0.
What am I doing wrong? Is this behavior standard?
What did you see happen?
The server hangs on the line request, err := io.ReadAll(r.Body), and after canceling the request, it logs an unexpected EOF error.
What did you expect to see?
I expected the request to either fail with an error or proceed without hanging