Handling multiple paths in a Rust Application
Prerequisites
Section titled “Prerequisites”- Language-specific tools - Install Heim Prerequisites
- Heim cli and runtime - Install Heim
- Text Editor - VsCode is a great option with language-specific plugins
- Terminal - Heim is accessed through its command-line interface
Create a new project from the rust-http template.
read more
Using a template
Section titled “Using a template”Start by running
heim newto create a project from a template.Use the up/down arrow keys to select the template and press enter:
Heim Cli heim@system:~/repos$ heim new? Select template: ›csharp-httpcsharp-http-async...❯ rust-httpNext, we will choose the http method that the application will respond on.
We want to our application to respond to all types of methods.
For this we will choose the value of
ALL:Heim Cli ✔ Select template: · rust-http? Http trigger method POST/GET, Default (GET) › ALLNext we need to choose the path that the application will listen on. We’ll also set the HTTP method. The path and HTTP method will be used when we call the application, later.
We will use
*as a wildcard to allow any subpath for the trigger, and the HTTP methodALL, so that any HTTP method is usable.Heim Cli ✔ Select template: · rust-http✔ Http trigger method POST/GET, Default · ALL? Http trigger path /??? , Default (/hello) › /multi-path-app/*Last we will need the name and version of the application:
Heim Cli ✔ Select template: · rust-http✔ Http trigger method POST/GET, Default · ALL✔ Http trigger path /??? , Default · /multi-path-app/*? Package name, (Required) › multi-path-app...? Input a string for value version, Default (0.1.0) ›Rendered Template: ""/tmp/heimQ2z7DE/multi-path-app/component.toml""Rendered Template: ""/tmp/heimQ2z7DE/multi-path-app/application.toml""Rendered Template: ""/tmp/heimQ2z7DE/multi-path-app/src/lib.rs""Rendered Template: ""/tmp/heimQ2z7DE/multi-path-app/Cargo.toml""[00:00:00] ######################################## 5/5 SuccessINFO [ Heim new ] New component created: Some("/home/heim/repos/multi-path-app")Have a look at the newly created project folder.
You’ll have an application folder with this file structure:
Directorysrc/
- lib.rs
- .gitignore
- Cargo.toml
- component.toml
- application.toml
read more
Examining file structure
Section titled “Examining file structure”Now we have a project ready to go. Lets open the project in your text editor and see what we have.
component.toml
Section titled “component.toml”The component.toml file, defines a component, and one or more components make up an application in Heim.
component.tomlname = "multi-path-app" # Component nameversion = "0.1.0" # Component language[commands] # Custom commands that can be used from the heim clirun = "wasmtime serve -S cli target/wasm32-wasip1/debug/multi-path-app.wasm --addr=127.0.0.1:8080"run_production = "wasmtime serve -S cli target/wasm32-wasip1/release/multi-path-app.wasm --addr=127.0.0.1:8080"[build.dev] # Dev build command and outputbuild = "cargo component build --target=wasm32-wasip1"wasm_path = "target/wasm32-wasip1/debug/multi_path_app.wasm"[build.prod] # Prod build command and outputbuild = "cargo component build --target=wasm32-wasip1 --release"wasm_path = "target/wasm32-wasip1/release/multi_path_app.wasm"a simple component.toml, with explanatory comments
application.toml
Section titled “application.toml”The application.toml is what defines an application. A application.toml is required for deploying an application to Heim.
application.tomlapplication.toml name = "multi-path-app" # application namecomponent_target = 'multi-path-app:0.1.0' # component to target[[trigger]] # Trigger definitiontype = 'http'path = '/multi-path-app/*'method = 'ALL'
Have a look at the newly created project folder.
Open
src/lib.rsto check the code. Deploy and start the application by using the Heim CLI.read more
Get started with the new application
Section titled “Get started with the new application”Open
src/lib.rsand take a look at the code:src/lib.rs use waki::{handler, ErrorCode, Request, Response};#[handler]fn hello(req: Request) -> Result<Response, ErrorCode> {Response::builder().status_code(200).body("Hello World").build()}Now, we want to start the application. Read on to learn the steps needed to start an application.
Run heim start, to start the heim runtime:
heim runtime heim@system:~/repos$ heim startStarting Runtime with Config:...Http server listening on: http://127.0.0.1:3000Heim portal: http://127.0.0.1:3000/heim/portal/Note that the Heim portal comes included with any Heim installation, and that it will be available on
http://127.0.0.1:3000/heim/portal/once you’ve installed Heim.To build the application and deploy it to our local-instance, we will run heim deploy. By default, this will create a debug build, but we can change it to a release build with the
--releaseflag:heim cli heim@system:~/repos$ heim deployINFO [ heim_cli::wasm::build ] Building Component: myappCompiling myapp v0.1.0 (/home/work/repos/myapp)...Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18sCreating component target/wasm32-wasip2/debug/myapp.wasmINFO [ heim_registry::registry ] "myapp":"0.1.0":cb4abe161542a7b146fe27c5f5cdb60970d68a369f5703bde252664ddbe2eedfINFO [ Heim::Deploy ] Heim application `myapp` is now built and written to registry.INFO [ Heim::Pipeline ] Starting pipeline 'Deploy application'INFO [ Heim::Pipeline ] Storing unoptimized component stateINFO [ Heim::Pipeline ] Checking if defined regions in app definition is validINFO [ Heim::Pipeline ] Continuing with localhost region heim-localhostINFO [ Heim::Pipeline ] Getting region specific application environment valuesINFO [ Heim::Pipeline ] Set environment values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting region specific application scaling valuesINFO [ Heim::Pipeline ] Set scaling values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting capabilitiesINFO [ Heim::Pipeline ] Set capabilities values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting triggersINFO [ Heim::Pipeline ] Set trigger values for region 'heim-localhost'INFO [ Heim::Pipeline ] Component environment values are valid for all regionsINFO [ Heim::Pipeline ] Checking for region with changes to buildINFO [ Heim::Pipeline ] Region "heim-localhost" has been changed and will be builtINFO [ Heim::Pipeline ] Storing component and optimized componentINFO [ Heim::Pipeline ] Stored component at ''INFO [ Heim::Pipeline ] Stored optimized component at ''INFO [ Heim::Pipeline ] Storing region statesINFO [ Heim::Pipeline ] Stored state for region(s) heim-localhost successfullyINFO [ Heim::Pipeline ] Creating and caching application(s)INFO [ Heim::Pipeline ] Creating and caching application(s) for region(s) heim-localhostINFO [ Heim::Pipeline ] Created application with hash 8aba8efca7c309fe7cafce25df43fe78bbe9d0479f9b709cea97f9d507048a11 for region(s) heim-localhostINFO [ Heim::Pipeline ] Application has been deployed for region(s) heim-localhostINFO [ Heim::Pipeline ] Pipeline 'Deploy application' finished with status 'Success'INFO [ Heim::Deploy ] Heim deploy pipeline was successfulINFO [ Heim::Deploy ] Application is accaccessible at:INFO [ Heim::Deploy ] [GET] http://127.0.0.1:3000/myappINFO [ Heim::Deploy ] Written and deployed application `myapp`.From here, you can see that we are able to access the application on
http://127.0.0.1:3000/myapp:curl heim@system:~/repos$ curl http://127.0.0.1:3000/myappHello World!To deploy the application to the cloud instance, we will run heim deploy with the
--cloudflag. This will generate a release build as default, but it can be changed to a debug with the the--devflag.This will deploy the application to your subdomain at
my-subdomain.cloud.heim.devheim cli heim@system:~/repos$ heim deploy --cloudINFO [ heim_registry::registry ] "myapp":"0.1.0":02f6d7d3c34210438f293efc1ff2f8e9494f783a402e5dcbe6a308fea40d870eINFO [ Heim::Deploy ] Heim application `myapp` is now built and written to registry.INFO [ Heim::Pipeline ] Starting pipeline 'Deploy application'INFO [ Heim::Pipeline ] Storing unoptimized component stateINFO [ Heim::Pipeline ] Checking if defined regions in app definition is validINFO [ Heim::Pipeline ] Continuing with localhost region heim-localhostINFO [ Heim::Pipeline ] Getting region specific application environment valuesINFO [ Heim::Pipeline ] Set environment values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting region specific application scaling valuesINFO [ Heim::Pipeline ] Set scaling values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting capabilitiesINFO [ Heim::Pipeline ] Set capabilities values for region 'heim-localhost'INFO [ Heim::Pipeline ] Getting triggersINFO [ Heim::Pipeline ] Set trigger values for region 'heim-localhost'INFO [ Heim::Pipeline ] Component environment values are valid for all regionsINFO [ Heim::Pipeline ] Checking for region with changes to buildINFO [ Heim::Pipeline ] Region "heim-localhost" has been changed and will be builtINFO [ Heim::Pipeline ] Optimizing base componentINFO [ Heim::Pipeline ] Optimized base component, size before optimization: 2881425 bytes, size after optimization: 2278867 bytesINFO [ Heim::Pipeline ] Storing component and optimized componentINFO [ Heim::Pipeline ] Stored component at ''INFO [ Heim::Pipeline ] Stored optimized component at ''INFO [ Heim::Pipeline ] Storing region statesINFO [ Heim::Pipeline ] Stored state for region(s) heim-localhost successfullyINFO [ Heim::Pipeline ] Creating and caching application(s)INFO [ Heim::Pipeline ] Creating and caching application(s) for region(s) heim-localhostINFO [ Heim::Pipeline ] Created application with hash 62fd854418968a7c0cc383e962382dfc8e2d420b7fa5a4d48c6058ebb8544a05 for region(s) heim-localhostINFO [ Heim::Pipeline ] Application has been deployed for region(s) heim-localhostINFO [ Heim::Pipeline ] Pipeline 'Deploy application' finished with status 'Success'INFO [ Heim::Deploy ] Heim deploy pipeline was successfulINFO [ Heim::Deploy ] Application is accaccessible at:INFO [ Heim::Deploy ] [GET] https://my-subdomain.cloud.heim.dev/myappINFO [ Heim::Deploy ] Written and deployed application `myapp`.And, in the heim runtime you will see something like this, if you started the runtime with the
--verboseflag:INFO WasmComponentHandler{app_id="localhost:00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000:myapp:bbcb756cbf72e572fd47f3c299f44e1d083522aaa2696c3b0b24653deb884d3c"uri=/myapp}:heim_runtime::services::handlers::wasm_component_handler: 48:app_id="localhost:00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000:myapp:bbcb756cbf72e572fd47f3c299f44e1d083522aaa2696c3b0b24653deb884d3c"uri=/myapp method=GETtrigger=Trigger {app_id: AppId("localhost:00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000:myapp:bbcb756cbf72e572fd47f3c299f44e1d083522aaa2696c3b0b24653deb884d3c"),path: "/myapp",method: "GET",function_target: None,hash: "",domain: "127.0.0.1"}The Heim runtime output above summarizes all the basic information about your application.
Now that we’ve gone through the basic steps required to create and start a new application, it’s time to add the functionality we need for this particular application.
Let’s add a simple router handling multiple paths, with json.
We’ll also add serde and serde_json to the
Cargo.tomlyou’ll find in the application folder. This will allow us to easily work with json.Lastly, we also want to add support for calling an external api by HTTP from our application.
Cargo.toml[package]name = "multi-path-app"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"][package.metadata.component]package = "multi-path-app:myapp"proxy = false[dependencies]waki = { version = "0.5.0", features = ["json"] }serde = { version = "1.0.216", features = ["derive"] }serde_json = "1.0.133"# reduce wasm binary size[profile.release]lto = truestrip = "symbols"Add the new section called
capabilitiesto theapplication.toml. This will allow our application to call outside URLs:application.tomlname = "multi-path-app"component_target = 'multi-path-app:latest'[trigger.http]type = 'http'path = '/multi-path-app/*'method = 'ALL'path = '/hello'method = 'GET'[capabilities]allowed-outgoing-url = ["https://dummyjson.com/*"]Now, it’s time to update our code. We’ll implement a simple router and add conditions based on the called path and HTTP method:
src/lib.rsuse serde::{Deserialize, Serialize};use std::{collections::HashMap, time::Duration};use waki::{handler, header::ACCEPT, header::CONTENT_TYPE, ErrorCode, Method, Request, Response};use waki::{handler, ErrorCode, Request, Response};pub const GET_QUERY_PATH: &str = "/multi-path-app/v1/get";pub const POST_BODY_PATH: &str = "/multi-path-app/v1/post";#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SearchQuery {search_text: String,result_limit: i32,}#[handler]fn hello(req: Request) -> Result<Response, ErrorCode> {Response::builder().status_code(200).body("Hello World").build()let path = &req.path().to_string();let query = &req.query();let method = &req.method();//Here we have implemented a really simple router based on method and path in the request.match (method, path.as_str()) {(Method::Post, POST_BODY_PATH) => {let body = match &req.body() {Ok(body_value) => body_value.clone(),Err(err) => {return bad_request(format!("Invalid body request sent, {err:#?}").as_str())}};post_body_handler(&body)}(Method::Get, GET_QUERY_PATH) => get_body_handler(query),_ => not_found(),}}/// Not found responsepub fn not_found() -> Result<Response, ErrorCode> {Response::builder().status_code(404).body("NOT FOUND!".to_string()).build()}/// Bad request responsepub fn bad_request(msg: &str) -> Result<Response, ErrorCode> {Response::builder().status_code(400).body(msg.to_string()).build()}pub fn get_body_handler(query: &HashMap<String, String>) -> Result<Response, ErrorCode> {let _search: String = match query.get("search") {Some(search) => search.to_string(),None => return bad_request("We are missing the required query param called Search"),};let client = waki::Client::new();let result = client.get("https://dummyjson.com/recipes/1").headers([("Content-Type", "application/json"), ("Accept", "*")]).connect_timeout(Duration::from_secs(1)).send().and_then(|res| res.body().map_err(|e| e.into())).and_then(|body| String::from_utf8(body).map_err(|e| e.into()));let body = match result {Ok(text_body) => text_body,Err(err) => format!("We failed to retrieve the response: {err}"),};Response::builder().status_code(200).headers([(CONTENT_TYPE, "application/json")]).body(body).build()}/// Post body responsepub fn post_body_handler(body: &Vec<u8>) -> Result<Response, ErrorCode> {match serde_json::from_slice::<SearchQuery>(&body) {Ok(value) => Response::builder().status_code(200).body(format!("We received the following search query: {value:#?}")).build(),Err(err) => bad_request(&format!("Failed deserialize the json body: {err}")),}}Since the Heim runtime is already running, we just need to build and deploy, using the Heim CLI:
heim cli heim@system:~/repos$ heim deployINFO [ heim_cli::wasm::build ] Building Component: multi-path-appCompiling multi-path-app v0.1.0 (/home/work/repos/multi-path-app)...Finished `release` profile [optimized] target(s) in 0.05sINFO [ heim_cli::wasm::wasmodule ] multi-path-app:0051d2ecfc1866c7a33b310a8df4691141655dba34086e60e4a23ebf78dc2525INFO [ Heim deploy ] Built and published multi-path-app/0051d2ecfc1866c7a33b310a8df4691141655dba34086e60e4a23ebf78dc2525INFO [ Heim deploy ] Deployed to Heim-portal-backend multi-path-app/0051d2ecfc1866c7a33b310a8df4691141655dba34086e60e4a23ebf78dc2525INFO [ Heim deploy ] Access at: http://127.0.0.1:3000/multi-path-app/* with Method ALLINFO [ Heim::Publish ] Written and deployed module multi-path-app to runtime for profile localcurl heim@system:~/repos$ curl http://127.0.0.1:3000/multi-path-app/v1/post -X POST{"error":"Failed deserialize the json body","name":null,"msg":null}heim@system:~/repos$ curl http://127.0.0.1:3000/multi-path-app/v1/post -d '{ "search_text": "heim", "result_limit": 2}'{"We received the following search query: { search_text: heim, result_limit: 2}"}When we are done, we can use the shortcut
Ctrl+Cto shut down the runtime.To clean up any resources from the guide you can run
heim clear. This will remove all data from the cache.
Summary
Section titled “Summary”-
- run
heim newand use the rust-httptemplateto create a new application
- run
-
- open the created application folder/project in your text editor and check out the configuration files
-
- open
lib.rsand have a look at the code - run
heim startto start the Heim runtime - run
heim buildto build the application. - run
heim deploy --local ${application_name}to deploy the application while targeting the local runtime
- open
-
- edit the
Cargo.tomlfile to add useful dependencies - edit the
component.tomlfile to add environment variables - edit the
application.tomlfile to change the HTTP method - edit the
application.tomlfile to change the trigger path - edit the
application.tomlfile to set environment variable values - edit the
src/lib.rsfile to use new environment variables - edit the
src/lib.rsfile to set up return and accept values - deploy the application again to see your changes
- edit the
-
- use the
Ctrl+Cshortcut to shut down the Heim runtime - use the
heim clearcommand to remove all data from cache
- use the