Skip to content

Commit 3bd801c

Browse files
authored
Merge pull request #6 from ThorstenHans/feature/spin-component
Rework spin-redirect to Non-WAGI & support for custom status codes
2 parents b84b210 + 6f23528 commit 3bd801c

File tree

6 files changed

+210
-11
lines changed

6 files changed

+210
-11
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ ENABLE_WASM_OPT ?= true
22

33
.PHONY: build
44
build:
5-
tinygo build -wasm-abi=generic -target=wasi -gc=leaking -o redirect.wasm redirect.go
5+
tinygo build -target=wasi -gc=leaking -o redirect.wasm ./
66
ifeq ($(ENABLE_WASM_OPT),true)
77
wasm-opt -Os -o redirect.wasm redirect.wasm
88
endif

README.md

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,72 @@ This is a simple HTTP redirect component written in Go.
44

55
This is not a Spin application in itself but a component that can be used in applications to redirect a route.
66

7-
Example usage:
7+
## Configuration
88

9+
The `spin-redirect` component can be configured to address your needs in different scenarios. `spin-redirect` tries to load configuration data from multiple places in the specified order:
10+
11+
1. Spin component configuration
12+
2. Environment variables
13+
14+
The following table outlines available configuration values:
15+
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+
21+
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.
22+
23+
### Valid redirection status codes
24+
25+
The `spin-redirect` component supports the following HTTP status codes to perform a redirect:
26+
27+
- `301` Moved Permanently
28+
- `302` Found (Moved Temporarily)
29+
- `303` See Other: Only supported for `PUT` and `POST` requests
30+
- `307` Temporary Redirect
31+
- `308` Permanent Redirect
32+
33+
## Example usage
34+
35+
The following snippet shows how to add and configure `spin-redirect` in your `spin.toml` using environment variables
36+
37+
```toml
38+
spin_manifest_version = "1"
39+
description = ""
40+
name = "test"
41+
trigger = { type = "http", base = "/" }
42+
version = "0.1.0"
43+
44+
# Redirect / to /index.html using HTTP status code 301
45+
[[component]]
46+
id = "redirect-sample"
47+
source = "path/to/redirect.wasm"
48+
environment = { DESTINATION = "/index.html", STATUSCODE = "301" }
49+
50+
[component.trigger]
51+
route = "/"
952
```
10-
# Redirect / to /index.html
53+
54+
Alternatively, you can use component configuration to configure `spin-redirect` as shown below:
55+
56+
```toml
57+
spin_manifest_version = "1"
58+
description = ""
59+
name = "test"
60+
trigger = { type = "http", base = "/" }
61+
version = "0.1.0"
62+
63+
# Redirect / to /index.html using HTTP status code 301
1164
[[component]]
12-
id = "redirect-to-index"
65+
id = "redirect-sample"
1366
source = "path/to/redirect.wasm"
14-
environment = { DESTINATION = "/index.html" }
67+
68+
[component.config]
69+
destination="/index.html"
70+
statuscode="301"
71+
1572
[component.trigger]
1673
route = "/"
17-
executor = { type = "wagi" }
74+
1875
```

config.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"strings"
6+
7+
config "github.com/fermyon/spin/sdk/go/config"
8+
)
9+
10+
type ConfigReader interface {
11+
Get(key string) string
12+
}
13+
14+
/*
15+
DefaultConfigReader implements ConfigReader interface
16+
and provides a Get method for reading configuration values.
17+
18+
It first tries to find the value in Spin configuration.
19+
If the key is not found in Spin configuration, it will try
20+
to find the value in the environment variables.
21+
*/
22+
type DefaultConfigReader struct{}
23+
24+
// NewDefaultConfigReader returns a new DefaultConfigReader
25+
func NewDefaultConfigReader() DefaultConfigReader {
26+
return DefaultConfigReader{}
27+
}
28+
29+
/*
30+
Get returns the configuration value for the given key
31+
If the key is not found in Spin configuration, it will try
32+
to find the value in the environment variables.
33+
34+
For looking up a value in spin configuration, keys are used as is (case sensitive).
35+
For looking up a value in environment variables, keys are converted to uppercase.
36+
37+
If key is neither found in Spin configuration nor in environment variables,
38+
an empty string is returned.
39+
40+
Usage:
41+
42+
cfg := NewDefaultConfigReader()
43+
value := cfg.Get("destination")
44+
*/
45+
func (c DefaultConfigReader) Get(key string) string {
46+
v, err := config.Get(key)
47+
if err != nil {
48+
return os.Getenv(strings.ToUpper(key))
49+
}
50+
return v
51+
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
module github.com/fermyon/finicky-whiskers/redirect
22

3-
go 1.17
3+
go 1.17
4+
5+
require github.com/fermyon/spin/sdk/go v1.4.2
6+
7+
require github.com/julienschmidt/httprouter v1.3.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +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=
3+
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
4+
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=

redirect.go

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,94 @@
11
package main
22

33
import (
4-
"fmt"
5-
"os"
4+
"net/http"
5+
"strconv"
6+
7+
spinhttp "github.com/fermyon/spin/sdk/go/http"
8+
)
9+
10+
const (
11+
// Default value for HTTP status code
12+
DefaultStatusCode int = http.StatusFound
13+
// Default value for redirection target
14+
DefaultRedirectionTarget string = "/"
15+
// Key for loading desired destination
16+
destinationKey string = "destination"
17+
// Key for loading desired HTTP status code
18+
statusCodeKey string = "statuscode"
619
)
720

21+
func init() {
22+
r := NewSpinRedirect()
23+
spinhttp.Handle(r.handleFunc)
24+
}
25+
826
func main() {
9-
dest := os.Getenv("DESTINATION")
10-
fmt.Printf("status: 302\nlocation: %s\n\n", dest)
27+
}
28+
29+
// SpinRedirect is a struct that provides a handleFunc
30+
// for redirecting to a destination URL using configurable HTTP status code.
31+
type SpinRedirect struct {
32+
cfg ConfigReader
33+
}
34+
35+
// NewSpinRedirect returns a new SpinRedirect
36+
func NewSpinRedirect() SpinRedirect {
37+
return SpinRedirect{
38+
cfg: NewDefaultConfigReader(),
39+
}
40+
}
41+
42+
func (s SpinRedirect) handleFunc(w http.ResponseWriter, r *http.Request) {
43+
dest, _ := s.getDestination()
44+
code, _ := s.getStatusCode(r.Method)
45+
46+
w.Header().Set("Location", dest)
47+
w.WriteHeader(code)
48+
}
49+
50+
// getDestination returns the destination URL and a boolean indicating if the user provided a destination URL.
51+
// If no destination is found, DefaultRedirectionTarget (/) is returned.
52+
func (s SpinRedirect) getDestination() (string, bool) {
53+
d := s.cfg.Get(destinationKey)
54+
if len(d) == 0 {
55+
return DefaultRedirectionTarget, false
56+
}
57+
return d, true
58+
}
59+
60+
// getStatusCode returns the HTTP status code
61+
// If no status code is found, or if the provided value is invalid,
62+
// DefaultStatusCode is returned along with a boolean indicating if the user provided a valid status code.
63+
func (s SpinRedirect) getStatusCode(method string) (int, bool) {
64+
str := s.cfg.Get(statusCodeKey)
65+
code, err := strconv.Atoi(str)
66+
if err != nil {
67+
return DefaultStatusCode, false
68+
}
69+
if !isValidRedirectStatusCode(code, method) {
70+
return DefaultStatusCode, false
71+
}
72+
return code, true
73+
}
74+
75+
// isValidRedirectStatusCode returns true if the provided status code is valid for redirection
76+
func isValidRedirectStatusCode(code int, method string) bool {
77+
if code == http.StatusSeeOther &&
78+
(method == http.MethodPut || method == http.MethodPost) {
79+
return true
80+
}
81+
validCodes := []int{
82+
http.StatusMovedPermanently,
83+
http.StatusFound,
84+
http.StatusTemporaryRedirect,
85+
http.StatusPermanentRedirect,
86+
}
87+
88+
for _, c := range validCodes {
89+
if c == code {
90+
return true
91+
}
92+
}
93+
return false
1194
}

0 commit comments

Comments
 (0)