support responseHeaders.set for proxy type http (#4192)

pull/4204/head
fatedier 7 months ago committed by GitHub
parent d0d396becb
commit e81b36c5ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -983,7 +983,7 @@ The HTTP request will have the `Host` header rewritten to `Host: dev.example.com
### Setting other HTTP Headers ### Setting other HTTP Headers
Similar to `Host`, You can override other HTTP request headers with proxy type `http`. Similar to `Host`, You can override other HTTP request and response headers with proxy type `http`.
```toml ```toml
# frpc.toml # frpc.toml
@ -995,9 +995,10 @@ localPort = 80
customDomains = ["test.example.com"] customDomains = ["test.example.com"]
hostHeaderRewrite = "dev.example.com" hostHeaderRewrite = "dev.example.com"
requestHeaders.set.x-from-where = "frp" requestHeaders.set.x-from-where = "frp"
responseHeaders.set.foo = "bar"
``` ```
In this example, it will set header `x-from-where: frp` in the HTTP request. In this example, it will set header `x-from-where: frp` in the HTTP request and `foo: bar` in the HTTP response.
### Get Real IP ### Get Real IP

@ -7,6 +7,7 @@ When connecting to frps versions older than v0.39.0 might encounter compatibilit
### Features ### Features
* Show tcpmux proxies on the frps dashboard. * Show tcpmux proxies on the frps dashboard.
* `http` proxy can modify the response header. For example, `responseHeaders.set.foo = "bar"` will add a new header `foo: bar` to the response.
### Fixes ### Fixes

@ -209,6 +209,7 @@ locations = ["/", "/pic"]
# routeByHTTPUser = abc # routeByHTTPUser = abc
hostHeaderRewrite = "example.com" hostHeaderRewrite = "example.com"
requestHeaders.set.x-from-where = "frp" requestHeaders.set.x-from-where = "frp"
responseHeaders.set.foo = "bar"
healthCheck.type = "http" healthCheck.type = "http"
# frpc will send a GET http request '/status' to local http service # frpc will send a GET http request '/status' to local http service
# http service is alive when it return 2xx http response code # http service is alive when it return 2xx http response code

@ -291,6 +291,7 @@ type HTTPProxyConfig struct {
HTTPPassword string `json:"httpPassword,omitempty"` HTTPPassword string `json:"httpPassword,omitempty"`
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"` HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"` RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
ResponseHeaders HeaderOperations `json:"responseHeaders,omitempty"`
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"` RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
} }
@ -304,6 +305,7 @@ func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
m.HTTPUser = c.HTTPUser m.HTTPUser = c.HTTPUser
m.HTTPPwd = c.HTTPPassword m.HTTPPwd = c.HTTPPassword
m.Headers = c.RequestHeaders.Set m.Headers = c.RequestHeaders.Set
m.ResponseHeaders = c.ResponseHeaders.Set
m.RouteByHTTPUser = c.RouteByHTTPUser m.RouteByHTTPUser = c.RouteByHTTPUser
} }
@ -317,6 +319,7 @@ func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
c.HTTPUser = m.HTTPUser c.HTTPUser = m.HTTPUser
c.HTTPPassword = m.HTTPPwd c.HTTPPassword = m.HTTPPwd
c.RequestHeaders.Set = m.Headers c.RequestHeaders.Set = m.Headers
c.ResponseHeaders.Set = m.ResponseHeaders
c.RouteByHTTPUser = m.RouteByHTTPUser c.RouteByHTTPUser = m.RouteByHTTPUser
} }

@ -121,6 +121,7 @@ type NewProxy struct {
HTTPPwd string `json:"http_pwd,omitempty"` HTTPPwd string `json:"http_pwd,omitempty"`
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"` HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
Headers map[string]string `json:"headers,omitempty"` Headers map[string]string `json:"headers,omitempty"`
ResponseHeaders map[string]string `json:"response_headers,omitempty"`
RouteByHTTPUser string `json:"route_by_http_user,omitempty"` RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
// stcp, sudp, xtcp // stcp, sudp, xtcp

@ -63,9 +63,9 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
req := r.Out req := r.Out
req.URL.Scheme = "http" req.URL.Scheme = "http"
reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo) reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo)
oldHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host) originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
rc := rp.GetRouteConfig(oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser) rc := req.Context().Value(RouteConfigKey).(*RouteConfig)
if rc != nil { if rc != nil {
if rc.RewriteHost != "" { if rc.RewriteHost != "" {
req.Host = rc.RewriteHost req.Host = rc.RewriteHost
@ -77,7 +77,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
endpoint, _ = rc.ChooseEndpointFn() endpoint, _ = rc.ChooseEndpointFn()
reqRouteInfo.Endpoint = endpoint reqRouteInfo.Endpoint = endpoint
log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]", log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
endpoint, oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser) endpoint, originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
} }
// Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections. // Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections.
req.URL.Host = rc.Domain + "." + req.URL.Host = rc.Domain + "." +
@ -92,6 +92,15 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
req.URL.Host = req.Host req.URL.Host = req.Host
} }
}, },
ModifyResponse: func(r *http.Response) error {
rc := r.Request.Context().Value(RouteConfigKey).(*RouteConfig)
if rc != nil {
for k, v := range rc.ResponseHeaders {
r.Header.Set(k, v)
}
}
return nil
},
// Create a connection to one proxy routed by route policy. // Create a connection to one proxy routed by route policy.
Transport: &http.Transport{ Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout, ResponseHeaderTimeout: rp.responseHeaderTimeout,
@ -157,14 +166,6 @@ func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser str
return nil return nil
} }
func (rp *HTTPReverseProxy) GetHeaders(domain, location, routeByHTTPUser string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
headers = vr.payload.(*RouteConfig).Headers
}
return
}
// CreateConnection create a new connection by route config // CreateConnection create a new connection by route config
func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) { func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) {
host, _ := httppkg.CanonicalHost(reqRouteInfo.Host) host, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
@ -305,8 +306,13 @@ func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Requ
RemoteAddr: req.RemoteAddr, RemoteAddr: req.RemoteAddr,
URLHost: req.URL.Host, URLHost: req.URL.Host,
} }
originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
rc := rp.GetRouteConfig(originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
newctx := req.Context() newctx := req.Context()
newctx = context.WithValue(newctx, RouteInfoKey, reqRouteInfo) newctx = context.WithValue(newctx, RouteInfoKey, reqRouteInfo)
newctx = context.WithValue(newctx, RouteConfigKey, rc)
return req.Clone(newctx) return req.Clone(newctx)
} }

@ -29,7 +29,8 @@ import (
type RouteInfo string type RouteInfo string
const ( const (
RouteInfoKey RouteInfo = "routeInfo" RouteInfoKey RouteInfo = "routeInfo"
RouteConfigKey RouteInfo = "routeConfig"
) )
type RequestRouteInfo struct { type RequestRouteInfo struct {
@ -113,6 +114,7 @@ type RouteConfig struct {
Username string Username string
Password string Password string
Headers map[string]string Headers map[string]string
ResponseHeaders map[string]string
RouteByHTTPUser string RouteByHTTPUser string
CreateConnFn CreateConnFunc CreateConnFn CreateConnFunc

@ -58,6 +58,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
RewriteHost: pxy.cfg.HostHeaderRewrite, RewriteHost: pxy.cfg.HostHeaderRewrite,
RouteByHTTPUser: pxy.cfg.RouteByHTTPUser, RouteByHTTPUser: pxy.cfg.RouteByHTTPUser,
Headers: pxy.cfg.RequestHeaders.Set, Headers: pxy.cfg.RequestHeaders.Set,
ResponseHeaders: pxy.cfg.ResponseHeaders.Set,
Username: pxy.cfg.HTTPUser, Username: pxy.cfg.HTTPUser,
Password: pxy.cfg.HTTPPassword, Password: pxy.cfg.HTTPPassword,
CreateConnFn: pxy.GetRealConn, CreateConnFn: pxy.GetRealConn,

@ -267,7 +267,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
Ensure() Ensure()
}) })
ginkgo.It("Modify headers", func() { ginkgo.It("Modify request headers", func() {
vhostHTTPPort := f.AllocPort() vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort) serverConf := getDefaultServerConf(vhostHTTPPort)
@ -292,7 +292,6 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
f.RunProcesses([]string{serverConf}, []string{clientConf}) f.RunProcesses([]string{serverConf}, []string{clientConf})
// not set auth header
framework.NewRequestExpect(f).Port(vhostHTTPPort). framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) { RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com") r.HTTP().HTTPHost("normal.example.com")
@ -301,6 +300,40 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
Ensure() Ensure()
}) })
ginkgo.It("Modify response headers", func() {
vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort)
localPort := f.AllocPort()
localServer := httpserver.New(
httpserver.WithBindPort(localPort),
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(200)
})),
)
f.RunServer("", localServer)
clientConf := consts.DefaultClientConfig
clientConf += fmt.Sprintf(`
[[proxies]]
name = "test"
type = "http"
localPort = %d
customDomains = ["normal.example.com"]
responseHeaders.set.x-from-where = "frp"
`, localPort)
f.RunProcesses([]string{serverConf}, []string{clientConf})
framework.NewRequestExpect(f).Port(vhostHTTPPort).
RequestModify(func(r *request.Request) {
r.HTTP().HTTPHost("normal.example.com")
}).
Ensure(func(res *request.Response) bool {
return res.Header.Get("X-From-Where") == "frp"
})
})
ginkgo.It("Host Header Rewrite", func() { ginkgo.It("Host Header Rewrite", func() {
vhostHTTPPort := f.AllocPort() vhostHTTPPort := f.AllocPort()
serverConf := getDefaultServerConf(vhostHTTPPort) serverConf := getDefaultServerConf(vhostHTTPPort)

Loading…
Cancel
Save