Compare commits

...

4 Commits

Author SHA1 Message Date
Sword
82fa6702e3
Merge 3a5c2bf06a856832a1838426013780ffd20d8023 into 27db6217ecda9236f5bc25c65824f1e723810751 2025-01-06 14:45:54 +08:00
fatedier
27db6217ec
frpc: support metadatas and annotations in frpc proxy commands (#4623) 2025-01-06 14:22:57 +08:00
Sword
3a5c2bf06a
Update pkg/util/vhost/http.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2024-12-24 17:36:40 +09:00
sword-jin
ae5fe4c8a6 Implement HTTP/2 to HTTP/1.1 downgrade support if the request is GRPC in reverse proxy 2024-12-24 08:11:05 +00:00
3 changed files with 54 additions and 26 deletions

View File

@ -1,5 +1,3 @@
### Features
* `tzdata` is installed by default in the container image, and the time zone can be set using the `TZ` environment variable.
* The `quic-bind-port` command line parameter is supported in frps, which specifies the port for accepting frpc connections using the QUIC protocol.
* The vhost HTTP proxy of frps supports the h2c protocol.
* Support metadatas and annotations in frpc proxy commands.

View File

@ -106,6 +106,8 @@ func registerProxyBaseConfigFlags(cmd *cobra.Command, c *v1.ProxyBaseConfig, opt
}
cmd.Flags().StringVarP(&c.Name, "proxy_name", "n", "", "proxy name")
cmd.Flags().StringToStringVarP(&c.Metadatas, "metadatas", "", nil, "metadata key-value pairs (e.g., key1=value1,key2=value2)")
cmd.Flags().StringToStringVarP(&c.Annotations, "annotations", "", nil, "annotation key-value pairs (e.g., key1=value1,key2=value2)")
if !options.sshMode {
cmd.Flags().StringVarP(&c.LocalIP, "local_ip", "i", "127.0.0.1", "local ip")

View File

@ -16,6 +16,7 @@ package vhost
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
@ -49,6 +50,21 @@ type HTTPReverseProxy struct {
responseHeaderTimeout time.Duration
}
type grpcSwitchH2Transport struct {
h2t *http2.Transport
}
var _ http.RoundTripper = (*grpcSwitchH2Transport)(nil)
// RoundTrip implements http.RoundTripper.
func (d *grpcSwitchH2Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if req.Header.Get("Content-Type") != "application/grpc" {
return nil, http.ErrSkipAltProtocol
}
return d.h2t.RoundTrip(req)
}
func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *HTTPReverseProxy {
if option.ResponseHeaderTimeoutS <= 0 {
option.ResponseHeaderTimeoutS = 60
@ -57,6 +73,40 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
vhostRouter: vhostRouter,
}
h2t := &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
}
h1t := &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
IdleConnTimeout: 60 * time.Second,
MaxIdleConnsPerHost: 5,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
ForceAttemptHTTP2: true,
Proxy: func(req *http.Request) (*url.URL, error) {
// Use proxy mode if there is host in HTTP first request line.
// GET http://example.com/ HTTP/1.1
// Host: example.com
//
// Normal:
// GET / HTTP/1.1
// Host: example.com
urlHost := req.Context().Value(RouteInfoKey).(*RequestRouteInfo).URLHost
if urlHost != "" {
return req.URL, nil
}
return nil, nil
},
}
// although register http protocol with a h2t, but it's only used for grpc.
// for normal http request, it still uses h1t.
h1t.RegisterProtocol("http", &grpcSwitchH2Transport{h2t: h2t})
proxy := &httputil.ReverseProxy{
// Modify incoming requests by route policies.
Rewrite: func(r *httputil.ProxyRequest) {
@ -103,29 +153,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
}
return nil
},
// Create a connection to one proxy routed by route policy.
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
IdleConnTimeout: 60 * time.Second,
MaxIdleConnsPerHost: 5,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return rp.CreateConnection(ctx.Value(RouteInfoKey).(*RequestRouteInfo), true)
},
Proxy: func(req *http.Request) (*url.URL, error) {
// Use proxy mode if there is host in HTTP first request line.
// GET http://example.com/ HTTP/1.1
// Host: example.com
//
// Normal:
// GET / HTTP/1.1
// Host: example.com
urlHost := req.Context().Value(RouteInfoKey).(*RequestRouteInfo).URLHost
if urlHost != "" {
return req.URL, nil
}
return nil, nil
},
},
Transport: h1t,
BufferPool: pool.NewBuffer(32 * 1024),
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {