Skip to content

Commit 5344ae8

Browse files
authored
Merge pull request #12 from adamreese/wildcard-redirects
feat(redirect): add wildcard redirect support; spin sdk bump; CI test additions
2 parents 76c30c6 + c1f5b4a commit 5344ae8

File tree

10 files changed

+191
-24
lines changed

10 files changed

+191
-24
lines changed

.github/workflows/build.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ jobs:
1414
steps:
1515
- uses: actions/checkout@v2
1616

17+
- name: Setup Wasmtime
18+
uses: bytecodealliance/actions/wasmtime/setup@v1
19+
with:
20+
version: "31.0.0"
21+
1722
- name: "Install Go"
1823
uses: actions/setup-go@v3
1924
with:
@@ -24,7 +29,10 @@ jobs:
2429
wget https://github.com/tinygo-org/tinygo/releases/download/v0.37.0/tinygo_0.37.0_amd64.deb
2530
sudo dpkg -i tinygo_0.37.0_amd64.deb
2631
27-
- name: Make
32+
- name: Build
2833
env:
2934
ENABLE_WASM_OPT: false
3035
run: make build
36+
37+
- name: Test
38+
run: make test

.github/workflows/release.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ jobs:
1616
- name: set the release version (tag)
1717
run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
1818

19+
- name: Setup Wasmtime
20+
uses: bytecodealliance/actions/wasmtime/setup@v1
21+
with:
22+
version: "31.0.0"
23+
1924
- name: "Install Go"
2025
uses: actions/setup-go@v3
2126
with:
@@ -26,11 +31,14 @@ jobs:
2631
wget https://github.com/tinygo-org/tinygo/releases/download/v0.37.0/tinygo_0.37.0_amd64.deb
2732
sudo dpkg -i tinygo_0.37.0_amd64.deb
2833
29-
- name: Make
34+
- name: Build
3035
env:
3136
ENABLE_WASM_OPT: false
3237
run: make build
3338

39+
- name: Test
40+
run: make test
41+
3442
- name: generate checksums
3543
run: |
3644
sha256sum redirect.wasm > checksums-${{ env.RELEASE_VERSION }}.txt

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ build:
66
ifeq ($(ENABLE_WASM_OPT),true)
77
wasm-opt -Os -o redirect.wasm redirect.wasm
88
endif
9+
10+
.PHONY: test
11+
test:
12+
tinygo test -target=wasip1 -gc=leaking -v ./redirect/...

README.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ The `spin-redirect` component can be configured to address your needs in differe
1313

1414
The following table outlines available configuration values:
1515

16-
| Key | Description | Default Value |
17-
|---------------|-------------------------------------------------------|------------------|
18-
| `destination` | Where should the component redirect to | `/` |
19-
| `statuscode` | What HTTP status code should be used when redirecting | `302` |
16+
| Key | Description | Default Value |
17+
|----------------|-------------------------------------------------------------------------------------------------------------------------|---------------|
18+
| `destination` | Where should the component redirect to | `/` |
19+
| `statuscode` | What HTTP status code should be used when redirecting | `302` |
20+
| `include_path` | Whether to include the original request path on the destination redirect; see [wildcard redirects](#wildcard-redirects) | `false` |
21+
| `trim_prefix` | A specific prefix portion of the original request path to trim (when `include_path` is `true`) | |
2022

2123
The `spin-redirect` component tries to look up the config value in the Spin component configuration using the keys shown in the table above (lower case). If desired key is not present, it transforms the key to upper case (e.g., `DESTINATION`) and checks environment variables.
2224

@@ -73,3 +75,47 @@ statuscode="301"
7375
route = "/"
7476

7577
```
78+
79+
### Wildcard redirects
80+
81+
Wildcard redirects can be enabled by setting `include_path` to `true`. In the example below,
82+
all requests to the application will be redirected to `/foo`, with the original request path included,
83+
e.g. `/bar` will redirect to `/foo/bar`, `/baz` will redirect to `/foo/baz` and so on.
84+
85+
```toml
86+
spin_manifest_version = 2
87+
88+
[application]
89+
name = "wildcard-redirect"
90+
version = "0.1.0"
91+
92+
[[trigger.http]]
93+
id = "trigger-redirect-to-foo"
94+
component = "redirect-to-foo"
95+
route = "/..."
96+
97+
[component.redirect-to-foo]
98+
source = "modules/redirect.wasm"
99+
environment = { DESTINATION = "/foo", INCLUDE_PATH = "true" }
100+
```
101+
102+
A prefix portion of the original request path can be trimmed via the `trim_prefix` configuration.
103+
104+
In this example, the `/v1/` prefix is trimmed, such that requests to `/v1/bar` will redirect to `/v2/bar` and so on.
105+
106+
```toml
107+
spin_manifest_version = 2
108+
109+
[application]
110+
name = "wildcard-redirect"
111+
version = "0.1.0"
112+
113+
[[trigger.http]]
114+
id = "trigger-redirect-v1-to-v2"
115+
component = "redirect-v1-to-v2"
116+
route = "/v1/..."
117+
118+
[component.redirect-v1-to-v2]
119+
source = "modules/redirect.wasm"
120+
environment = { DESTINATION = "/v2", INCLUDE_PATH = "true", TRIM_PREFIX = "/v1/" }
121+
```

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ module github.com/fermyon/spin-redirect
22

33
go 1.24
44

5-
require github.com/fermyon/spin/sdk/go v1.4.2
5+
require github.com/spinframework/spin-go-sdk v0.0.0-20250310233821-2665ad7e30a4
66

77
require github.com/julienschmidt/httprouter v1.3.0 // indirect
8+
9+
replace github.com/spinframework/spin-go-sdk => github.com/fermyon/spin-go-sdk v0.0.0-20250310233821-2665ad7e30a4

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github.com/fermyon/spin/sdk/go v1.4.2 h1:4U2J2WooKptCa4zeBetKctz/DEJxv3RvGauW0bZ+e6U=
2-
github.com/fermyon/spin/sdk/go v1.4.2/go.mod h1:yb8lGesopgj/GwPzLPATxcOeqWZT/HjrzEFfwbztAXE=
1+
github.com/fermyon/spin-go-sdk v0.0.0-20250310233821-2665ad7e30a4 h1:er/5jOnjXEjx95+7iiioj5l/CwT5AQgM7Udr8xM/mDI=
2+
github.com/fermyon/spin-go-sdk v0.0.0-20250310233821-2665ad7e30a4/go.mod h1:9GoW1+MR0gN1OEinITtjPOzmu0dur3U6ty3pIH/gN24=
33
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
44
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=

main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package main
2+
3+
import (
4+
spinhttp "github.com/spinframework/spin-go-sdk/http"
5+
6+
"github.com/fermyon/spin-redirect/redirect"
7+
)
8+
9+
func init() {
10+
r := redirect.NewSpinRedirect()
11+
spinhttp.Handle(r.HandleFunc)
12+
}

config.go renamed to redirect/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
package main
1+
package redirect
22

33
import (
44
"os"
55
"strings"
66

7-
config "github.com/fermyon/spin/sdk/go/config"
7+
config "github.com/spinframework/spin-go-sdk/variables"
88
)
99

1010
type ConfigReader interface {

redirect.go renamed to redirect/redirect.go

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
package main
1+
package redirect
22

33
import (
44
"net/http"
5+
"net/url"
6+
"path"
57
"strconv"
6-
7-
spinhttp "github.com/fermyon/spin/sdk/go/http"
8+
"strings"
89
)
910

1011
const (
@@ -16,16 +17,12 @@ const (
1617
destinationKey string = "destination"
1718
// Key for loading desired HTTP status code
1819
statusCodeKey string = "statuscode"
20+
// Key to enable adding original path in redirect
21+
includePathKey string = "include_path"
22+
// Key for trimming a preceding portion of the included path
23+
trimPrefixKey string = "trim_prefix"
1924
)
2025

21-
func init() {
22-
r := NewSpinRedirect()
23-
spinhttp.Handle(r.handleFunc)
24-
}
25-
26-
func main() {
27-
}
28-
2926
// SpinRedirect is a struct that provides a handleFunc
3027
// for redirecting to a destination URL using configurable HTTP status code.
3128
type SpinRedirect struct {
@@ -39,11 +36,11 @@ func NewSpinRedirect() SpinRedirect {
3936
}
4037
}
4138

42-
func (s SpinRedirect) handleFunc(w http.ResponseWriter, r *http.Request) {
39+
func (s SpinRedirect) HandleFunc(w http.ResponseWriter, r *http.Request) {
4340
dest, _ := s.getDestination()
4441
code, _ := s.getStatusCode(r.Method)
4542

46-
w.Header().Set("Location", dest)
43+
w.Header().Set("Location", s.WithPath(dest, r))
4744
w.WriteHeader(code)
4845
}
4946

@@ -92,3 +89,21 @@ func isValidRedirectStatusCode(code int, method string) bool {
9289
}
9390
return false
9491
}
92+
93+
// WithPath returns the provided dest with the path portion of the request,
94+
// assuming the includePathKey has a value of true
95+
func (s SpinRedirect) WithPath(dest string, r *http.Request) string {
96+
includePath := s.cfg.Get(includePathKey)
97+
if includePath != "true" {
98+
return dest
99+
}
100+
101+
u, err := url.Parse(dest)
102+
if err != nil {
103+
return dest
104+
}
105+
106+
u.Path = path.Join(u.Path, strings.TrimPrefix(r.URL.Path, s.cfg.Get(trimPrefixKey)))
107+
108+
return u.String()
109+
}

redirect/redirect_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package redirect
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"testing"
7+
)
8+
9+
type testConfigReader struct {
10+
includePath string
11+
trimPrefix string
12+
}
13+
14+
func (c *testConfigReader) Get(key string) string {
15+
switch key {
16+
case includePathKey:
17+
return c.includePath
18+
case trimPrefixKey:
19+
return c.trimPrefix
20+
default:
21+
return ""
22+
}
23+
}
24+
25+
func TestWithPath(t *testing.T) {
26+
type test struct {
27+
name string
28+
cfg testConfigReader
29+
reqPath string
30+
wantURL string
31+
}
32+
33+
tests := []test{
34+
{
35+
"include_path false",
36+
testConfigReader{},
37+
"/foo/bar",
38+
"http://localhost",
39+
},
40+
{
41+
"include_path true, trim_prefix empty",
42+
testConfigReader{includePath: "true"},
43+
"/foo/bar",
44+
"http://localhost/foo/bar",
45+
},
46+
{
47+
"include_path true, trim_prefix is foo",
48+
testConfigReader{includePath: "true", trimPrefix: "/foo"},
49+
"/foo/bar",
50+
"http://localhost/bar",
51+
},
52+
}
53+
54+
for _, tc := range tests {
55+
t.Run(tc.name, func(t *testing.T) {
56+
r := &SpinRedirect{
57+
cfg: &tc.cfg,
58+
}
59+
60+
dest := "http://localhost"
61+
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost%s", tc.reqPath), nil)
62+
if err != nil {
63+
t.Fatalf("failed to create new http request: %s", err)
64+
}
65+
66+
gotURL := r.WithPath(dest, req)
67+
if gotURL != tc.wantURL {
68+
t.Fatalf("failed: got '%s', want '%s'", gotURL, tc.wantURL)
69+
}
70+
})
71+
}
72+
}

0 commit comments

Comments
 (0)