Skip to content

Commit

Permalink
feat: add moonbit http component
Browse files Browse the repository at this point in the history
Signed-off-by: Brooks Townsend <brooksmtownsend@gmail.com>
  • Loading branch information
brooksmtownsend committed Aug 7, 2024
1 parent 37eb8d3 commit 1e2cc4d
Showing 69 changed files with 6,821 additions and 0 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# wasmCloud-contrib

Community contributions of providers, components, and demos

## Components

- [moonbit/http-hello-world](./components/moonbit/http-hello-world/) is an example component that implements the `wasi-http/incoming-handler@0.2.0` interface and is built with [Moonbit](https://www.moonbitlang.com/)

## Secrets

There are currently two implementations of [wasmCloud secrets backends](https://wasmcloud.com/docs/deployment/security/secrets#implementing-a-secrets-backend) available in this repository.

- [secrets-kubernetes](./secrets/secrets-kubernetes/) for using secrets stored in Kubernetes in wasmCloud applications
- [secrets-vault](./secrets/secrets-vault/) for using secrets stored in Vault in wasmCloud applications
1 change: 1 addition & 0 deletions components/moonbit/http-hello-world/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/*.wasm
92 changes: 92 additions & 0 deletions components/moonbit/http-hello-world/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Moonbit http-bello-world

This example was created following the [Developing Wasm component model in MoonBit with minimal output size](https://www.moonbitlang.com/blog/component-model) blog.

## Prerequisites

- [Rust toolchain](https://www.rust-lang.org/tools/install) to install prerequisites
- wit-deps
- `cargo install wit-deps-cli`
- wasm-tools
- `cargo install wasm-tools`
- Moonbit wit-bindgen fork
- `cargo install wit-bindgen-cli --git https://github.com/peter-jerry-ye/wit-bindgen/ --branch moonbit`
- [Moonbit CLI](https://www.moonbitlang.com/download/#moonbit-cli-tools)
- [wash CLI](https://wasmcloud.com/docs/installation)

## Run in wasmCloud

After the tutorial and running the Moonbit Wasm component in wasmtime, we created a `wasmcloud.toml` to encapsulate the build & adapt steps and a `wadm.yaml` for running the component in wasmCloud.

The `wadm.yaml` was created using [wit2wadm](https://github.com/brooksmtownsend/wit2wadm). You can install the `wit2wadm` plugin using `wash plugin install oci://ghcr.io/brooksmtownsend/wit2wadm:0.2.0`.

```bash
wash build
wash wit2wadm ./build/gen.wasm --name moonbit-http --description "HTTP hello world demo with a component written in Moonbit" > wadm.yaml
wash up -d
wash app deploy ./wadm.yaml
```

Then, once the application is in the `Deployed` status:

```bash
> curl localhost:8000
Hello, World%
```

## Size & Speed

As promised, the Moonbit component is tiny!

```bash
➜ ll build
Permissions Size User Date Modified Name
.rw-r--r-- 22k brooks 7 Aug 10:30 gen.wasm
```

Running 100 max instances of the moonbit component, we get impressive numbers for HTTP throughput. As Moonbit evolves, it will be interesting to see how viable the language is for creating performant components.

```bash
➜ hey -z 20s -c 100 http://localhost:8000

Summary:
Total: 20.0071 secs
Slowest: 0.0401 secs
Fastest: 0.0009 secs
Average: 0.0048 secs
Requests/sec: 20901.9356


Response time histogram:
0.001 [1] |
0.005 [238066] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.009 [176898] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.013 [2474] |
0.017 [384] |
0.021 [140] |
0.024 [95] |
0.028 [81] |
0.032 [47] |
0.036 [0] |
0.040 [1] |


Latency distribution:
10% in 0.0035 secs
25% in 0.0040 secs
50% in 0.0047 secs
75% in 0.0054 secs
90% in 0.0061 secs
95% in 0.0067 secs
99% in 0.0084 secs

Details (average, fastest, slowest):
DNS+dialup: 0.0000 secs, 0.0009 secs, 0.0401 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0017 secs
req write: 0.0000 secs, 0.0000 secs, 0.0009 secs
resp wait: 0.0046 secs, 0.0007 secs, 0.0400 secs
resp read: 0.0002 secs, 0.0000 secs, 0.0140 secs

Status code distribution:
[200] 418187 responses
```
5 changes: 5 additions & 0 deletions components/moonbit/http-hello-world/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

moon build --target wasm
wasm-tools component embed wit target/wasm/release/build/gen/gen.wasm -o target/wasm/release/build/gen/gen.wasm --encoding utf16
wasm-tools component new target/wasm/release/build/gen/gen.wasm -o target/wasm/release/build/gen/gen.wasm
Empty file.
1 change: 1 addition & 0 deletions components/moonbit/http-hello-world/ffi/moon.pkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
112 changes: 112 additions & 0 deletions components/moonbit/http-hello-world/ffi/top.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@

pub extern "wasm" fn extend16(value : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.extend16_s)

pub extern "wasm" fn extend8(value : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.extend8_s)

pub extern "wasm" fn store8(offset : Int, value : Int) =
#|(func (param i32) (param i32) local.get 0 local.get 1 i32.store8)

pub extern "wasm" fn load8_u(offset : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.load8_u)

pub extern "wasm" fn load8(offset : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.load8_s)

pub extern "wasm" fn store16(offset : Int, value : Int) =
#|(func (param i32) (param i32) local.get 0 local.get 1 i32.store16)

pub extern "wasm" fn load16(offset : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.load16_s)

pub extern "wasm" fn load16_u(offset : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.load16_u)

pub extern "wasm" fn store32(offset : Int, value : Int) =
#|(func (param i32) (param i32) local.get 0 local.get 1 i32.store)

pub extern "wasm" fn load32(offset : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 i32.load)

pub extern "wasm" fn store64(offset : Int, value : Int64) =
#|(func (param i32) (param i64) local.get 0 local.get 1 i64.store)

pub extern "wasm" fn load64(offset : Int) -> Int64 =
#|(func (param i32) (result i64) local.get 0 i64.load)

pub extern "wasm" fn storef32(offset : Int, value : Float) =
#|(func (param i32) (param f32) local.get 0 local.get 1 f32.store)

pub extern "wasm" fn loadf32(offset : Int) -> Float =
#|(func (param i32) (result f32) local.get 0 f32.load)

pub extern "wasm" fn storef64(offset : Int, value : Double) =
#|(func (param i32) (param f64) local.get 0 local.get 1 f64.store)

pub extern "wasm" fn loadf64(offset : Int) -> Double =
#|(func (param i32) (result f64) local.get 0 f64.load)

pub extern "wasm" fn f32_to_i32(value : Float) -> Int =
#|(func (param f32) (result i32) local.get 0 f32.convert_i32_s)

pub extern "wasm" fn i32_to_f32(value : Int) -> Float =
#|(func (param i32) (result f32) local.get 0 i32.trunc_f32_s)

pub extern "wasm" fn f32_to_i64(value : Float) -> Int64 =
#|(func (param f32) (result i64) local.get 0 f32.convert_i64_s)

pub extern "wasm" fn i64_to_f32(value : Int64) -> Float =
#|(func (param i64) (result f32) local.get 0 i64.trunc_f32_s)

pub extern "wasm" fn f32_to_f64(value : Float) -> Double =
#|(func (param f32) (result f64) local.get 0 f64.promote_f32)

pub extern "wasm" fn f64_to_f32(value : Double) -> Float =
#|(func (param f64) (result f32) local.get 0 f32.demote_f64)

extern "wasm" fn malloc_inline(size : Int) -> Int =
#|(func (param i32) (result i32) local.get 0 call $moonbit.malloc)

pub fn malloc(size : Int) -> Int {
let words = size / 4 + 1
let address = malloc_inline(8 + words * 4)
store32(address, 1)
store32(address + 4, words.lsl(8).lor(246))
store8(address + words * 4 + 7, 3 - size % 4)
address + 8
}

pub extern "wasm" fn free(position : Int) =
#|(func (param i32) local.get 0 i32.const 8 i32.sub call $moonbit.decref)

pub fn copy(dest : Int, src : Int) -> Unit {
let src = src - 8
let dest = dest - 8
let src_len = load32(src + 4).land(0xFFFFFF)
let dest_len = load32(dest + 4).land(0xFFFFFF)
let min = if src_len < dest_len { src_len } else { dest_len }
copy_inline(dest, src, min)
}

extern "wasm" fn copy_inline(dest : Int, src : Int, len : Int) =
#|(func (param i32) (param i32) (param i32) local.get 0 local.get 1 local.get 2 memory.copy)

pub extern "wasm" fn str2ptr(str : String) -> Int =
#|(func (param i32) (result i32) local.get 0 call $moonbit.decref local.get 0 i32.const 8 i32.add)

pub extern "wasm" fn ptr2str(ptr : Int) -> String =
#|(func (param i32) (result i32) local.get 0 i32.const 4 i32.sub i32.const 243 i32.store8 local.get 0 i32.const 8 i32.sub)

pub extern "wasm" fn bytes2ptr(bytes : Bytes) -> Int =
#|(func (param i32) (result i32) local.get 0 call $moonbit.decref local.get 0 i32.const 8 i32.add)

pub extern "wasm" fn ptr2bytes(ptr : Int) -> Bytes =
#|(func (param i32) (result i32) local.get 0 i32.const 8 i32.sub)

pub trait Any {}
pub struct Cleanup {
address : Int
size : Int
align : Int
}
23 changes: 23 additions & 0 deletions components/moonbit/http-hello-world/gen/ffi.mbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

pub fn cabi_realloc(
src_offset : Int,
src_size : Int,
_dst_alignment : Int,
dst_size : Int
) -> Int {
// malloc
if src_offset == 0 && src_size == 0 {
return @ffi.malloc(dst_size)
}
// free
if dst_size <= 0 {
@ffi.free(src_offset)
return 0
}
// realloc
let dst = @ffi.malloc(dst_size)
@ffi.copy(dst, src_offset)
@ffi.free(src_offset)
dst
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT!

pub fn wasmExportHandle(p0 : Int, p1 : Int) -> Unit {

let _ = @incomingHandler.handle(@types.IncomingRequest::IncomingRequest(p0), @types.ResponseOutparam::ResponseOutparam(p1));

}

11 changes: 11 additions & 0 deletions components/moonbit/http-hello-world/gen/moon.pkg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

{
"link": {
"wasm": {
"exports": ["cabi_realloc:cabi_realloc", "wasmExportHandle:wasi:http/incoming-handler@0.2.0#handle"],
"export-memory-name": "memory",
"heap-start-address": 0
}
}
,"import": [{ "path" : "wasmcloud/example/ffi", "alias" : "ffi" }, { "path" : "wasmcloud/example/interface/exports/wasi/http/incomingHandler", "alias" : "incomingHandler" }, { "path" : "wasmcloud/example/interface/imports/wasi/http/types", "alias" : "types" }]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Generated by `wit-bindgen` 0.29.0. DO NOT EDIT!

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This interface defines a handler of incoming HTTP Requests. It should
be exported by components which can respond to HTTP Requests.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"import": [
{
"path": "wasmcloud/example/interface/imports/wasi/http/types",
"alias": "types"
},
"wasmcloud/example/interface/imports/wasi/io/streams"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Generated by `wit-bindgen` 0.28.0. DO NOT EDIT!
/// This function is invoked with an incoming HTTP Request, and a resource
/// `response-outparam` which provides the capability to reply with an HTTP
/// Response. The response is sent by calling the `response-outparam.set`
/// method, which allows execution to continue after the response has been
/// sent. This enables both streaming to the response body, and performing other
/// work.
///
/// The implementor of this function must write a response to the
/// `response-outparam` before returning, or else the caller will respond
/// with an error on its behalf.
pub fn handle(
request : @types.IncomingRequest,
response_out : @types.ResponseOutparam
) -> Unit {
let response = match request.path_with_query() {
None | Some("/") => make_response(b"Hello, World")
_ => make_response(b"Not Found", status_code=404)
}
|> Ok
response_out.set(response)
}

fn make_response(
body : Bytes,
~status_code : UInt = 200
) -> @types.OutgoingResponse {
let response = @types.outgoing_response(@types.fields())
response.body().unwrap().write().unwrap().blocking_write_and_flush(body).unwrap()
response.set_status_code(status_code).unwrap()
response
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
WASI Monotonic Clock is a clock API intended to let users measure elapsed
time.

It is intended to be portable at least between Unix-family platforms and
Windows.

A monotonic clock is a clock which has an unspecified initial value, and
successive reads of the clock will produce non-decreasing values.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"import": [
{ "path": "wasmcloud/example/ffi", "alias": "ffi" },
{
"path": "wasmcloud/example/interface/imports/wasi/io/poll",
"alias": "poll"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Generated by `wit-bindgen` 0.28.0. DO NOT EDIT!
fn wasmImportNow() -> Int64 = "wasi:clocks/monotonic-clock@0.2.0" "now"

/// Read the current value of the clock.
///
/// The clock is monotonic, therefore calling this function repeatedly will
/// produce a sequence of non-decreasing values.
pub fn now() -> UInt64 {
let result : Int64 = wasmImportNow()
return result.to_uint64()
}

fn wasmImportResolution() -> Int64 = "wasi:clocks/monotonic-clock@0.2.0" "resolution"

/// Query the resolution of the clock. Returns the duration of time
/// corresponding to a clock tick.
pub fn resolution() -> UInt64 {
let result : Int64 = wasmImportResolution()
return result.to_uint64()
}

fn wasmImportSubscribeInstant(p0 : Int64) -> Int = "wasi:clocks/monotonic-clock@0.2.0" "subscribe-instant"

/// Create a `pollable` which will resolve once the specified instant
/// has occurred.
pub fn subscribe_instant(when : UInt64) -> @poll.Pollable {
let result : Int = wasmImportSubscribeInstant(when.to_int64())
return @poll.Pollable::Pollable(result)
}

fn wasmImportSubscribeDuration(p0 : Int64) -> Int = "wasi:clocks/monotonic-clock@0.2.0" "subscribe-duration"

/// Create a `pollable` that will resolve after the specified duration has
/// elapsed from the time this function is invoked.
pub fn subscribe_duration(when : UInt64) -> @poll.Pollable {
let result : Int = wasmImportSubscribeDuration(when.to_int64())
return @poll.Pollable::Pollable(result)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This interface defines all of the types and methods for implementing
HTTP Requests and Responses, both incoming and outgoing, as well as
their headers, trailers, and bodies.
Loading

0 comments on commit 1e2cc4d

Please sign in to comment.