Skip to content

Commit 4d0dd19

Browse files
committed
name based virtual host support
1 parent d2da13b commit 4d0dd19

File tree

3 files changed

+96
-11
lines changed

3 files changed

+96
-11
lines changed

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,29 @@ auth:
109109
api_endpoint: https://github.yourcompany.com/api
110110
```
111111

112+
## Name Based Virtual Host
113+
114+
An example of "Name Based Viatual Host" setting.
115+
116+
```yaml
117+
auth:
118+
session:
119+
# authentication key for cookie store
120+
key: secret123
121+
# domain of virtual hosts base host
122+
cookie_domain: gate.example.com
123+
124+
# proxy definitions
125+
proxy:
126+
- path: /
127+
host: elasticsearch.gate.example.com
128+
dest: http://127.0.0.1:9200
129+
130+
- path: /
131+
host: influxdb.gate.example.com
132+
dest: http://127.0.0.1:8086
133+
```
134+
112135
## License
113136

114137
MIT

conf.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ type AuthConf struct {
2626
}
2727

2828
type AuthSessionConf struct {
29-
Key string `yaml:"key"`
29+
Key string `yaml:"key"`
30+
CookieDomain string `yaml:"cookie_domain"`
3031
}
3132

3233
type AuthInfoConf struct {
@@ -42,6 +43,7 @@ type ProxyConf struct {
4243
Path string `yaml:"path"`
4344
Dest string `yaml:"dest"`
4445
Strip bool `yaml:"strip_path"`
46+
Host string `yaml:"host"`
4547
}
4648

4749
func ParseConf(path string) (*Conf, error) {

httpd.go

+70-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ type User struct {
2525
Email string
2626
}
2727

28+
type Backend struct {
29+
Host string
30+
URL *url.URL
31+
Strip bool
32+
StripPath string
33+
}
34+
35+
const (
36+
BackendHostHeader = "X-Gate-Backend-Host"
37+
)
38+
2839
func NewServer(conf *Conf) *Server {
2940
return &Server{conf}
3041
}
@@ -33,12 +44,19 @@ func (s *Server) Run() error {
3344
m := martini.Classic()
3445
a := NewAuthenticator(s.Conf)
3546

36-
m.Use(sessions.Sessions("session", sessions.NewCookieStore([]byte(s.Conf.Auth.Session.Key))))
47+
cookieStore := sessions.NewCookieStore([]byte(s.Conf.Auth.Session.Key))
48+
if domain := s.Conf.Auth.Session.CookieDomain; domain != "" {
49+
cookieStore.Options(sessions.Options{Domain: domain})
50+
}
51+
m.Use(sessions.Sessions("session", cookieStore))
3752
m.Use(a.Handler())
3853

3954
m.Use(loginRequired())
4055
m.Use(restrictRequest(s.Conf.Restrictions, a))
4156

57+
backendsFor := make(map[string][]Backend)
58+
backendIndex := make([]string, len(s.Conf.Proxies))
59+
4260
for i := range s.Conf.Proxies {
4361
p := s.Conf.Proxies[i]
4462

@@ -55,15 +73,24 @@ func (s *Server) Run() error {
5573
if err != nil {
5674
return err
5775
}
76+
backendsFor[p.Path] = append(backendsFor[p.Path], Backend{
77+
Host: p.Host,
78+
URL: u,
79+
Strip: p.Strip,
80+
StripPath: strip_path,
81+
})
82+
backendIndex[i] = p.Path
83+
log.Printf("register proxy host:%s path:%s dest:%s strip_path:%v", p.Host, strip_path, u.String(), p.Strip)
84+
}
5885

59-
proxy := httputil.NewSingleHostReverseProxy(u)
60-
if p.Strip {
61-
m.Any(p.Path, http.StripPrefix(strip_path, proxyHandleWrapper(u, proxy)))
62-
} else {
63-
m.Any(p.Path, proxyHandleWrapper(u, proxy))
86+
registered := make(map[string]bool)
87+
for _, path := range backendIndex {
88+
if registered[path] {
89+
continue
6490
}
65-
66-
log.Printf("register proxy path:%s dest:%s", strip_path, u.String())
91+
proxy := newVirtualHostReverseProxy(backendsFor[path])
92+
m.Any(path, proxyHandleWrapper(proxy))
93+
registered[path] = true
6794
}
6895

6996
path, err := filepath.Abs(s.Conf.Htdocs)
@@ -84,6 +111,34 @@ func (s *Server) Run() error {
84111
}
85112
}
86113

114+
func newVirtualHostReverseProxy(backends []Backend) http.Handler {
115+
bmap := make(map[string]Backend)
116+
for _, b := range backends {
117+
bmap[b.Host] = b
118+
}
119+
defaultBackend, ok := bmap[""]
120+
if !ok {
121+
defaultBackend = backends[0]
122+
}
123+
124+
director := func(req *http.Request) {
125+
b, ok := bmap[req.Host]
126+
if !ok {
127+
b = defaultBackend
128+
}
129+
req.URL.Scheme = b.URL.Scheme
130+
req.URL.Host = b.URL.Host
131+
if b.Strip {
132+
if p := strings.TrimPrefix(req.URL.Path, b.StripPath); len(p) < len(req.URL.Path) {
133+
req.URL.Path = "/" + p
134+
}
135+
}
136+
req.Header.Set(BackendHostHeader, req.URL.Host)
137+
log.Println("backend url", req.URL.String())
138+
}
139+
return &httputil.ReverseProxy{Director: director}
140+
}
141+
87142
func isWebsocket(r *http.Request) bool {
88143
if strings.ToLower(r.Header.Get("Connection")) == "upgrade" &&
89144
strings.ToLower(r.Header.Get("Upgrade")) == "websocket" {
@@ -93,11 +148,16 @@ func isWebsocket(r *http.Request) bool {
93148
}
94149
}
95150

96-
func proxyHandleWrapper(u *url.URL, handler http.Handler) http.Handler {
151+
func proxyHandleWrapper(handler http.Handler) http.Handler {
152+
proxy, _ := handler.(*httputil.ReverseProxy)
153+
director := proxy.Director
154+
97155
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
98156
// websocket?
99157
if isWebsocket(r) {
100-
target := u.Host
158+
director(r) // rewrite request headers for backend
159+
target := r.Header.Get(BackendHostHeader)
160+
101161
if strings.HasPrefix(r.URL.Path, "/") == false {
102162
r.URL.Path = "/" + r.URL.Path
103163
}

0 commit comments

Comments
 (0)