- commit
- ccb7239
- parent
- 2f441a5
- author
- Eric Bower
- date
- 2024-04-05 18:54:28 +0000 UTC
feat(pgs): rewrite support feat(pgs): rewrite proxy api support
3 files changed,
+194,
-17
+15,
-0
1@@ -212,6 +212,21 @@ func (h *AssetHandler) handle(w http.ResponseWriter, r *http.Request) {
2 )
3 http.Redirect(w, r, fp.Filepath, fp.Status)
4 return
5+ } else if hasProtocol(fp.Filepath) {
6+ // fetch content from url and serve it
7+ resp, err := http.Get(fp.Filepath)
8+ if err != nil {
9+ http.Error(w, "404 not found", http.StatusNotFound)
10+ return
11+ }
12+
13+ w.Header().Set("content-type", resp.Header.Get("content-type"))
14+ w.WriteHeader(status)
15+ _, err = io.Copy(w, resp.Body)
16+ if err != nil {
17+ h.Logger.Error("io copy", "err", err.Error())
18+ }
19+ return
20 }
21
22 attempts = append(attempts, fp.Filepath)
R pgs/cal_route.go =>
pgs/calc_route.go
+103,
-11
1@@ -3,6 +3,7 @@ package pgs
2 import (
3 "fmt"
4 "net/http"
5+ "net/url"
6 "path/filepath"
7 "regexp"
8 "strings"
9@@ -73,6 +74,90 @@ func checkIsRedirect(status int) bool {
10 return status >= 300 && status <= 399
11 }
12
13+func correlatePlaceholder(orig, pattern string) string {
14+ origList := splitFp(orig)
15+ patternList := splitFp(pattern)
16+ nextList := []string{}
17+ for idx, item := range patternList {
18+ if len(origList) <= idx {
19+ continue
20+ }
21+
22+ if strings.HasPrefix(item, ":") {
23+ nextList = append(nextList, origList[idx])
24+ } else if item == origList[idx] {
25+ nextList = append(nextList, origList[idx])
26+ }
27+ }
28+ return filepath.Join(nextList...)
29+}
30+
31+func splitFp(str string) []string {
32+ ls := strings.Split(str, "/")
33+ fin := []string{}
34+ for _, l := range ls {
35+ if l == "" {
36+ continue
37+ }
38+ fin = append(fin, l)
39+ }
40+ return fin
41+}
42+
43+func genRedirectRoute(actual string, fromStr string, to string) string {
44+ if to == "/" {
45+ return to
46+ }
47+ actualList := splitFp(actual)
48+ fromList := splitFp(fromStr)
49+ prefix := ""
50+ var toList []string
51+ if hasProtocol(to) {
52+ u, _ := url.Parse(to)
53+ if u.Path == "" {
54+ return to
55+ }
56+ toList = splitFp(u.Path)
57+ prefix = u.Scheme + "://" + u.Host
58+ } else {
59+ toList = splitFp(to)
60+ }
61+
62+ mapper := map[string]string{}
63+ for idx, item := range fromList {
64+ if len(actualList) <= idx {
65+ continue
66+ }
67+
68+ if strings.HasPrefix(item, ":") {
69+ mapper[item] = actualList[idx]
70+ }
71+ if item == "*" {
72+ splat := strings.Join(actualList[idx:], "/")
73+ mapper[":splat"] = splat
74+ break
75+ }
76+ }
77+
78+ fin := []string{"/"}
79+
80+ for _, item := range toList {
81+ if item == ":splat" {
82+ fin = append(fin, mapper[item])
83+ } else if mapper[item] != "" {
84+ fin = append(fin, mapper[item])
85+ } else {
86+ fin = append(fin, item)
87+ }
88+ }
89+
90+ result := prefix + filepath.Join(fin...)
91+ if !strings.HasSuffix(result, "/") && (strings.HasSuffix(to, "/") || strings.HasSuffix(actual, "/")) {
92+ result += "/"
93+ }
94+ return result
95+}
96+
97 func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpReply {
98 rts := []*HttpReply{}
99 // add route as-is without expansion
100@@ -87,32 +172,33 @@ func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpRe
101
102 // user routes
103 for _, redirect := range userRedirects {
104- // this doesn't make sense and it forbidden
105+ // this doesn't make sense so it is forbidden
106 if redirect.From == redirect.To {
107 continue
108 }
109
110- from := redirect.From
111- if !strings.HasSuffix(redirect.From, "*") {
112- from = strings.TrimSuffix(redirect.From, "/") + "/?"
113- }
114- rr := regexp.MustCompile(from)
115+ // hack: make suffix `/` optional when matching
116+ from := filepath.Clean(redirect.From)
117+ fromMatcher := correlatePlaceholder(fp, from)
118+ rr := regexp.MustCompile(fromMatcher)
119 match := rr.FindStringSubmatch(fp)
120 if len(match) > 0 {
121 isRedirect := checkIsRedirect(redirect.Status)
122- if !isRedirect {
123+ if !isRedirect && !hasProtocol(redirect.To) {
124+ route := genRedirectRoute(fp, from, redirect.To)
125 // wipe redirect rules to prevent infinite loops
126 // as such we only support a single hop for user defined redirects
127- redirectRoutes := calcRoutes(projectName, redirect.To, []*RedirectRule{})
128+ redirectRoutes := calcRoutes(projectName, route, []*RedirectRule{})
129 rts = append(rts, redirectRoutes...)
130 return rts
131 }
132
133+ route := genRedirectRoute(fp, from, redirect.To)
134 userReply := []*HttpReply{}
135 var rule *HttpReply
136 if redirect.To != "" {
137 rule = &HttpReply{
138- Filepath: redirect.To,
139+ Filepath: route,
140 Status: redirect.Status,
141 Query: redirect.Query,
142 }
143@@ -124,8 +210,14 @@ func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpRe
144 } else {
145 rts = append(rts, userReply...)
146 }
147- // quit after first match
148- break
149+
150+ if hasProtocol(redirect.To) {
151+ // redirecting to another site so we should bail early
152+ return rts
153+ } else {
154+ // quit after first match
155+ break
156+ }
157 }
158 }
159
+76,
-6
1@@ -140,8 +140,6 @@ func TestCalcRoutes(t *testing.T) {
2 {Filepath: "test/tester1", Status: 200},
3 {Filepath: "test/tester1.html", Status: 200},
4 {Filepath: "https://pico.sh", Status: 301},
5- {Filepath: "/tester1/", Status: 301},
6- {Filepath: "test/404.html", Status: 404},
7 },
8 },
9 {
10@@ -211,8 +209,6 @@ func TestCalcRoutes(t *testing.T) {
11 Expected: []*HttpReply{
12 {Filepath: "test/wow.html", Status: 200},
13 {Filepath: "https://pico.sh", Status: 301},
14- {Filepath: "/wow.html/", Status: 301},
15- {Filepath: "test/404.html", Status: 404},
16 },
17 },
18 {
19@@ -232,8 +228,6 @@ func TestCalcRoutes(t *testing.T) {
20 {Filepath: "test/wow", Status: 200},
21 {Filepath: "test/wow.html", Status: 200},
22 {Filepath: "https://pico.sh", Status: 301},
23- {Filepath: "/wow/", Status: 301},
24- {Filepath: "test/404.html", Status: 404},
25 },
26 },
27 {
28@@ -353,6 +347,82 @@ func TestCalcRoutes(t *testing.T) {
29 {Filepath: "public/404.html", Status: 404},
30 },
31 },
32+ {
33+ Name: "redirect-to-site",
34+ Actual: calcRoutes(
35+ "public",
36+ "/",
37+ []*RedirectRule{
38+ {
39+ From: "/*",
40+ To: "https://my-other-site.pgs.sh/:splat",
41+ Status: 200,
42+ },
43+ },
44+ ),
45+ Expected: []*HttpReply{
46+ {Filepath: "public/index.html", Status: 200},
47+ {Filepath: "https://my-other-site.pgs.sh/", Status: 200},
48+ },
49+ },
50+ {
51+ Name: "redirect-to-site-subdir",
52+ Actual: calcRoutes(
53+ "public",
54+ "/plugin/nice/",
55+ []*RedirectRule{
56+ {
57+ From: "/*",
58+ To: "https://my-other-site.pgs.sh/:splat",
59+ Status: 200,
60+ },
61+ },
62+ ),
63+ Expected: []*HttpReply{
64+ {Filepath: "public/plugin/nice/index.html", Status: 200},
65+ {Filepath: "https://my-other-site.pgs.sh/plugin/nice/", Status: 200},
66+ },
67+ },
68+ {
69+ Name: "redirect-to-another-pgs-site",
70+ Actual: calcRoutes(
71+ "public",
72+ "/my-site/index.html",
73+ []*RedirectRule{
74+ {
75+ From: "/my-site/*",
76+ To: "https://my-other-site.pgs.sh/:splat",
77+ Status: 200,
78+ },
79+ },
80+ ),
81+ Expected: []*HttpReply{
82+ {Filepath: "public/my-site/index.html", Status: 200},
83+ {Filepath: "https://my-other-site.pgs.sh/index.html", Status: 200},
84+ },
85+ },
86+ {
87+ Name: "redirect-placeholders",
88+ Actual: calcRoutes(
89+ "public",
90+ "/news/02/12/2004/my-story",
91+ []*RedirectRule{
92+ {
93+ From: "/news/:month/:date/:year/*",
94+ To: "/blog/:year/:month/:date/:splat",
95+ Status: 200,
96+ },
97+ },
98+ ),
99+ Expected: []*HttpReply{
100+ {Filepath: "public/news/02/12/2004/my-story", Status: 200},
101+ {Filepath: "public/news/02/12/2004/my-story.html", Status: 200},
102+ {Filepath: "public/blog/2004/02/12/my-story", Status: 200},
103+ {Filepath: "public/blog/2004/02/12/my-story.html", Status: 200},
104+ {Filepath: "/blog/2004/02/12/my-story/", Status: 301},
105+ {Filepath: "public/404.html", Status: 404},
106+ },
107+ },
108 }
109
110 for _, fixture := range fixtures {