Skip to content

Commit

Permalink
Merge pull request #25 from madflojo/fix-examples
Browse files Browse the repository at this point in the history
Fix examples
  • Loading branch information
madflojo authored Mar 31, 2022
2 parents 77e30ba + cf684f1 commit 66465e7
Show file tree
Hide file tree
Showing 28 changed files with 156 additions and 1,713 deletions.
145 changes: 49 additions & 96 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,113 +31,66 @@ At the moment, Tramac is executing WASM functions by executing a defined set of

As part of the WASM Function, users must register their handlers using the pre-defined function signatures.

To understand this better, look at one of our simple examples written in Rust \(found in [example/](https://github.com/madflojo/tarmac/blob/main/example/tac/README.md)\).

```rust
// Tac is a small, simple Rust program that is an example WASM function for Tarmac.
// This program will accept a Tarmac server request, log it, and echo back the payload
// but with the payload reversed.
extern crate wapc_guest as guest;
extern crate base64;
use serde::{Deserialize, Serialize};
use serde_json;
use std::collections::HashMap;
use guest::prelude::*;

#[derive(Serialize, Deserialize)]
struct ServerRequest {
headers: HashMap<String, String>,
payload: String,
To understand this better, look at one of our simple examples \(found in [example/](https://github.com/madflojo/tarmac/blob/main/example/tac/README.md)\).

```golang
// Tac is a small, simple Go program that is an example WASM module for Tarmac. This program will accept a Tarmac
// server request, log it, and echo back the payload in reverse.
package main

import (
"fmt"
wapc "github.com/wapc/wapc-guest-tinygo"
)

func main() {
// Tarmac uses waPC to facilitate WASM module execution. Modules must register their custom handlers under the
// appropriate method as shown below.
wapc.RegisterFunctions(wapc.Functions{
// Register a GET request handler
"GET": NoHandler,
// Register a POST request handler
"POST": Handler,
// Register a PUT request handler
"PUT": Handler,
// Register a DELETE request handler
"DELETE": NoHandler,
})
}

#[derive(Serialize, Deserialize)]
struct ServerResponse {
headers: HashMap<String, String>,
status: Status,
payload: String,
// NoHandler is a custom Tarmac Handler function that will return an error that denies
// the client request.
func NoHandler(payload []byte) ([]byte, error) {
return []byte(""), fmt.Errorf("Not Implemented")
}

#[derive(Serialize, Deserialize)]
struct Status {
code: u32,
status: String,
}

fn main() {}

#[no_mangle]
pub extern "C" fn wapc_init() {
// Add Handler for the GET request
register_function("http:GET", fail_handler);
// Add Handler for the POST request
register_function("http:POST", handler);
// Add Handler for the PUT request
register_function("http:PUT", handler);
// Add Handler for the DELETE request
register_function("http:DELETE", fail_handler);
}

// fail_handler will accept the server request and return a server response
// which rejects the client request
fn fail_handler(_msg: &[u8]) -> CallResult {
// Create the response
let rsp = ServerResponse {
status: Status {
code: 503,
status: "Not Implemented".to_string(),
},
payload: "".to_string(),
headers: HashMap::new(),
};

// Marshal the response
let r = serde_json::to_vec(&rsp).unwrap();

// Return JSON byte array
Ok(r)
}

// handler is a simple example of a Tarmac WASM function written in Rust.
// This function will accept the server request, log it, and echo back the payload
// but with the payload reversed.
fn handler(msg: &[u8]) -> CallResult {
// Perform a host callback to log the incoming request
let _res = host_call("tarmac", "logger", "debug", &msg.to_vec());

// Unmarshal the request
let rq: ServerRequest = serde_json::from_slice(msg).unwrap();

// Decode Payload
let b = base64::decode(rq.payload).unwrap();
// Convert to a String
let s = String::from_utf8(b).expect("Found Invalid UTF-8");
// Reverse it and re-encode
let enc = base64::encode(s.chars().rev().collect::<String>());

// Create the response
let rsp = ServerResponse {
status: Status {
code: 200,
status: "OK".to_string(),
},
payload: enc,
headers: HashMap::new(),
};

// Marshal the response
let r = serde_json::to_vec(&rsp).unwrap();

// Return JSON byte array
Ok(r)
// Handler is the custom Tarmac Handler function that will receive a payload and
// must return a payload along with a nil error.
func Handler(payload []byte) ([]byte, error) {
// Perform a host callback to log the incoming request
_, err := wapc.HostCall("tarmac", "logger", "trace", []byte(fmt.Sprintf("Reversing Payload: %s", payload)))
if err != nil {
return []byte(""), fmt.Errorf("Unable to call callback - %s", err)
}

// Flip it and reverse
if len(payload) > 0 {
for i, n := 0, len(payload)-1; i < n; i, n = i+1, n-1 {
payload[i], payload[n] = payload[n], payload[i]
}
}

// Return the payload via a ServerResponse JSON
return payload, nil
}
```

Tarmac passes the HTTP Context and Payload to the WASM function via the incoming `msg`. The `msg` is a JSON that contains Headers and a Payload which is Base64 encoded but otherwise untouched.
Tarmac passes the HTTP Payload to the WASM function untouched.

To compile the example above, run:

```text
$ cd example/tac/rust
$ cd example/tac/go
$ make build
```

Expand Down
140 changes: 45 additions & 95 deletions docs/wasm-functions/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,73 +20,41 @@ Tarmac internally uses a Web Assembly Procedure Calls \(waPC\) runtime, which me

```go
import (
"encoding/base64"
"fmt"
"github.com/valyala/fastjson"
wapc "github.com/wapc/wapc-guest-tinygo"
)
```

In the code example, above you will see other imported packages such as `base64` and `fastjson` these will be used and discussed later in this guide.

Once the waPC package is imported, we will create a `main()` function; this function will be our primary entry point for Tarmac execution. Within this function, we will register other handler functions for Tarmac to execute using the `wapc.RegisterFunctions` function.

```go
func main() {
wapc.RegisterFunctions(wapc.Functions{
// Register a POST request handler
"http:POST": Handler,
"POST": Handler,
// Register a PUT request handler
"http:PUT": Handler,
"PUT": Handler,
})
}
```

In the example above, we have registered the `Handler` function under two Tarmac routes; `http:POST` and `http:PUT`. When Tarmac receives an HTTP POST request for this WASM Function, it will execute the handler function as defined. If we wanted this function also to be used for HTTP GET requests, we could add another line registering it under `http:GET`.
In the example above, we have registered the `Handler` function under two Tarmac routes; `POST` and `PUT`. When Tarmac receives an HTTP POST request for this WASM Function, it will execute the handler function as defined. If we wanted this function also to be used for HTTP GET requests, we could add another line registering it under `GET`.

With our handler function registered, we must create a basic version of this function.
With our handler function registered, we must create a basic function.

```go
func Handler(payload []byte) ([]byte, error) {
// Return the payload via a ServerResponse JSON
return []byte(`{"payload":"","status":{"code":200,"status":"Success"}}`), nil
return []byte(`Success`), nil
}

```

As we can see from the example above, the handler has a byte slice input and return value. These are the Server Request and Server Response JSON payloads outlined in the [Inputs & Outputs](inputs-and-outputs.md) documentation.
As we can see from the example above, the handler has a byte slice input and return value. The HTTP payload is sent as the input untouched.

### Adding Logic

Now that we have the basic structure of our WASM function created, we can start adding logic to the function and process our request.

#### Parsing the Server Request

The first step in our logic will be to Parse the request payload.

```go
// Parse the JSON request
rq, err := fastjson.ParseBytes(payload)
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to call parse json - %s"}}`, err)), nil
}
```

In the above code, we are using the [fastjson](https://github.com/valyala/fastjson) package to parse the Server Request JSON. The reason we are using fastjson instead of the traditional JSON decoder is that TinyGo at the moment has limited JSON support. With fastjson we can parse JSON messages with no problems; however, creating JSON messages for the Server Response \(as shown above\) is a bit manual. As TinyGo progresses or WASM support is added to the main Go project, this issue should be resolved.

#### Decoding the HTTP Payload

After parsing the Server Request JSON, the next step we need to perform is decoding the payload.

```go
// Decode the payload
s, err := base64.StdEncoding.DecodeString(string(rq.GetStringBytes("payload")))
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to perform base64 decode - %s"}}`, err)), nil
}
b := []byte(s)
```

In order to avoid conflicts with the Server Request JSON, the original HTTP payload is base64 encoded. To access the original contents, we must decode them.

#### Host Callbacks
Expand All @@ -99,7 +67,7 @@ For our example, we will use the Host Callbacks to create a Trace log entry.
// Perform a host callback to log the incoming request
_, err = wapc.HostCall("tarmac", "logger", "trace", []byte(fmt.Sprintf("Reversing Payload: %s", s)))
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to call host callback - %s"}}`, err)), nil
return []byte(fmt.Sprintf("Failed to call host callback - %s", err)), nil
}
```

Expand All @@ -111,22 +79,19 @@ We can add our logic to the example, which in this case will be a payload revers

```go
// Flip it and reverse
if len(b) > 0 {
for i, n := 0, len(b)-1; i < n; i, n = i+1, n-1 {
b[i], b[n] = b[n], b[i]
if len(payload) > 0 {
for i, n := 0, len(payload)-1; i < n; i, n = i+1, n-1 {
payload[i], payload[n] = payload[n], payload[i]
}
}
```

Now with our WASM function complete, we must return a Server Response JSON with our reply payload.
Now with our WASM function complete, we must return a response.

```go
// Return the payload via a ServerResponse JSON
return []byte(fmt.Sprintf(`{"payload":"%s","status":{"code":200,"status":"Success"}}`, base64.StdEncoding.EncodeToString(b))), nil
return payload, nil
```

As mentioned earlier in this document, in Go creating a JSON must be performed manually. The example above encodes our new payload with base64 and returns it within a JSON string.

### Full WASM function

For quick reference, below is the full WASM function from this example.
Expand All @@ -137,64 +102,49 @@ For quick reference, below is the full WASM function from this example.
package main

import (
"encoding/base64"
"fmt"
"github.com/valyala/fastjson"
wapc "github.com/wapc/wapc-guest-tinygo"
"fmt"
wapc "github.com/wapc/wapc-guest-tinygo"
)

func main() {
// Tarmac uses waPC to facilitate WASM module execution. Modules must register their custom handlers under the
// appropriate method as shown below.
wapc.RegisterFunctions(wapc.Functions{
// Register a GET request handler
"http:GET": NoHandler,
// Register a POST request handler
"http:POST": Handler,
// Register a PUT request handler
"http:PUT": Handler,
// Register a DELETE request handler
"http:DELETE": NoHandler,
})
// Tarmac uses waPC to facilitate WASM module execution. Modules must register their custom handlers under the
// appropriate method as shown below.
wapc.RegisterFunctions(wapc.Functions{
// Register a GET request handler
"GET": NoHandler,
// Register a POST request handler
"POST": Handler,
// Register a PUT request handler
"PUT": Handler,
// Register a DELETE request handler
"DELETE": NoHandler,
})
}

// NoHandler is a custom Tarmac Handler function that will return a tarmac.ServerResponse JSON that denies
// NoHandler is a custom Tarmac Handler function that will return an error that denies
// the client request.
func NoHandler(payload []byte) ([]byte, error) {
return []byte(`{"status":{"code":503,"status":"Not Implemented"}}`), nil
return []byte(""), fmt.Errorf("Not Implemented")
}

// Handler is the custom Tarmac Handler function that will receive a tarmac.ServerRequest JSON payload and
// must return a tarmac.ServerResponse JSON payload along with a nil error.
// Handler is the custom Tarmac Handler function that will receive a payload and
// must return a payload along with a nil error.
func Handler(payload []byte) ([]byte, error) {
// Parse the JSON request
rq, err := fastjson.ParseBytes(payload)
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to call parse json - %s"}}`, err)), nil
}

// Decode the payload
s, err := base64.StdEncoding.DecodeString(string(rq.GetStringBytes("payload")))
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to perform base64 decode - %s"}}`, err)), nil
}
b := []byte(s)

// Perform a host callback to log the incoming request
_, err = wapc.HostCall("tarmac", "logger", "trace", []byte(fmt.Sprintf("Reversing Payload: %s", s)))
if err != nil {
return []byte(fmt.Sprintf(`{"status":{"code":500,"status":"Failed to call host callback - %s"}}`, err)), nil
}

// Flip it and reverse
if len(b) > 0 {
for i, n := 0, len(b)-1; i < n; i, n = i+1, n-1 {
b[i], b[n] = b[n], b[i]
}
}

// Return the payload via a ServerResponse JSON
return []byte(fmt.Sprintf(`{"payload":"%s","status":{"code":200,"status":"Success"}}`, base64.StdEncoding.EncodeToString(b))), nil
// Perform a host callback to log the incoming request
_, err := wapc.HostCall("tarmac", "logger", "trace", []byte(fmt.Sprintf("Reversing Payload: %s", payload)))
if err != nil {
return []byte(""), fmt.Errorf("Unable to call callback - %s", err)
}

// Flip it and reverse
if len(payload) > 0 {
for i, n := 0, len(payload)-1; i < n; i, n = i+1, n-1 {
payload[i], payload[n] = payload[n], payload[i]
}
}

// Return the payload via a ServerResponse JSON
return payload, nil
}
```

Expand Down
Loading

0 comments on commit 66465e7

Please sign in to comment.