Skip to content

Bug: Prefix option causes http: invalid Read on closed Body in downstream handlers #49

@jinuthankachan

Description

@jinuthankachan

Summary

While experimenting with echov5, this was observed. Later trying it on echo/v4, the issue could be reproduced.
When Options.Prefix is set, the middleware reads and closes the request body during validation but fails to restore it on the original echo.Context request. Downstream handlers that try to read the body receive http: invalid Read on closed Body or EOF.

Reproduction

package main
import (
	"context"
	"tryecho/api"

	"github.com/labstack/echo/v4"
	echomiddleware "github.com/oapi-codegen/echo-middleware"
)
type Server struct{}
func (s *Server) CreateItem(ctx context.Context, request api.CreateItemRequestObject) (api.CreateItemResponseObject, error) {
	return api.CreateItem201JSONResponse{
		Name: &request.Body.Name,
	}, nil
}

func main() {
	e := echo.New()
	spec, err := api.GetSwagger()
	if err != nil {
		panic(err)
	}
	spec.Servers = nil
	g := e.Group("/api")
	g.Use(echomiddleware.OapiRequestValidatorWithOptions(spec, &echomiddleware.Options{
		Prefix: "/api",
	}))
	myServer := &Server{}
	strictHandler := api.NewStrictHandler(myServer, nil)
	api.RegisterHandlers(g, strictHandler)
	e.Logger.Fatal(e.Start(":8080"))
}
➜  tryecho curl --location 'localhost:8080/api/items' \
--header 'Content-Type: application/json' \
--data '{
    "name":""
}'
{"message":"http: invalid Read on closed Body"}

Workaround (until fixed)

Consumers using Prefix must pre-buffer the body themselves so GetBody is set before the middleware runs:

g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        req := c.Request()
        if req.Body != nil && req.Body != http.NoBody {
            data, _ := io.ReadAll(req.Body)
            req.Body.Close()
            req.GetBody = func() (io.ReadCloser, error) {
                return io.NopCloser(bytes.NewReader(data)), nil
            }
            req.Body, _ = req.GetBody()
        }
        return next(c)
    }
})
g.Use(OapiRequestValidatorWithOptions(spec, &Options{Prefix: "/api"}))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions