Skip to content

Commit 9e682b9

Browse files
committed
feat(proxy): add reverse proxy
1 parent f6f04a1 commit 9e682b9

File tree

9 files changed

+186
-15
lines changed

9 files changed

+186
-15
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ server [options]
4848
Root directory of the server.
4949
Defaults to current working directory.
5050
51+
-x|--fallback-proxy <separator><url-path><separator><proxy-target> ...
52+
If local resource under <url-path> not found, reverse proxy to <proxy-target>.
53+
<proxy-target> must be the format of schema://host/path.
54+
-X|--always-proxy <separator><url-path><separator><proxy-target> ...
55+
Always reverse proxy from <url-path> to <proxy-target>.
56+
<proxy-target> must be the format of schema://host/path.
57+
--ignore-proxy-target-bad-cert
58+
Ignore proxy target server's certificate validation issue.
59+
5160
-a|--alias <separator><url-path><separator><fs-path> ...
5261
Set path alias. e.g. ":/doc:/usr/share/doc"
5362

src/param/cli.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ func init() {
2121
err = options.AddFlagsValue("root", []string{"-r", "--root"}, "GHFS_ROOT", ".", "root directory of server")
2222
serverErrHandler.CheckFatal(err)
2323

24+
err = options.AddFlagsValues("fallbackproxies", []string{"-x", "--fallback-proxy"}, "", nil, "reverse proxy to target if local resource not found, <sep><url><sep><target>, e.g. :/doc:http://remote/doc")
25+
serverErrHandler.CheckFatal(err)
26+
27+
err = options.AddFlagsValues("alwaysproxies", []string{"-X", "--always-proxy"}, "", nil, "reverse proxy to target shadows local resource, <sep><url><sep><target>, e.g. :/doc:http://remote/doc")
28+
serverErrHandler.CheckFatal(err)
29+
30+
err = options.AddFlag("ignorebadproxycert", "--ignore-proxy-target-bad-cert", "", "ignore proxy target bad certificate")
31+
serverErrHandler.CheckFatal(err)
32+
2433
err = options.AddFlagsValues("aliases", []string{"-a", "--alias"}, "", nil, "set alias path, <sep><url><sep><path>, e.g. :/doc:/usr/share/doc")
2534
serverErrHandler.CheckFatal(err)
2635

@@ -177,6 +186,7 @@ func doParseCli() []*Param {
177186

178187
// normalize option
179188
param.Root, _ = result.GetString("root")
189+
param.IgnoreProxyTargetBadCert = result.HasKey("ignorebadproxycert")
180190
param.GlobalUpload = result.HasKey("globalupload")
181191
param.GlobalArchive = result.HasKey("globalarchive")
182192
param.GlobalCors = result.HasKey("globalcors")
@@ -199,6 +209,12 @@ func doParseCli() []*Param {
199209

200210
param.ListenTLS, _ = result.GetStrings("listentls")
201211

212+
// normalize proxies
213+
arrFallbackProxies, _ := result.GetStrings("fallbackproxies")
214+
param.FallbackProxies = normalizeProxyMaps(arrFallbackProxies)
215+
arrAlwaysProxies, _ := result.GetStrings("alwaysproxies")
216+
param.AlwaysProxies = normalizeProxyMaps(arrAlwaysProxies)
217+
202218
// normalize aliases
203219
arrAlias, _ := result.GetStrings("aliases")
204220
param.Aliases = normalizePathMaps(arrAlias)

src/param/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ type user struct {
88
}
99

1010
type Param struct {
11-
Root string
11+
Root string
12+
13+
FallbackProxies map[string]string
14+
AlwaysProxies map[string]string
15+
IgnoreProxyTargetBadCert bool
16+
1217
Aliases map[string]string
1318

1419
GlobalUpload bool

src/param/util.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ func normalizePathMaps(inputs []string) map[string]string {
4646
return maps
4747
}
4848

49+
func normalizeProxyMaps(inputs []string) map[string]string {
50+
maps := map[string]string{}
51+
52+
for _, input := range inputs {
53+
urlPath, target, ok := splitMapping(input)
54+
if !ok {
55+
continue
56+
}
57+
58+
cleanUrlPath := util.CleanUrlPath(urlPath)
59+
if len(cleanUrlPath) > 1 && urlPath[len(urlPath)-1] == '/' {
60+
cleanUrlPath += "/"
61+
}
62+
maps[cleanUrlPath] = target
63+
}
64+
65+
return maps
66+
}
67+
4968
func normalizeUrlPaths(inputs []string) []string {
5069
outputs := make([]string, 0, len(inputs))
5170

src/reverseProxy/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package reverseProxy
2+
3+
import (
4+
"../util"
5+
"net/http"
6+
"net/http/httputil"
7+
"net/url"
8+
)
9+
10+
func NewReverseProxy(target *url.URL, tr http.RoundTripper) *httputil.ReverseProxy {
11+
targetRawQuery := target.RawQuery
12+
targetHost := target.Host
13+
14+
director := func(req *http.Request) {
15+
req.URL.Scheme = target.Scheme
16+
req.URL.Host = targetHost
17+
req.URL.Path = util.CleanUrlPath(target.Path + "/" + req.URL.Path)
18+
if len(targetRawQuery) == 0 || len(req.URL.RawQuery) == 0 {
19+
req.URL.RawQuery = targetRawQuery + req.URL.RawQuery
20+
} else {
21+
req.URL.RawQuery = targetRawQuery + "&" + req.URL.RawQuery
22+
}
23+
24+
// host header
25+
req.Host = targetHost
26+
}
27+
28+
return &httputil.ReverseProxy{
29+
Transport: tr,
30+
Director: director,
31+
}
32+
}

src/serveMux/main.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package serveMux
22

33
import (
44
"../param"
5+
"../reverseProxy"
56
"../serverErrHandler"
67
"../serverHandler"
78
"../serverLog"
89
"../tpl"
910
"../user"
11+
"crypto/tls"
1012
"net/http"
13+
"net/url"
1114
)
1215

1316
func NewServeMux(
@@ -35,6 +38,12 @@ func NewServeMux(
3538
errorHandler.LogError(users.AddSha512(u.Username, u.Password))
3639
}
3740

41+
tr := &http.Transport{
42+
TLSClientConfig: &tls.Config{InsecureSkipVerify: p.IgnoreProxyTargetBadCert},
43+
}
44+
fallbackProxies := mapToReverseProxy(p.FallbackProxies, tr)
45+
alwaysProxies := mapToReverseProxy(p.AlwaysProxies, tr)
46+
3847
tplObj, err := tpl.LoadPage(p.Template)
3948
errorHandler.LogError(err)
4049

@@ -45,7 +54,7 @@ func NewServeMux(
4554

4655
handlers := map[string]http.Handler{}
4756
for urlPath, fsPath := range aliases {
48-
handlers[urlPath] = serverHandler.NewHandler(fsPath, urlPath, p, users, tplObj, logger, errorHandler)
57+
handlers[urlPath] = serverHandler.NewHandler(fsPath, urlPath, p, users, fallbackProxies, alwaysProxies, tplObj, logger, errorHandler)
4958
}
5059

5160
// create ServeMux
@@ -59,3 +68,19 @@ func NewServeMux(
5968

6069
return serveMux
6170
}
71+
72+
func mapToReverseProxy(input map[string]string, tr http.RoundTripper) map[string]http.Handler {
73+
maps := map[string]http.Handler{}
74+
75+
for inUrl, target := range input {
76+
targetUrl, err := url.Parse(target)
77+
if err != nil || len(targetUrl.Scheme) == 0 || len(targetUrl.Host) == 0 {
78+
continue
79+
}
80+
var proxyHandler http.Handler = reverseProxy.NewReverseProxy(targetUrl, tr)
81+
proxyHandler = http.StripPrefix(inUrl, proxyHandler)
82+
maps[inUrl] = proxyHandler
83+
}
84+
85+
return maps
86+
}

src/serverHandler/main.go

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import (
1313
type handler struct {
1414
root string
1515
urlPrefix string
16-
aliases map[string]string
16+
17+
fallbackProxies map[string]http.Handler
18+
alwaysProxies map[string]http.Handler
19+
aliases map[string]string
1720

1821
globalUpload bool
1922
uploadUrls []string
@@ -47,6 +50,7 @@ type handler struct {
4750
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4851
go h.logRequest(r)
4952

53+
// assert
5054
if len(r.URL.RawQuery) > 0 {
5155
queryValues := r.URL.Query()
5256
assertPath := queryValues.Get("assert")
@@ -56,7 +60,10 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5660
}
5761
}
5862

59-
data := h.getResponseData(r)
63+
alwaysProxyHandler := getProxyHandler(r, h.alwaysProxies)
64+
65+
// data
66+
data := h.getResponseData(r, alwaysProxyHandler == nil)
6067
if len(data.Errors) > 0 {
6168
go func() {
6269
for _, err := range data.Errors {
@@ -66,25 +73,39 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6673
}
6774
file := data.File
6875
if file != nil {
69-
defer func() {
70-
err := file.Close()
71-
h.errHandler.LogError(err)
72-
}()
76+
defer file.Close()
7377
}
7478

7579
if data.NeedAuth && !h.auth(w, r) {
7680
return
7781
}
7882

83+
if data.CanCors {
84+
h.cors(w, r)
85+
}
86+
87+
// always proxy
88+
if alwaysProxyHandler != nil {
89+
proxy(w, r, alwaysProxyHandler)
90+
return
91+
}
92+
93+
// fallback proxy
94+
if data.hasNotFoundError {
95+
fallbackProxyHandler := getProxyHandler(r, h.fallbackProxies)
96+
if fallbackProxyHandler != nil {
97+
proxy(w, r, fallbackProxyHandler)
98+
return
99+
}
100+
}
101+
79102
if data.CanUpload && r.Method == "POST" {
80103
h.saveUploadFiles(data.handlerReqPath, r)
81104
http.Redirect(w, r, r.RequestURI, http.StatusFound)
82105
return
83106
}
84107

85-
if data.CanCors {
86-
h.cors(w, r)
87-
}
108+
// regular flows
88109

89110
if data.CanArchive && len(r.URL.RawQuery) > 0 {
90111
switch r.URL.RawQuery {
@@ -113,14 +134,19 @@ func NewHandler(
113134
urlPrefix string,
114135
p *param.Param,
115136
users user.Users,
137+
fallbackProxies map[string]http.Handler,
138+
alwaysProxies map[string]http.Handler,
116139
template *template.Template,
117140
logger *serverLog.Logger,
118141
errHandler *serverErrHandler.ErrHandler,
119142
) *handler {
120143
h := &handler{
121144
root: root,
122145
urlPrefix: urlPrefix,
123-
aliases: p.Aliases,
146+
147+
fallbackProxies: fallbackProxies,
148+
alwaysProxies: alwaysProxies,
149+
aliases: p.Aliases,
124150

125151
globalUpload: p.GlobalUpload,
126152
uploadUrls: p.UploadUrls,

src/serverHandler/proxy.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package serverHandler
2+
3+
import (
4+
"../util"
5+
"net/http"
6+
)
7+
8+
func getProxyHandler(r *http.Request, proxies map[string]http.Handler) http.Handler {
9+
if len(proxies) == 0 {
10+
return nil
11+
}
12+
13+
maxUrlLen := 0
14+
var proxyHandler http.Handler = nil
15+
16+
requestUrlPath := r.RequestURI
17+
for urlPath, handler := range proxies {
18+
if len(requestUrlPath) < len(urlPath) || !util.HasUrlPrefixDir(requestUrlPath, urlPath) {
19+
continue
20+
}
21+
22+
urlLen := len(urlPath)
23+
if urlLen > maxUrlLen {
24+
maxUrlLen = urlLen
25+
proxyHandler = handler
26+
}
27+
}
28+
29+
return proxyHandler
30+
}
31+
32+
func proxy(w http.ResponseWriter, r *http.Request, proxyHandler http.Handler) {
33+
w.Header().Set("Cache-Control", "public, max-age=0")
34+
proxyHandler.ServeHTTP(w, r)
35+
}

src/serverHandler/responseData.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ func getPathEntries(path string, tailSlash bool) []*pathEntry {
7474
return pathEntries
7575
}
7676

77-
func stat(reqFsPath string) (file *os.File, item os.FileInfo, err error) {
77+
func stat(reqFsPath string, visitFs bool) (file *os.File, item os.FileInfo, err error) {
78+
if !visitFs {
79+
return
80+
}
81+
7882
file, err = os.Open(reqFsPath)
7983
if err != nil {
8084
return
@@ -207,7 +211,7 @@ func getItemName(item os.FileInfo, r *http.Request) (itemName string) {
207211
return
208212
}
209213

210-
func (h *handler) getResponseData(r *http.Request) (data *responseData) {
214+
func (h *handler) getResponseData(r *http.Request, visitFs bool) (data *responseData) {
211215
requestUri := r.URL.Path
212216
tailSlash := requestUri[len(requestUri)-1] == '/'
213217

@@ -232,7 +236,7 @@ func (h *handler) getResponseData(r *http.Request) (data *responseData) {
232236
reqFsPath = path.Clean(h.root + reqPath)
233237
}
234238

235-
file, item, _statErr := stat(reqFsPath)
239+
file, item, _statErr := stat(reqFsPath, visitFs)
236240
if _statErr != nil {
237241
errs = append(errs, _statErr)
238242
notFound = os.IsNotExist(_statErr)

0 commit comments

Comments
 (0)