Repository: valyala/fasthttp
Pull request #1457 (2022-12-22 12:41:36)

fix adapter support http2
Since the [adapter](fasthttpadaptor/request.go) hardcode `HTTP/1.1`:
```go
	r.Proto = "HTTP/1.1"
	r.ProtoMajor = 1
```

Maybe this is better?
```go
	r.Proto = b2s(ctx.Request.Header.Protocol())
	if r.Proto == "HTTP/2" {
		r.ProtoMajor = 2
	} else {
		r.ProtoMajor = 1
	}
```

With this change, compose  `fasthttp` and `grpc` together is possible.

Usage:
```go
// start server
ws := &fasthttp.Server{...}
http2.ConfigureServer(ws)
ws.ServeTLS(...)

// transform to grpc request
if bytes.Equal(ctx.Request.Header.ContentType(), grpcContentType) {
    fh := fasthttpadaptor.NewFastHTTPHandler(a.Rpc)
    fh(ctx)
    return
}
```


pedia: Agree, This is a small step.

erikdubbelboer: How would this work? In your example you use `http2.ConfigureServer(ws)` but [`http2.ConfigureServer`](https://pkg.go.dev/golang.org/x/net/http2#ConfigureServer) expects a `http.Server` as argument, not a `fasthttp.Server`.

A fasthttp server isn't be able to receive `HTTP/2` requests, so how would that `fasthttp.RequestCtx` with a `Proto` of `HTTP/2` ever be created?
pedia: in `http2.ConfigureServer(ws)`, `http2` come from https://github.com/dgrr/http2

I think http2.ConfigureServer patched a little.

Testing with `curl --http2`, it passed.

Patch:
diff --git a/fasthttpadaptor/adaptor.go b/fasthttpadaptor/adaptor.go
index 47a4c29..dcd43e4 100644
--- a/fasthttpadaptor/adaptor.go
+++ b/fasthttpadaptor/adaptor.go
@@ -109,3 +109,5 @@ func (w *netHTTPResponseWriter) WriteHeader(statusCode int) {
 func (w *netHTTPResponseWriter) Write(p []byte) (int, error) {
 	return w.w.Write(p)
 }
+
+func (w *netHTTPResponseWriter) Flush() {}
diff --git a/fasthttpadaptor/request.go b/fasthttpadaptor/request.go
index cc2684b..d763a98 100644
--- a/fasthttpadaptor/request.go
+++ b/fasthttpadaptor/request.go
@@ -25,8 +25,12 @@ func ConvertRequest(ctx *fasthttp.RequestCtx, r *http.Request, forServer bool) e
 	}
 
 	r.Method = b2s(ctx.Method())
-	r.Proto = "HTTP/1.1"
-	r.ProtoMajor = 1
+	r.Proto = b2s(ctx.Request.Header.Protocol())
+	if r.Proto == "HTTP/2" {
+		r.ProtoMajor = 2
+	} else {
+		r.ProtoMajor = 1
+	}
 	r.ProtoMinor = 1
 	r.ContentLength = int64(len(body))
 	r.RemoteAddr = ctx.RemoteAddr().String()
diff --git a/http.go b/http.go
index 25defad..3d1429c 100644
--- a/http.go
+++ b/http.go
@@ -1129,7 +1129,7 @@ func (req *Request) readLimitBody(r *bufio.Reader, maxBodySize int, getOnly bool
 	// Do not reset the request here - the caller must reset it before
 	// calling this method.
 
-	if getOnly && !req.Header.IsGet() {
+	if getOnly && !req.Header.IsGet() && !req.Header.IsHead() {
 		return ErrGetOnly
 	}
 
@@ -1147,7 +1147,7 @@ func (req *Request) readBodyStream(r *bufio.Reader, maxBodySize int, getOnly boo
 	// Do not reset the request here - the caller must reset it before
 	// calling this method.
 
-	if getOnly && !req.Header.IsGet() {
+	if getOnly && !req.Header.IsGet() && !req.Header.IsHead() {
 		return ErrGetOnly
 	}
 
diff --git a/server.go b/server.go
index c329907..bb029ec 100644
--- a/server.go
+++ b/server.go
@@ -292,7 +292,7 @@ type Server struct {
 	// Rejects all non-GET requests if set to true.
 	//
 	// This option is useful as anti-DoS protection for servers
-	// accepting only GET requests. The request size is limited
+	// accepting only GET requests and HEAD requests. The request size is limited
 	// by ReadBufferSize if GetOnly is set.
 	//
 	// Server accepts all the requests by default.
@@ -406,6 +406,12 @@ type Server struct {
 	// instead.
 	TLSConfig *tls.Config
 
+	// FormValueFunc, which is used by RequestCtx.FormValue and support for customising
+	// the behaviour of the RequestCtx.FormValue function.
+	//
+	// NetHttpFormValueFunc gives a FormValueFunc func implementation that is consistent with net/http.
+	FormValueFunc FormValueFunc
+
 	nextProtos map[string]ServeHandler
 
 	concurrency      uint32
@@ -604,6 +610,7 @@ type RequestCtx struct {
 
 	hijackHandler    HijackHandler
 	hijackNoResponse bool
+	formValueFunc    FormValueFunc
 }
 
 // HijackHandler must process the hijacked connection c.
@@ -1108,23 +1115,54 @@ func SaveMultipartFile(fh *multipart.FileHeader, path string) (err error) {
 //
 // The returned value is valid until your request handler returns.
 func (ctx *RequestCtx) FormValue(key string) []byte {
-	v := ctx.QueryArgs().Peek(key)
-	if len(v) > 0 {
-		return v
+	if ctx.formValueFunc != nil {
+		return ctx.formValueFunc(ctx, key)
 	}
-	v = ctx.PostArgs().Peek(key)
-	if len(v) > 0 {
-		return v
+	return defaultFormValue(ctx, key)
+}
+
+type FormValueFunc func(*RequestCtx, string) []byte
+
+var (
+	defaultFormValue = func(ctx *RequestCtx, key string) []byte {
+		v := ctx.QueryArgs().Peek(key)
+		if len(v) > 0 {
+			return v
+		}
+		v = ctx.PostArgs().Peek(key)
+		if len(v) > 0 {
+			return v
+		}
+		mf, err := ctx.MultipartForm()
+		if err == nil && mf.Value != nil {
+			vv := mf.Value[key]
+			if len(vv) > 0 {
+				return []byte(vv[0])
+			}
+		}
+		return nil
 	}
-	mf, err := ctx.MultipartForm()
-	if err == nil && mf.Value != nil {
-		vv := mf.Value[key]
-		if len(vv) > 0 {
-			return []byte(vv[0])
+
+	// NetHttpFormValueFunc gives consistent behavior with net/http. POST and PUT body parameters take precedence over URL query string values.
+	NetHttpFormValueFunc = func(ctx *RequestCtx, key string) []byte {
+		v := ctx.PostArgs().Peek(key)
+		if len(v) > 0 {
+			return v
+		}
+		mf, err := ctx.MultipartForm()
+		if err == nil && mf.Value != nil {
+			vv := mf.Value[key]
+			if len(vv) > 0 {
+				return []byte(vv[0])
+			}
+		}
+		v = ctx.QueryArgs().Peek(key)
+		if len(v) > 0 {
+			return v
 		}
+		return nil
 	}
-	return nil
-}
+)
 
 // IsGet returns true if request method is GET.
 func (ctx *RequestCtx) IsGet() bool {
@@ -2638,7 +2676,9 @@ func (s *Server) acquireCtx(c net.Conn) (ctx *RequestCtx) {
 	} else {
 		ctx = v.(*RequestCtx)
 	}
-
+	if s.FormValueFunc != nil {
+		ctx.formValueFunc = s.FormValueFunc
+	}
 	ctx.c = c
 
 	return ctx


Test patch:
diff --git a/server_test.go b/server_test.go
index 0c44a23..7d96e93 100644
--- a/server_test.go
+++ b/server_test.go
@@ -1713,6 +1713,20 @@ func TestRequestCtxFormValue(t *testing.T) {
 	}
 }
 
+func TestSetStandardFormValueFunc(t *testing.T) {
+	t.Parallel()
+	var ctx RequestCtx
+	var req Request
+	req.SetRequestURI("/foo/bar?aaa=bbb")
+	req.SetBodyString("aaa=port")
+	req.Header.SetContentType("application/x-www-form-urlencoded")
+	ctx.Init(&req, nil, nil)
+	ctx.formValueFunc = NetHttpFormValueFunc
+	v := ctx.FormValue("aaa")
+	if string(v) != "port" {
+		t.Fatalf("unexpected value %q. Expecting %q", v, "port")
+	}
+}
 func TestRequestCtxUserValue(t *testing.T) {
 	t.Parallel()
 
@@ -3287,6 +3301,28 @@ func TestServeConnSingleRequest(t *testing.T) {
 	verifyResponse(t, br, 200, "aaa", "requestURI=/foo/bar?baz, host=google.com")
 }
 
+func TestServerSetFormValueFunc(t *testing.T) {
+	t.Parallel()
+	s := &Server{
+		Handler: func(ctx *RequestCtx) {
+			ctx.Success("aaa", ctx.FormValue("aaa"))
+		},
+		FormValueFunc: func(ctx *RequestCtx, s string) []byte {
+			return []byte(s)
+		},
+	}
+
+	rw := &readWriter{}
+	rw.r.WriteString("GET /foo/bar?baz HTTP/1.1\r\nHost: google.com\r\n\r\n")
+
+	if err := s.ServeConn(rw); err != nil {
+		t.Fatalf("Unexpected error from serveConn: %v", err)
+	}
+
+	br := bufio.NewReader(&rw.w)
+	verifyResponse(t, br, 200, "aaa", "aaa")
+}
+
 func TestServeConnMultiRequests(t *testing.T) {
 	t.Parallel()