Skip to content

Commit

Permalink
feature: Go net/http -> wasi:http Proxy (#34)
Browse files Browse the repository at this point in the history
* feature: Go net/http -> wasi:http Proxy

Signed-off-by: Lucas Fontes <[email protected]>

* chore: tests

Signed-off-by: Lucas Fontes <[email protected]>

* chore: Updating golangci

Signed-off-by: Lucas Fontes <[email protected]>

* chore: Running go mod tidy

Signed-off-by: Lucas Fontes <[email protected]>

---------

Signed-off-by: Lucas Fontes <[email protected]>
  • Loading branch information
lxfontes authored Sep 13, 2024
1 parent c12cb79 commit fd7d2c9
Show file tree
Hide file tree
Showing 109 changed files with 48,537 additions and 14 deletions.
28 changes: 14 additions & 14 deletions .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@ name: Go

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"

- name: Build
run: go build -v ./...
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: Test
run: go test -v ./...

lint:
runs-on: ubuntu-latest
Expand All @@ -33,6 +32,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.58
version: v1.61
# temporary until current issues in the repo are fixed
only-new-issues: true
only-new-issues: true

2 changes: 2 additions & 0 deletions examples/http-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
http-server
/build
48 changes: 48 additions & 0 deletions examples/http-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# http-server

This example demonstrates how to forward requests to components exporting `wasi:http/incoming-handler`.

It starts a http server listening on port 8080 containing 2 routes:

- `/proxy`: Forwards the request to the component `http-component`
- `/`: Serve the request directly from the provider

# Internals

Proxying uses a custom `http.RoundTripper` implementation that forwards requests to the component.
In this example we forward to a single target ( `http-http_component` ).

```go
transport := wrpchttp.NewIncomingRoundTripper(wasmcloudprovider, wrpchttp.WithSingleTarget("http-http_component"))

wasiIncomingClient := &http.Client{
Transport: transport,
}

wasiIncomingClient.Get("http://localhost:8080/proxy")
```

You can also provide a custom `Director` function to select the target based on the request.

```go
func director(r *http.Request) string {
if r.URL.Host == "api" {
return "http-api"
}
return "http-ui"
})


transport := wrpchttp.NewIncomingRoundTripper(wasmcloudprovider, wrpchttp.WithDirector(director))

wasiIncomingClient := &http.Client{
Transport: transport,
}

// forward to http-api component
wasiIncomingClient.Get("http://api/users")

// forward to http-ui component
wasiIncomingClient.Get("http://ui/index.html")
wasiIncomingClient.Get("http://anyothername/index.html")
```
3 changes: 3 additions & 0 deletions examples/http-server/bindings/server.wrpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Generated by `wit-bindgen-wrpc-go` 0.8.0. DO NOT EDIT!
// server package contains wRPC bindings for `server` world
package server
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
// Generated by `wit-bindgen-wrpc-go` 0.8.0. DO NOT EDIT!
package monotonic_clock

import (
bytes "bytes"
context "context"
binary "encoding/binary"
errors "errors"
fmt "fmt"
wasi__io__poll "github.com/wasmCloud/provider-sdk-go/examples/http-server/bindings/wasi/io/poll"
io "io"
slog "log/slog"
utf8 "unicode/utf8"
wrpc "wrpc.io/go"
)

type Pollable = wasi__io__poll.Pollable

// An instant in time, in nanoseconds. An instant is relative to an
// unspecified initial value, and can only be compared to instances from
// the same monotonic-clock.
type Instant = uint64

// A duration of time, in nanoseconds.
type Duration = uint64

// Read the current value of the clock.
//
// The clock is monotonic, therefore calling this function repeatedly will
// produce a sequence of non-decreasing values.
func Now(ctx__ context.Context, wrpc__ wrpc.Invoker) (r0__ uint64, err__ error) {
var w__ wrpc.IndexWriteCloser
var r__ wrpc.IndexReadCloser
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/[email protected]", "now", nil)
if err__ != nil {
err__ = fmt.Errorf("failed to invoke `now`: %w", err__)
return
}
defer func() {
if err := r__.Close(); err != nil {
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/[email protected]", "name", "now", "err", err)
}
}()
if cErr__ := w__.Close(); cErr__ != nil {
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/[email protected]", "name", "now", "err", cErr__)
}
r0__, err__ = func() (Instant, error) {
v, err := func(r io.ByteReader) (uint64, error) {
var x uint64
var s uint8
for i := 0; i < 10; i++ {
slog.Debug("reading u64 byte", "i", i)
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return x, fmt.Errorf("failed to read u64 byte: %w", err)
}
if s == 63 && b > 0x01 {
return x, errors.New("varint overflows a 64-bit integer")
}
if b < 0x80 {
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return x, errors.New("varint overflows a 64-bit integer")
}(r__)
return (Instant)(v), err
}()

if err__ != nil {
err__ = fmt.Errorf("failed to read result 0: %w", err__)
return
}
return
}

// Query the resolution of the clock. Returns the duration of time
// corresponding to a clock tick.
func Resolution(ctx__ context.Context, wrpc__ wrpc.Invoker) (r0__ uint64, err__ error) {
var w__ wrpc.IndexWriteCloser
var r__ wrpc.IndexReadCloser
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/[email protected]", "resolution", nil)
if err__ != nil {
err__ = fmt.Errorf("failed to invoke `resolution`: %w", err__)
return
}
defer func() {
if err := r__.Close(); err != nil {
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/[email protected]", "name", "resolution", "err", err)
}
}()
if cErr__ := w__.Close(); cErr__ != nil {
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/[email protected]", "name", "resolution", "err", cErr__)
}
r0__, err__ = func() (Duration, error) {
v, err := func(r io.ByteReader) (uint64, error) {
var x uint64
var s uint8
for i := 0; i < 10; i++ {
slog.Debug("reading u64 byte", "i", i)
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return x, fmt.Errorf("failed to read u64 byte: %w", err)
}
if s == 63 && b > 0x01 {
return x, errors.New("varint overflows a 64-bit integer")
}
if b < 0x80 {
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
}
return x, errors.New("varint overflows a 64-bit integer")
}(r__)
return (Duration)(v), err
}()

if err__ != nil {
err__ = fmt.Errorf("failed to read result 0: %w", err__)
return
}
return
}

// Create a `pollable` which will resolve once the specified instant
// occured.
func SubscribeInstant(ctx__ context.Context, wrpc__ wrpc.Invoker, when uint64) (r0__ wrpc.Own[Pollable], err__ error) {
var buf__ bytes.Buffer
write0__, err__ := (func(wrpc.IndexWriter) error)(nil), func(v uint64, w io.Writer) (err error) {
b := make([]byte, binary.MaxVarintLen64)
i := binary.PutUvarint(b, uint64(v))
slog.Debug("writing u64")
_, err = w.Write(b[:i])
return err
}(when, &buf__)
if err__ != nil {
err__ = fmt.Errorf("failed to write `when` parameter: %w", err__)
return
}
if write0__ != nil {
err__ = errors.New("unexpected deferred write for synchronous `when` parameter")
return
}
var w__ wrpc.IndexWriteCloser
var r__ wrpc.IndexReadCloser
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/[email protected]", "subscribe-instant", buf__.Bytes())
if err__ != nil {
err__ = fmt.Errorf("failed to invoke `subscribe-instant`: %w", err__)
return
}
defer func() {
if err := r__.Close(); err != nil {
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/[email protected]", "name", "subscribe-instant", "err", err)
}
}()
if cErr__ := w__.Close(); cErr__ != nil {
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/[email protected]", "name", "subscribe-instant", "err", cErr__)
}
r0__, err__ = func(r interface {
io.ByteReader
io.Reader
}) (wrpc.Own[Pollable], error) {
var x uint32
var s uint
for i := 0; i < 5; i++ {
slog.Debug("reading owned resource ID length byte", "i", i)
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return "", fmt.Errorf("failed to read owned resource ID length byte: %w", err)
}
if b < 0x80 {
if i == 4 && b > 1 {
return "", errors.New("owned resource ID length overflows a 32-bit integer")
}
x = x | uint32(b)<<s
buf := make([]byte, x)
slog.Debug("reading owned resource ID bytes", "len", x)
_, err = r.Read(buf)
if err != nil {
return "", fmt.Errorf("failed to read owned resource ID bytes: %w", err)
}
if !utf8.Valid(buf) {
return "", errors.New("owned resource ID is not valid UTF-8")
}
return wrpc.Own[Pollable](buf), nil
}
x |= uint32(b&0x7f) << s
s += 7
}
return "", errors.New("owned resource ID length overflows a 32-bit integer")
}(r__)
if err__ != nil {
err__ = fmt.Errorf("failed to read result 0: %w", err__)
return
}
return
}

// Create a `pollable` which will resolve once the given duration has
// elapsed, starting at the time at which this function was called.
// occured.
func SubscribeDuration(ctx__ context.Context, wrpc__ wrpc.Invoker, when uint64) (r0__ wrpc.Own[Pollable], err__ error) {
var buf__ bytes.Buffer
write0__, err__ := (func(wrpc.IndexWriter) error)(nil), func(v uint64, w io.Writer) (err error) {
b := make([]byte, binary.MaxVarintLen64)
i := binary.PutUvarint(b, uint64(v))
slog.Debug("writing u64")
_, err = w.Write(b[:i])
return err
}(when, &buf__)
if err__ != nil {
err__ = fmt.Errorf("failed to write `when` parameter: %w", err__)
return
}
if write0__ != nil {
err__ = errors.New("unexpected deferred write for synchronous `when` parameter")
return
}
var w__ wrpc.IndexWriteCloser
var r__ wrpc.IndexReadCloser
w__, r__, err__ = wrpc__.Invoke(ctx__, "wasi:clocks/[email protected]", "subscribe-duration", buf__.Bytes())
if err__ != nil {
err__ = fmt.Errorf("failed to invoke `subscribe-duration`: %w", err__)
return
}
defer func() {
if err := r__.Close(); err != nil {
slog.ErrorContext(ctx__, "failed to close reader", "instance", "wasi:clocks/[email protected]", "name", "subscribe-duration", "err", err)
}
}()
if cErr__ := w__.Close(); cErr__ != nil {
slog.DebugContext(ctx__, "failed to close outgoing stream", "instance", "wasi:clocks/[email protected]", "name", "subscribe-duration", "err", cErr__)
}
r0__, err__ = func(r interface {
io.ByteReader
io.Reader
}) (wrpc.Own[Pollable], error) {
var x uint32
var s uint
for i := 0; i < 5; i++ {
slog.Debug("reading owned resource ID length byte", "i", i)
b, err := r.ReadByte()
if err != nil {
if i > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return "", fmt.Errorf("failed to read owned resource ID length byte: %w", err)
}
if b < 0x80 {
if i == 4 && b > 1 {
return "", errors.New("owned resource ID length overflows a 32-bit integer")
}
x = x | uint32(b)<<s
buf := make([]byte, x)
slog.Debug("reading owned resource ID bytes", "len", x)
_, err = r.Read(buf)
if err != nil {
return "", fmt.Errorf("failed to read owned resource ID bytes: %w", err)
}
if !utf8.Valid(buf) {
return "", errors.New("owned resource ID is not valid UTF-8")
}
return wrpc.Own[Pollable](buf), nil
}
x |= uint32(b&0x7f) << s
s += 7
}
return "", errors.New("owned resource ID length overflows a 32-bit integer")
}(r__)
if err__ != nil {
err__ = fmt.Errorf("failed to read result 0: %w", err__)
return
}
return
}
Loading

0 comments on commit fd7d2c9

Please sign in to comment.