Create
- Rust
- TinyGo
- TypeScript
- Python
- My Language Isn't Listed
Let's create a component that accepts an HTTP request and responds with "Hello from Rust!"
To create your new component project, change to the directory where you want the project to be created, and enter the command below.
- The first term on the command (
hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide. - Every new wasmCloud project is created from a template. If you do not specify a template,
wash
will give you options to select. In this case, we're using thehello-world-rust
template. You can find this template hosted in the wasmCloud monorepo.
wash new component hello --template-name hello-world-rust
Navigate to the new hello
directory and take a look at the generated project. The file src/lib.rs
includes a wit-bindgen
generate macro, an imports section, a struct HttpServer
, and an impl
block that implements the Guest
trait for the component struct. Let's walk through these sections in detail.
wit_bindgen::generate!();
This shows us that we're using a core wasmCloud package called wit-bindgen
to generate the types and bindings for our component. This is automatically done from the wit/world.wit
file, any changes to imports or exports will update automatically on the next build.
Note the two lines near the top of the source code file:
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;
These two imports are bringing in the Guest
trait to implement our function to be called whenever an incoming HTTP request is received, and the types that we'll use to interact with the HTTP request and response. These are the only two imports that we need to interact with the HTTP server capability.
struct HttpServer;
impl Guest for HttpServer {
fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
let response = OutgoingResponse::new(Fields::new());
response.set_status_code(200).unwrap();
let response_body = response.body().unwrap();
response_body
.write()
.unwrap()
.blocking_write_and_flush(b"Hello from Rust!\n")
.unwrap();
OutgoingBody::finish(response_body, None).expect("failed to finish response body");
ResponseOutparam::set(response_out, Ok(response));
}
}
Within the handle
method, the component receives the HTTP request, creates an OutgoingResponse
and writes that response out back to the requesting http client (such as a curl
command or a web browser).
Finally, we use the wit-bindgen
generated macro export!
to export the HttpServer
struct as the incoming handler for the HTTP server capability. If you don't implement the Guest
trait for your struct, the export!
macro will give you an error at compile time noting which functions you need to implement.
export!(HttpServer);
Let's create a component that accepts an HTTP request and responds with "Hello from Go!"
To create your new component project, change to the directory where you want the project to be created, and enter the command below.
- The first term on the command (
hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide. - Every new wasmCloud project is created from a template. If you do not specify a template,
wash
will give you options to select. In this case, we're using thehello-world-tinygo
template. You can find this template hosted in the wasmCloud monorepo.
wash new component hello --template-name hello-world-tinygo
Navigate to the new hello
directory and take a look at the generated project. The file main.go
includes imports, init
and handleRequest
functions, and an empty main
function.
Let's walk through these pieces in depth.
//go:generate go run go.bytecodealliance.org/cmd/wit-bindgen-go generate --world hello --out gen ./wit
package main
import (
"fmt"
"net/http"
"go.wasmcloud.dev/component/net/wasihttp"
)
func init() {
// Register the handleRequest function as the handler for all incoming requests.
wasihttp.HandleFunc(handleRequest)
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from Go!\n")
}
// Since we don't run this program like a CLI, the `main` function is empty. Instead,
// we call the `handleRequest` function when an HTTP request is received.
func main() {}
- The Go directive at the very top ensures that when we build our project with
tinygo build
, thewit-bindgen-go
tool will be run to generate the bindings for our component from thehello
WIT world found in the./wit
directory. Bindings will be generated in thegen
directory. - In the import section, we import Go's standard
fmt
andnet/http
libraries. We also import thewasihttp
package, which provides an implementation ofhttp.Handler
backed bywasi:http
—enabling us to write idiomatic Go using a language-agnostic WASI interface. - In the
init
section, we set thehandleRequest
function to handle incoming HTTP requests. - In the
handleRequest
function, we respond to an HTTP request by printingHello from Go!
- The main function is empty, since the component doesn't run like a CLI, but instead runs the
handleRequest
function when triggered by an HTTP request.
Let's create a component that accepts an HTTP request and responds with "Hello from TypeScript!
To create your new component project, change to the directory where you want the project to be created, and enter the command below.
- The first term on the command (
hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide. - Every new wasmCloud project is created from a template. If you do not specify a template,
wash
will give you options to select. In this case, we're using thehello-world-typescript
template. You can find this template hosted in the wasmCloud monorepo.
wash new component hello --template-name hello-world-typescript
Navigate to the new hello
directory and take a look at the generated project. The file http-hello-world.ts
will include imports and a handle
function exported under incomingHandler
. Let's walk through these pieces in depth.
import { IncomingRequest, ResponseOutparam, OutgoingResponse, Fields } from 'wasi:http/types@0.2.0';
The import section of this file imports our generated types (from jco
) for the WASI HTTP interface.
// Implementation of wasi-http incoming-handler
//
// NOTE: To understand the types involved, take a look at wit/deps/http/types.wit
function handle(req: IncomingRequest, resp: ResponseOutparam) {
// Start building an outgoing response
const outgoingResponse = new OutgoingResponse(new Fields());
// Access the outgoing response body
let outgoingBody = outgoingResponse.body();
{
// Create a stream for the response body
let outputStream = outgoingBody.write();
// Write hello world to the response stream
outputStream.blockingWriteAndFlush(
new Uint8Array(new TextEncoder().encode('Hello from Typescript!\n')),
);
// @ts-ignore: This is required in order to dispose the stream before we return
outputStream[Symbol.dispose]();
}
// Set the status code for the response
outgoingResponse.setStatusCode(200);
// Finish the response body
OutgoingBody.finish(outgoingBody, undefined);
// Set the created response
ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
}
The business logic of our component is contained within the handle
function. Within the handle
function, the component receives the HTTP request, creates an OutgoingResponse
and writes that response back out to the requesting HTTP client (such as a curl
command or a web browser).
export const incomingHandler = {
handle,
};
Lastly, this export statement includes the handle
function under the incomingHandler
interface, which matches the wasi:http/incoming-handler
interface declared in the application's wit file.
Let's create a component that accepts an HTTP request and responds with "Hello from Python!"
To create your new component project, change to the directory where you want the project to be created, and enter the command below.
- The first term on the command (
hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide. - Every new wasmCloud project is created from a template. If you do not specify a template,
wash
will give you options to select. In this case, we're using thehello-world-python
template. You can find this template hosted in the wasmCloud monorepo.
wash new component hello --template-name hello-world-python
Navigate to the new hello
directory and take a look at the generated project. The file app.py
will include imports and a handle
function on a IncomingHandler
class. Let's walk through these pieces in depth.
from hello import exports
from hello.types import Ok
from hello.imports.types import (
IncomingRequest, ResponseOutparam,
OutgoingResponse, Fields, OutgoingBody
)
In the import section of this file, we import our generated types (from componentize-py
) for the WASI HTTP interface.
class IncomingHandler(exports.IncomingHandler):
def handle(self, _: IncomingRequest, response_out: ResponseOutparam):
# Construct the HTTP response to send back
outgoingResponse = OutgoingResponse(Fields.from_list([]))
# Set the status code to OK
outgoingResponse.set_status_code(200)
outgoingBody = outgoingResponse.body()
# Write our Hello World message to the response body
outgoingBody.write().blocking_write_and_flush(bytes("Hello from Python!\n", "utf-8"))
OutgoingBody.finish(outgoingBody, None)
# Set and send the HTTP response
ResponseOutparam.set(response_out, Ok(outgoingResponse))
The business logic of our component is contained within the handle
method. Within the handle
method, the component receives the HTTP request, creates an OutgoingResponse
and writes that response back out to the requesting HTTP client (such as a curl
command or a web browser).
This method is defined on the IncomingHandler
class, which matches the wasi:http/incoming-handler
interface declared in the application's wit file.
We're looking to add more examples in languages that support Wasm components. If you prefer working in a language that isn't listed here, let us know!
If you use an IDE that comes with code completion and hover-tooltips, you'll be able to see documentation and get strongly-typed guidance as you develop code to interact with the WASI interfaces.
The above component example works without any modification in wasmCloud, but is specifically built using standard WebAssembly tooling. You do not need to use a wasmCloud-specific SDK to build components, and the above example will work directly with any WebAssembly runtime that supports the Wasm component model.
Something's missing
Before we get into modifying the scaffolding to create the rest of this component, take a look at what's not included in this code. This code returns an abstraction of an HTTP response. It is not tightly coupled to any particular HTTP server. Furthermore, you don't see the port number or server configuration options anywhere in the code. Finally, you can scale and compose this component any way you see fit without ever having to recompile or redeploy it.
This method of interface driven development is a core piece of the wasmCloud project philosophy. You should be able to write business logic in a language of your choice without having to worry about the non-functional requirements of your application, letting you configure loosely coupled abstractions at runtime. Moving on past our project scaffolding, the next step is to build and run our component.