Skip to content

Commit fd7d2c9

Browse files
authored
feature: Go net/http -> wasi:http Proxy (#34)
* feature: Go net/http -> wasi:http Proxy Signed-off-by: Lucas Fontes <lucas@cosmonic.com> * chore: tests Signed-off-by: Lucas Fontes <lucas@cosmonic.com> * chore: Updating golangci Signed-off-by: Lucas Fontes <lucas@cosmonic.com> * chore: Running go mod tidy Signed-off-by: Lucas Fontes <lucas@cosmonic.com> --------- Signed-off-by: Lucas Fontes <lucas@cosmonic.com>
1 parent c12cb79 commit fd7d2c9

109 files changed

Lines changed: 48537 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/go.yaml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,25 @@ name: Go
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: ["main"]
88

99
jobs:
10-
1110
build:
1211
runs-on: ubuntu-latest
1312
steps:
14-
- uses: actions/checkout@v4
15-
- name: Set up Go
16-
uses: actions/setup-go@v5
17-
with:
18-
go-version: '1.22'
13+
- uses: actions/checkout@v4
14+
- name: Set up Go
15+
uses: actions/setup-go@v5
16+
with:
17+
go-version: "1.22"
1918

20-
- name: Build
21-
run: go build -v ./...
19+
- name: Build
20+
run: go build -v ./...
2221

23-
- name: Test
24-
run: go test -v ./...
22+
- name: Test
23+
run: go test -v ./...
2524

2625
lint:
2726
runs-on: ubuntu-latest
@@ -33,6 +32,7 @@ jobs:
3332
- name: golangci-lint
3433
uses: golangci/golangci-lint-action@v6
3534
with:
36-
version: v1.58
35+
version: v1.61
3736
# temporary until current issues in the repo are fixed
38-
only-new-issues: true
37+
only-new-issues: true
38+

examples/http-server/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
http-server
2+
/build

examples/http-server/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# http-server
2+
3+
This example demonstrates how to forward requests to components exporting `wasi:http/incoming-handler`.
4+
5+
It starts a http server listening on port 8080 containing 2 routes:
6+
7+
- `/proxy`: Forwards the request to the component `http-component`
8+
- `/`: Serve the request directly from the provider
9+
10+
# Internals
11+
12+
Proxying uses a custom `http.RoundTripper` implementation that forwards requests to the component.
13+
In this example we forward to a single target ( `http-http_component` ).
14+
15+
```go
16+
transport := wrpchttp.NewIncomingRoundTripper(wasmcloudprovider, wrpchttp.WithSingleTarget("http-http_component"))
17+
18+
wasiIncomingClient := &http.Client{
19+
Transport: transport,
20+
}
21+
22+
wasiIncomingClient.Get("http://localhost:8080/proxy")
23+
```
24+
25+
You can also provide a custom `Director` function to select the target based on the request.
26+
27+
```go
28+
func director(r *http.Request) string {
29+
if r.URL.Host == "api" {
30+
return "http-api"
31+
}
32+
return "http-ui"
33+
})
34+
35+
36+
transport := wrpchttp.NewIncomingRoundTripper(wasmcloudprovider, wrpchttp.WithDirector(director))
37+
38+
wasiIncomingClient := &http.Client{
39+
Transport: transport,
40+
}
41+
42+
// forward to http-api component
43+
wasiIncomingClient.Get("http://api/users")
44+
45+
// forward to http-ui component
46+
wasiIncomingClient.Get("http://ui/index.html")
47+
wasiIncomingClient.Get("http://anyothername/index.html")
48+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Generated by `wit-bindgen-wrpc-go` 0.8.0. DO NOT EDIT!
2+
// server package contains wRPC bindings for `server` world
3+
package server
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
// Generated by `wit-bindgen-wrpc-go` 0.8.0. DO NOT EDIT!
2+
package monotonic_clock
3+
4+
import (
5+
bytes "bytes"
6+
context "context"
7+
binary "encoding/binary"
8+
errors "errors"
9+
fmt "fmt"
10+
wasi__io__poll "github.com/wasmCloud/provider-sdk-go/examples/http-server/bindings/wasi/io/poll"
11+
io "io"
12+
slog "log/slog"
13+
utf8 "unicode/utf8"
14+
wrpc "wrpc.io/go"
15+
)
16+
17+
type Pollable = wasi__io__poll.Pollable
18+
19+
// An instant in time, in nanoseconds. An instant is relative to an
20+
// unspecified initial value, and can only be compared to instances from
21+
// the same monotonic-clock.
22+
type Instant = uint64
23+
24+
// A duration of time, in nanoseconds.
25+
type Duration = uint64
26+
27+
// Read the current value of the clock.
28+
//
29+
// The clock is monotonic, therefore calling this function repeatedly will
30+
// produce a sequence of non-decreasing values.
31+
func Now(ctx__ context.Context, wrpc__ wrpc.Invoker) (r0__ uint64, err__ error) {
32+
var w__ wrpc.IndexWriteCloser
33+
var r__ wrpc.IndexReadCloser
34+
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/monotonic-clock@0.2.0", "now", nil)
35+
if err__ != nil {
36+
err__ = fmt.Errorf("failed to invoke `now`: %w", err__)
37+
return
38+
}
39+
defer func() {
40+
if err := r__.Close(); err != nil {
41+
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "now", "err", err)
42+
}
43+
}()
44+
if cErr__ := w__.Close(); cErr__ != nil {
45+
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "now", "err", cErr__)
46+
}
47+
r0__, err__ = func() (Instant, error) {
48+
v, err := func(r io.ByteReader) (uint64, error) {
49+
var x uint64
50+
var s uint8
51+
for i := 0; i < 10; i++ {
52+
slog.Debug("reading u64 byte", "i", i)
53+
b, err := r.ReadByte()
54+
if err != nil {
55+
if i > 0 && err == io.EOF {
56+
err = io.ErrUnexpectedEOF
57+
}
58+
return x, fmt.Errorf("failed to read u64 byte: %w", err)
59+
}
60+
if s == 63 && b > 0x01 {
61+
return x, errors.New("varint overflows a 64-bit integer")
62+
}
63+
if b < 0x80 {
64+
return x | uint64(b)<<s, nil
65+
}
66+
x |= uint64(b&0x7f) << s
67+
s += 7
68+
}
69+
return x, errors.New("varint overflows a 64-bit integer")
70+
}(r__)
71+
return (Instant)(v), err
72+
}()
73+
74+
if err__ != nil {
75+
err__ = fmt.Errorf("failed to read result 0: %w", err__)
76+
return
77+
}
78+
return
79+
}
80+
81+
// Query the resolution of the clock. Returns the duration of time
82+
// corresponding to a clock tick.
83+
func Resolution(ctx__ context.Context, wrpc__ wrpc.Invoker) (r0__ uint64, err__ error) {
84+
var w__ wrpc.IndexWriteCloser
85+
var r__ wrpc.IndexReadCloser
86+
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/monotonic-clock@0.2.0", "resolution", nil)
87+
if err__ != nil {
88+
err__ = fmt.Errorf("failed to invoke `resolution`: %w", err__)
89+
return
90+
}
91+
defer func() {
92+
if err := r__.Close(); err != nil {
93+
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "resolution", "err", err)
94+
}
95+
}()
96+
if cErr__ := w__.Close(); cErr__ != nil {
97+
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "resolution", "err", cErr__)
98+
}
99+
r0__, err__ = func() (Duration, error) {
100+
v, err := func(r io.ByteReader) (uint64, error) {
101+
var x uint64
102+
var s uint8
103+
for i := 0; i < 10; i++ {
104+
slog.Debug("reading u64 byte", "i", i)
105+
b, err := r.ReadByte()
106+
if err != nil {
107+
if i > 0 && err == io.EOF {
108+
err = io.ErrUnexpectedEOF
109+
}
110+
return x, fmt.Errorf("failed to read u64 byte: %w", err)
111+
}
112+
if s == 63 && b > 0x01 {
113+
return x, errors.New("varint overflows a 64-bit integer")
114+
}
115+
if b < 0x80 {
116+
return x | uint64(b)<<s, nil
117+
}
118+
x |= uint64(b&0x7f) << s
119+
s += 7
120+
}
121+
return x, errors.New("varint overflows a 64-bit integer")
122+
}(r__)
123+
return (Duration)(v), err
124+
}()
125+
126+
if err__ != nil {
127+
err__ = fmt.Errorf("failed to read result 0: %w", err__)
128+
return
129+
}
130+
return
131+
}
132+
133+
// Create a `pollable` which will resolve once the specified instant
134+
// occured.
135+
func SubscribeInstant(ctx__ context.Context, wrpc__ wrpc.Invoker, when uint64) (r0__ wrpc.Own[Pollable], err__ error) {
136+
var buf__ bytes.Buffer
137+
write0__, err__ := (func(wrpc.IndexWriter) error)(nil), func(v uint64, w io.Writer) (err error) {
138+
b := make([]byte, binary.MaxVarintLen64)
139+
i := binary.PutUvarint(b, uint64(v))
140+
slog.Debug("writing u64")
141+
_, err = w.Write(b[:i])
142+
return err
143+
}(when, &buf__)
144+
if err__ != nil {
145+
err__ = fmt.Errorf("failed to write `when` parameter: %w", err__)
146+
return
147+
}
148+
if write0__ != nil {
149+
err__ = errors.New("unexpected deferred write for synchronous `when` parameter")
150+
return
151+
}
152+
var w__ wrpc.IndexWriteCloser
153+
var r__ wrpc.IndexReadCloser
154+
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/monotonic-clock@0.2.0", "subscribe-instant", buf__.Bytes())
155+
if err__ != nil {
156+
err__ = fmt.Errorf("failed to invoke `subscribe-instant`: %w", err__)
157+
return
158+
}
159+
defer func() {
160+
if err := r__.Close(); err != nil {
161+
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "subscribe-instant", "err", err)
162+
}
163+
}()
164+
if cErr__ := w__.Close(); cErr__ != nil {
165+
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "subscribe-instant", "err", cErr__)
166+
}
167+
r0__, err__ = func(r interface {
168+
io.ByteReader
169+
io.Reader
170+
}) (wrpc.Own[Pollable], error) {
171+
var x uint32
172+
var s uint
173+
for i := 0; i < 5; i++ {
174+
slog.Debug("reading owned resource ID length byte", "i", i)
175+
b, err := r.ReadByte()
176+
if err != nil {
177+
if i > 0 && err == io.EOF {
178+
err = io.ErrUnexpectedEOF
179+
}
180+
return "", fmt.Errorf("failed to read owned resource ID length byte: %w", err)
181+
}
182+
if b < 0x80 {
183+
if i == 4 && b > 1 {
184+
return "", errors.New("owned resource ID length overflows a 32-bit integer")
185+
}
186+
x = x | uint32(b)<<s
187+
buf := make([]byte, x)
188+
slog.Debug("reading owned resource ID bytes", "len", x)
189+
_, err = r.Read(buf)
190+
if err != nil {
191+
return "", fmt.Errorf("failed to read owned resource ID bytes: %w", err)
192+
}
193+
if !utf8.Valid(buf) {
194+
return "", errors.New("owned resource ID is not valid UTF-8")
195+
}
196+
return wrpc.Own[Pollable](buf), nil
197+
}
198+
x |= uint32(b&0x7f) << s
199+
s += 7
200+
}
201+
return "", errors.New("owned resource ID length overflows a 32-bit integer")
202+
}(r__)
203+
if err__ != nil {
204+
err__ = fmt.Errorf("failed to read result 0: %w", err__)
205+
return
206+
}
207+
return
208+
}
209+
210+
// Create a `pollable` which will resolve once the given duration has
211+
// elapsed, starting at the time at which this function was called.
212+
// occured.
213+
func SubscribeDuration(ctx__ context.Context, wrpc__ wrpc.Invoker, when uint64) (r0__ wrpc.Own[Pollable], err__ error) {
214+
var buf__ bytes.Buffer
215+
write0__, err__ := (func(wrpc.IndexWriter) error)(nil), func(v uint64, w io.Writer) (err error) {
216+
b := make([]byte, binary.MaxVarintLen64)
217+
i := binary.PutUvarint(b, uint64(v))
218+
slog.Debug("writing u64")
219+
_, err = w.Write(b[:i])
220+
return err
221+
}(when, &buf__)
222+
if err__ != nil {
223+
err__ = fmt.Errorf("failed to write `when` parameter: %w", err__)
224+
return
225+
}
226+
if write0__ != nil {
227+
err__ = errors.New("unexpected deferred write for synchronous `when` parameter")
228+
return
229+
}
230+
var w__ wrpc.IndexWriteCloser
231+
var r__ wrpc.IndexReadCloser
232+
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/monotonic-clock@0.2.0", "subscribe-duration", buf__.Bytes())
233+
if err__ != nil {
234+
err__ = fmt.Errorf("failed to invoke `subscribe-duration`: %w", err__)
235+
return
236+
}
237+
defer func() {
238+
if err := r__.Close(); err != nil {
239+
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "subscribe-duration", "err", err)
240+
}
241+
}()
242+
if cErr__ := w__.Close(); cErr__ != nil {
243+
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/monotonic-clock@0.2.0", "name", "subscribe-duration", "err", cErr__)
244+
}
245+
r0__, err__ = func(r interface {
246+
io.ByteReader
247+
io.Reader
248+
}) (wrpc.Own[Pollable], error) {
249+
var x uint32
250+
var s uint
251+
for i := 0; i < 5; i++ {
252+
slog.Debug("reading owned resource ID length byte", "i", i)
253+
b, err := r.ReadByte()
254+
if err != nil {
255+
if i > 0 && err == io.EOF {
256+
err = io.ErrUnexpectedEOF
257+
}
258+
return "", fmt.Errorf("failed to read owned resource ID length byte: %w", err)
259+
}
260+
if b < 0x80 {
261+
if i == 4 && b > 1 {
262+
return "", errors.New("owned resource ID length overflows a 32-bit integer")
263+
}
264+
x = x | uint32(b)<<s
265+
buf := make([]byte, x)
266+
slog.Debug("reading owned resource ID bytes", "len", x)
267+
_, err = r.Read(buf)
268+
if err != nil {
269+
return "", fmt.Errorf("failed to read owned resource ID bytes: %w", err)
270+
}
271+
if !utf8.Valid(buf) {
272+
return "", errors.New("owned resource ID is not valid UTF-8")
273+
}
274+
return wrpc.Own[Pollable](buf), nil
275+
}
276+
x |= uint32(b&0x7f) << s
277+
s += 7
278+
}
279+
return "", errors.New("owned resource ID length overflows a 32-bit integer")
280+
}(r__)
281+
if err__ != nil {
282+
err__ = fmt.Errorf("failed to read result 0: %w", err__)
283+
return
284+
}
285+
return
286+
}

0 commit comments

Comments
 (0)