mirror of
https://gitee.com/IrisVega/frp.git
synced 2024-11-01 22:31:29 +08:00
support responseHeaders.set for proxy type http (#4192)
This commit is contained in:
parent
d0d396becb
commit
e81b36c5ba
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ 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…
Reference in New Issue
Block a user