diff --git a/README.md b/README.md index faac9d1d..03721391 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi * [Support KCP Protocol](#support-kcp-protocol) * [Connection Pool](#connection-pool) * [Rewriting the Host Header](#rewriting-the-host-header) + * [Set Headers In HTTP Request](#set-headers-in-http-request) * [Get Real IP](#get-real-ip) * [Password protecting your web service](#password-protecting-your-web-service) * [Custom subdomain names](#custom-subdomain-names) @@ -485,7 +486,7 @@ This feature is fit for a large number of short connections. ### Rewriting the Host Header -When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified Host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests. +When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests. ```ini # frpc.ini @@ -496,7 +497,24 @@ custom_domains = test.yourdomain.com host_header_rewrite = dev.yourdomain.com ``` -If `host_header_rewrite` is specified, the Host header will be rewritten to match the hostname portion of the forwarding address. +If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address. + +### Set Headers In HTTP Request + +You can set headers for proxy which type is `http`. + +```ini +# frpc.ini +[web] +type = http +local_port = 80 +custom_domains = test.yourdomain.com +host_header_rewrite = dev.yourdomain.com +header_X-From-Where = frp +``` + +Note that params which have prefix `header_` will be added to http request headers. +In this example, it will set header `X-From-Where: frp` to http request. ### Get Real IP diff --git a/README_zh.md b/README_zh.md index a4f95fee..ea8a15e4 100644 --- a/README_zh.md +++ b/README_zh.md @@ -34,6 +34,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp * [底层通信可选 kcp 协议](#底层通信可选-kcp-协议) * [连接池](#连接池) * [修改 Host Header](#修改-host-header) + * [设置 http 请求的 header](#设置-http-请求的-header) * [获取用户真实 IP](#获取用户真实-ip) * [通过密码保护你的 web 服务](#通过密码保护你的-web-服务) * [自定义二级域名](#自定义二级域名) @@ -525,6 +526,22 @@ host_header_rewrite = dev.yourdomain.com 原来 http 请求中的 host 字段 `test.yourdomain.com` 转发到后端服务时会被替换为 `dev.yourdomain.com`。 +### 设置 http 请求的 header + +对于 `type = http` 的代理,可以设置在转发中动态添加的 header 参数。 + +```ini +# frpc.ini +[web] +type = http +local_port = 80 +custom_domains = test.yourdomain.com +host_header_rewrite = dev.yourdomain.com +header_X-From-Where = frp +``` + +对于参数配置中所有以 `header_` 开头的参数(支持同时配置多个),都会被添加到 http 请求的 header 中,根据如上的配置,会在请求的 header 中加上 `X-From-Where: frp`。 + ### 获取用户真实 IP 目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。 diff --git a/conf/frpc_full.ini b/conf/frpc_full.ini index ca933f0d..e5e5c46a 100644 --- a/conf/frpc_full.ini +++ b/conf/frpc_full.ini @@ -56,10 +56,10 @@ dns_server = 8.8.8.8 # heartbeat_interval = 30 # heartbeat_timeout = 90 -# ssh is the proxy name same as server's configuration -# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as your_name.ssh +# 'ssh' is the unique proxy name +# if user in [common] section is not empty, it will be changed to {user}.{proxy} such as 'your_name.ssh' [ssh] -# tcp | udp | http | https, default is tcp +# tcp | udp | http | https | stcp | xtcp, default is tcp type = tcp local_ip = 127.0.0.1 local_port = 22 @@ -120,6 +120,8 @@ custom_domains = web02.yourdomain.com # locations is only available for http type locations = /,/pic host_header_rewrite = example.com +# params with prefix "header_" will be used to update http request headers +header_X-From-Where = frp [web02] type = https @@ -136,7 +138,7 @@ remote_port = 6003 # if plugin is defined, local_ip and local_port is useless # plugin will handle connections got from frps plugin = unix_domain_socket -# params set with prefix "plugin_" that plugin needed +# params with prefix "plugin_" that plugin needed plugin_unix_path = /var/run/docker.sock [plugin_http_proxy] diff --git a/models/config/proxy.go b/models/config/proxy.go index b1d36a8e..53a2a45b 100644 --- a/models/config/proxy.go +++ b/models/config/proxy.go @@ -429,10 +429,11 @@ type HttpProxyConf struct { LocalSvrConf - Locations []string `json:"locations"` - HostHeaderRewrite string `json:"host_header_rewrite"` - HttpUser string `json:"http_user"` - HttpPwd string `json:"http_pwd"` + Locations []string `json:"locations"` + HttpUser string `json:"http_user"` + HttpPwd string `json:"http_pwd"` + HostHeaderRewrite string `json:"host_header_rewrite"` + Headers map[string]string `json:"headers"` } func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { @@ -447,9 +448,20 @@ func (cfg *HttpProxyConf) Compare(cmp ProxyConf) bool { strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || cfg.HttpUser != cmpConf.HttpUser || - cfg.HttpPwd != cmpConf.HttpPwd { + cfg.HttpPwd != cmpConf.HttpPwd || + len(cfg.Headers) != len(cmpConf.Headers) { return false } + + for k, v := range cfg.Headers { + if v2, ok := cmpConf.Headers[k]; !ok { + return false + } else { + if v != v2 { + return false + } + } + } return true } @@ -461,6 +473,7 @@ func (cfg *HttpProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite cfg.HttpUser = pMsg.HttpUser cfg.HttpPwd = pMsg.HttpPwd + cfg.Headers = pMsg.Headers } func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { @@ -487,6 +500,13 @@ func (cfg *HttpProxyConf) UnmarshalFromIni(prefix string, name string, section i cfg.HostHeaderRewrite = section["host_header_rewrite"] cfg.HttpUser = section["http_user"] cfg.HttpPwd = section["http_pwd"] + cfg.Headers = make(map[string]string) + + for k, v := range section { + if strings.HasPrefix(k, "header_") { + cfg.Headers[strings.TrimPrefix(k, "header_")] = v + } + } return } @@ -498,6 +518,7 @@ func (cfg *HttpProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite pMsg.HttpUser = cfg.HttpUser pMsg.HttpPwd = cfg.HttpPwd + pMsg.Headers = cfg.Headers } func (cfg *HttpProxyConf) CheckForCli() (err error) { diff --git a/models/msg/msg.go b/models/msg/msg.go index fd3e656b..9669c6bf 100644 --- a/models/msg/msg.go +++ b/models/msg/msg.go @@ -91,12 +91,13 @@ type NewProxy struct { RemotePort int `json:"remote_port"` // http and https only - CustomDomains []string `json:"custom_domains"` - SubDomain string `json:"subdomain"` - Locations []string `json:"locations"` - HostHeaderRewrite string `json:"host_header_rewrite"` - HttpUser string `json:"http_user"` - HttpPwd string `json:"http_pwd"` + CustomDomains []string `json:"custom_domains"` + SubDomain string `json:"subdomain"` + Locations []string `json:"locations"` + HttpUser string `json:"http_user"` + HttpPwd string `json:"http_pwd"` + HostHeaderRewrite string `json:"host_header_rewrite"` + Headers map[string]string `json:"headers"` // stcp Sk string `json:"sk"` diff --git a/server/proxy.go b/server/proxy.go index 59477001..4bf6f1f8 100644 --- a/server/proxy.go +++ b/server/proxy.go @@ -225,6 +225,7 @@ type HttpProxy struct { func (pxy *HttpProxy) Run() (remoteAddr string, err error) { routeConfig := vhost.VhostRouteConfig{ RewriteHost: pxy.cfg.HostHeaderRewrite, + Headers: pxy.cfg.Headers, Username: pxy.cfg.HttpUser, Password: pxy.cfg.HttpPwd, CreateConnFn: pxy.GetRealConn, diff --git a/utils/version/version.go b/utils/version/version.go index e11d58e8..7ab09bef 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -19,7 +19,7 @@ import ( "strings" ) -var version string = "0.19.1" +var version string = "0.20.0" func Full() string { return version diff --git a/utils/vhost/newhttp.go b/utils/vhost/newhttp.go index 819ce1d1..1351b222 100644 --- a/utils/vhost/newhttp.go +++ b/utils/vhost/newhttp.go @@ -63,12 +63,17 @@ func NewHttpReverseProxy() *HttpReverseProxy { Director: func(req *http.Request) { req.URL.Scheme = "http" url := req.Context().Value("url").(string) - host := getHostFromAddr(req.Context().Value("host").(string)) - host = rp.GetRealHost(host, url) + oldHost := getHostFromAddr(req.Context().Value("host").(string)) + host := rp.GetRealHost(oldHost, url) if host != "" { req.Host = host } req.URL.Host = req.Host + + headers := rp.GetHeaders(oldHost, url) + for k, v := range headers { + req.Header.Set(k, v) + } }, Transport: &http.Transport{ ResponseHeaderTimeout: responseHeaderTimeout, @@ -117,6 +122,14 @@ func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host st return } +func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) { + vr, ok := rp.getVhost(domain, location) + if ok { + headers = vr.payload.(*VhostRouteConfig).Headers + } + return +} + func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) { vr, ok := rp.getVhost(domain, location) if ok { diff --git a/utils/vhost/vhost.go b/utils/vhost/vhost.go index fe0d1f29..84e57e95 100644 --- a/utils/vhost/vhost.go +++ b/utils/vhost/vhost.go @@ -59,6 +59,7 @@ type VhostRouteConfig struct { RewriteHost string Username string Password string + Headers map[string]string CreateConnFn CreateConnFunc }