- commit
- b910e2a
- parent
- 5d921ed
- author
- Eric Bower
- date
- 2024-04-03 14:23:25 +0000 UTC
feat(pgs): 301 redirect urls that are directories without trailing slash (#116)
3 files changed,
+107,
-28
+1,
-1
1@@ -179,7 +179,7 @@ func (h *AssetHandler) handle(w http.ResponseWriter, r *http.Request) {
2 status := http.StatusOK
3 attempts := []string{}
4 for _, fp := range routes {
5- if hasProtocol(fp.Filepath) {
6+ if hasProtocol(fp.Filepath) || fp.Status == http.StatusMovedPermanently {
7 h.Logger.Info(
8 "redirecting request",
9 "bucket", h.Bucket.Name,
+39,
-18
1@@ -19,6 +19,9 @@ type HttpReply struct {
2 }
3
4 func expandRoute(projectName, fp string, status int) []*HttpReply {
5+ if fp == "" {
6+ fp = "/"
7+ }
8 mimeType := storage.GetMimeType(fp)
9 fname := filepath.Base(fp)
10 fdir := filepath.Dir(fp)
11@@ -33,9 +36,22 @@ func expandRoute(projectName, fp string, status int) []*HttpReply {
12 return routes
13 }
14
15- if fname != "" && fname != "/" {
16- // we need to accommodate routes that are just directories
17- // and point the user to the index.html of each root dir.
18+ // we know it's a directory so send the index.html for it
19+ if strings.HasSuffix(fp, "/") {
20+ dirRoute := shared.GetAssetFileName(&utils.FileEntry{
21+ Filepath: filepath.Join(projectName, fp, "index.html"),
22+ })
23+
24+ routes = append(
25+ routes,
26+ &HttpReply{Filepath: dirRoute, Status: status},
27+ )
28+ } else {
29+ if fname == "." {
30+ return routes
31+ }
32+
33+ // pretty urls where we just append .html to base of fp
34 nameRoute := shared.GetAssetFileName(&utils.FileEntry{
35 Filepath: filepath.Join(
36 projectName,
37@@ -50,15 +66,6 @@ func expandRoute(projectName, fp string, status int) []*HttpReply {
38 )
39 }
40
41- dirRoute := shared.GetAssetFileName(&utils.FileEntry{
42- Filepath: filepath.Join(projectName, fp, "index.html"),
43- })
44-
45- routes = append(
46- routes,
47- &HttpReply{Filepath: dirRoute, Status: status},
48- )
49-
50 return routes
51 }
52
53@@ -68,13 +75,7 @@ func hasProtocol(url string) bool {
54 }
55
56 func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpReply {
57- notFound := &HttpReply{
58- Filepath: filepath.Join(projectName, "404.html"),
59- Status: http.StatusNotFound,
60- }
61-
62 rts := expandRoute(projectName, fp, http.StatusOK)
63-
64 fext := filepath.Ext(fp)
65 // add route as-is without expansion if there is a file ext
66 if fp != "" && fext != "" {
67@@ -130,6 +131,26 @@ func calcRoutes(projectName, fp string, userRedirects []*RedirectRule) []*HttpRe
68 }
69 }
70
71+ // filename without extension mean we might have a directory
72+ // so add a trailing slash with a 301
73+ if !strings.HasSuffix(fp, "/") && fp != "" && fext == "" {
74+ redirectRoute := shared.GetAssetFileName(&utils.FileEntry{
75+ Filepath: fp + "/",
76+ })
77+ rts = append(
78+ rts,
79+ &HttpReply{Filepath: redirectRoute, Status: http.StatusMovedPermanently},
80+ )
81+ // 301 is always actived so anything after this branch will never
82+ // be executed ... so return early
83+ // return rts
84+ }
85+
86+ notFound := &HttpReply{
87+ Filepath: filepath.Join(projectName, "404.html"),
88+ Status: http.StatusNotFound,
89+ }
90+
91 rts = append(rts,
92 notFound,
93 )
+67,
-9
1@@ -57,16 +57,24 @@ func TestCalcRoutes(t *testing.T) {
2 },
3 {
4 Name: "subdirectory-bare",
5- Actual: calcRoutes("test", "/nice", []*RedirectRule{}),
6+ Actual: calcRoutes("test", "/nice/", []*RedirectRule{}),
7 Expected: []*HttpReply{
8- {Filepath: "test/nice.html", Status: 200},
9 {Filepath: "test/nice/index.html", Status: 200},
10 {Filepath: "test/404.html", Status: 404},
11 },
12 },
13+ {
14+ Name: "trailing-slash",
15+ Actual: calcRoutes("test", "/folder", []*RedirectRule{}),
16+ Expected: []*HttpReply{
17+ {Filepath: "test/folder.html", Status: 200},
18+ {Filepath: "/folder/", Status: 301},
19+ {Filepath: "test/404.html", Status: 404},
20+ },
21+ },
22 {
23 Name: "spa",
24- Actual: calcRoutes("test", "/nice", []*RedirectRule{
25+ Actual: calcRoutes("test", "/nice.html", []*RedirectRule{
26 {
27 From: "/*",
28 To: "/index.html",
29@@ -75,7 +83,6 @@ func TestCalcRoutes(t *testing.T) {
30 }),
31 Expected: []*HttpReply{
32 {Filepath: "test/nice.html", Status: 200},
33- {Filepath: "test/nice/index.html", Status: 200},
34 {Filepath: "test/index.html", Status: 200},
35 {Filepath: "test/404.html", Status: 404},
36 },
37@@ -103,8 +110,28 @@ func TestCalcRoutes(t *testing.T) {
38 ),
39 Expected: []*HttpReply{
40 {Filepath: "test/wow.html", Status: 200},
41- {Filepath: "test/wow/index.html", Status: 200},
42 {Filepath: "test/index.html", Status: 301},
43+ {Filepath: "/wow/", Status: 301},
44+ {Filepath: "test/404.html", Status: 404},
45+ },
46+ },
47+ {
48+ Name: "redirectToPico",
49+ Actual: calcRoutes(
50+ "test",
51+ "/tester1",
52+ []*RedirectRule{
53+ {
54+ From: "/tester1",
55+ To: "https://pico.sh",
56+ Status: 301,
57+ },
58+ },
59+ ),
60+ Expected: []*HttpReply{
61+ {Filepath: "test/tester1.html", Status: 200},
62+ {Filepath: "https://pico.sh", Status: 301},
63+ {Filepath: "/tester1/", Status: 301},
64 {Filepath: "test/404.html", Status: 404},
65 },
66 },
67@@ -112,17 +139,28 @@ func TestCalcRoutes(t *testing.T) {
68 Name: "root",
69 Actual: calcRoutes(
70 "test",
71- "/wow",
72+ "",
73+ []*RedirectRule{},
74+ ),
75+ Expected: []*HttpReply{
76+ {Filepath: "test/index.html", Status: 200},
77+ {Filepath: "test/404.html", Status: 404},
78+ },
79+ },
80+ {
81+ Name: "redirectToRoot",
82+ Actual: calcRoutes(
83+ "test",
84+ "/wow/",
85 []*RedirectRule{
86 {
87- From: "/wow",
88+ From: "/wow/",
89 To: "/",
90 Status: 301,
91 },
92 },
93 ),
94 Expected: []*HttpReply{
95- {Filepath: "test/wow.html", Status: 200},
96 {Filepath: "test/wow/index.html", Status: 200},
97 {Filepath: "test/index.html", Status: 301},
98 {Filepath: "test/404.html", Status: 404},
99@@ -144,11 +182,31 @@ func TestCalcRoutes(t *testing.T) {
100 ),
101 Expected: []*HttpReply{
102 {Filepath: "test/index.html", Status: 301},
103+ {Filepath: "/wow/", Status: 301},
104 {Filepath: "test/404.html", Status: 404},
105 },
106 },
107 {
108 Name: "redirectFullUrl",
109+ Actual: calcRoutes(
110+ "test",
111+ "/wow.html",
112+ []*RedirectRule{
113+ {
114+ From: "/wow",
115+ To: "https://pico.sh",
116+ Status: 301,
117+ },
118+ },
119+ ),
120+ Expected: []*HttpReply{
121+ {Filepath: "test/wow.html", Status: 200},
122+ {Filepath: "https://pico.sh", Status: 301},
123+ {Filepath: "test/404.html", Status: 404},
124+ },
125+ },
126+ {
127+ Name: "redirectFullUrlDirectory",
128 Actual: calcRoutes(
129 "test",
130 "/wow",
131@@ -162,8 +220,8 @@ func TestCalcRoutes(t *testing.T) {
132 ),
133 Expected: []*HttpReply{
134 {Filepath: "test/wow.html", Status: 200},
135- {Filepath: "test/wow/index.html", Status: 200},
136 {Filepath: "https://pico.sh", Status: 301},
137+ {Filepath: "/wow/", Status: 301},
138 {Filepath: "test/404.html", Status: 404},
139 },
140 },