Skip to main content
Version: 0.82

Adding Capabilities

Going from "Hello World" to a full-fledged application requires just identifying what capabilities your application needs, and then adding them to your actor. Capabilities in wasmCloud can invoke a function handler in your actor in response to some external trigger (like HTTP Server and Messaging) and actors can invoke capabilities as a part of handling a request (like key-value store and logging). "Hello World" already has the HTTP Server capability, so let's add more features to this application with functional code and more capabilities.

Adding Functionality

Rust & WebAssembly

To perform the steps in this guide, you'll need to have a Rust toolchain installed locally and the wasm32 target installed:

bash
rustup target add wasm32-wasi

Let's extend this application to do more than just say "Hello from Rust!" Using the path_with_query method on the incoming request, we can check the request for a name provided in a query string, and then return a greeting with that name. If there isn't one or the path isn't in the format we expect, we'll default to saying "Hello, World!".

rust
wit_bindgen::generate!({
    world: "hello",
    exports: {
        "wasi:http/incoming-handler": HttpServer,
    },
});

use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;

struct HttpServer;

impl Guest for HttpServer {
    fn handle(_request: IncomingRequest, response_out: ResponseOutparam) { 
    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();
        let name = match request 
            .path_with_query()
            .unwrap()
            .split("=")
            .collect::<Vec<&str>>()[..]
        {
            // query string is "/?name=<name>" e.g. localhost:8080?name=Bob
            ["/?name", name] => name.to_string(),
            // query string is anything else or empty e.g. localhost:8080
            _ => "World".to_string(),
        };
        response_body
            .write()
            .unwrap()
            .blocking_write_and_flush(b"Hello from Rust!\n") 
            .blocking_write_and_flush(format!("Hello, {}!\n", name).as_bytes()) 
            .unwrap();
        OutgoingBody::finish(response_body, None).expect("failed to finish response body");
        ResponseOutparam::set(response_out, Ok(response));
    }

After changing the code, you can use wash to build the local actor:

bash
wash build

Deploying your Actor

Now that you've made an update to your actor, you can use wash to stop the previous version. You can supply either the name of the actor or the full 56 character signing key to the wash stop actor command. wadm will take care of starting the new local copy from the updated file.

bash
wash stop actor http-hello-world
info

Whenever you make a change to your actor that you want to deploy, be sure to run wash build to recompile and generate a new .wasm file.

bash
> curl localhost:8080
Hello, World!
> curl 'localhost:8080?name=Bob'
Hello, Bob!

Adding Persistent Storage

To further enhance our application, let's add persistent storage to keep a record of each person that this application greeted. We'll use the key-value store capability for this, and just like HTTP server, you won't need to pick a library or a specific vendor implementation yet. You'll just need to add the capability to your actor, and then you can pick a capability provider at runtime.

In your template, we already included the wasi:keyvalue interface for interacting with a key value store. We can also use the wasi:logging interface to log the name of each person we greet. Before you can use the functionality of those interfaces, you'll need to add a few imports to your wit/hello.wit file:

wit
package wasmcloud:hello;

world hello {
  import wasi:keyvalue/atomic@0.1.0; 
  import wasi:keyvalue/eventual@0.1.0; 
  import wasi:logging/logging; 

  export wasi:http/incoming-handler@0.2.0;
}

Let's use the atomic increment function to keep track of how many times we've greeted each person.

rust
    let name = match request
        .path_with_query()
        .unwrap()
        .split("=")
        .collect::<Vec<&str>>()[..]
    {
        // query string is "/?name=<name>" e.g. localhost:8080?name=Bob
        ["/?name", name] => name.to_string(),
        // query string is anything else or empty e.g. localhost:8080
        _ => "World".to_string(),
    };

    wasi::logging::logging::log( 
        wasi::logging::logging::Level::Info,
        "",
        &format!("Greeting {name}"),
    );

    let bucket =
        wasi::keyvalue::types::Bucket::open_bucket("").expect("failed to open empty bucket");
    let count = wasi::keyvalue::atomic::increment(&bucket, &name, 1)
        .expect("failed to increment count");

    response_body
        .write()
        .unwrap()
        .blocking_write_and_flush(format!("Hello x{count}, {name}!\n").as_bytes())
        .unwrap();

Deploying a Key-Value Store Provider

Our actor is prepared to use a key-value store, and now that we've built it we're ready to choose an implementation. A great option for local development and testing is the Redis provider, and will only require you to have redis-server or Docker installed.

Install and launch the local redis server in the background

bash
redis-server &

We can modify our wadm.yaml to include the Redis provider and configure a link for our actor. Since we're nearing the end of this tutorial, we'll provide the full manifest here:

yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: http-hello-world
  annotations:
    version: v0.0.3 # Increment the version number
    description: 'HTTP hello world demo, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
    experimental: true
spec:
  components:
    - name: http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        - type: spreadscaler
          properties:
            replicas: 50
        - type: linkdef
          properties:
            target: httpserver
            values:
              address: 0.0.0.0:8080
        # The new key-value link configuration
        - type: linkdef
          properties:
            target: keyvalue
            values:
              address: redis://127.0.0.1:6379

    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
    # The new capability provider
    - name: keyvalue
      type: capability
      properties:
        image: wasmcloud.azurecr.io/kvredis:0.22.0
        contract: wasmcloud:keyvalue

For the last step, we can deploy the v0.0.3 version of our application. Then, again, we can test our new functionality.

bash
> wash app deploy wadm.yaml
> curl 'localhost:8080?name=Bob'
Hello x1, Bob!
> curl 'localhost:8080?name=Bob'
Hello x2, Bob!
> curl 'localhost:8080?name=Alice'
Hello x1, Alice!

Moving on

In this tutorial, you added a few more features and persistent storage to a simple microservice. You also got to see the process of developing with interfaces, where the code you write is purely functional, doesn't require you to pick a library or vendor upfront, and allows you to change your application separately from its non-functional requirements. You can continue to build on this application by adding more features, or you can explore additional capabilities and providers in wasmCloud Capability Providers to get an idea of what is possible with wasmCloud.

The next page gives a high level overview of why this application that you've built already eliminates complexity and pain that developers often face when building applications for the cloud.