Azure Rust Function

## Intro Have been excited about Rust lately (*like everyone*) and found out that Azure Functions have Rust support by using a customHandler. Having worked with next.js and gone through the trouble of setting it up into AWS Lambda, I wanted to try out a bit similar approach of a serverless function that would listen to all http and serve back html (or json). This article __is not a tutorial__ in the sense that it would contain all steps and it would produce a similar working solution, I'm only attempting to highlight the most important bits below. I used the [Quickstart: Create a Go or Rust function in Azure using Visual Studio Code](https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=go%2Cwindows) as a starting point guide for this. However, we will slightly deviate from it to adapt it for serving html. __Fun fact:__ This blog was initially created into contentful as a sample data source to use in as a data retrieval example for the function. ## Source & Demo Repository: [https://github.com/jakke-korpelainen/az-fn-rust-http-html](https://github.com/jakke-korpelainen/az-fn-rust-http-html) Demo: [https://jakke-fi-function.azurewebsites.net/](https://jakke-fi-function.azurewebsites.net/) ## Azure Function related configurations To accept all http into all paths we need to make some changes. **function.json** Set the function route accordingly. Add __{\*subpath}__ as route into the first object inside bindings and change the methods if you require e.g. __put__ or __patch__. ```json { "bindings": [ { "authLevel": "anonymous", "type": "httpTrigger", "direction": "in", "name": "req", "route": "{*subpath}", "methods": [ "get", "post" ] }, { "type": "http", "direction": "out", "name": "res" } ] } ``` **host.json** In the host, we need to adjust the route prefix to serve all requests. ```json { "version": "2.0", "logging": { "applicationInsights": { "samplingSettings": { "isEnabled": true, "excludedTypes": "Request" } } }, "extensions": { "http": { "routePrefix": "" } }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", "version": "[4.*, 5.0.0)" }, "customHandler": { "description": { "defaultExecutablePath": "handler.exe", "workingDirectory": "", "arguments": [] }, "enableForwardingHttpRequest": true } } ``` ## Rust HTTP Server ([axum](https://github.com/tokio-rs/axum)) The sample used warp, I'm more familiar with axum. I went with the approach below. **main.rs** ```rust use axum::routing::get; use axum::Router; use std::env; use std::net::Ipv4Addr; use tower_http::services::ServeDir; mod home; mod posts; mod templates; #[tokio::main] async fn main() { http_server().await; } const PORT_KEY: &str = "FUNCTIONS_CUSTOMHANDLER_PORT"; async fn http_server() { let port: u16 = match env::var(PORT_KEY) { Ok(val) => val.parse().expect("Custom Handler port is not a number!"), Err(_) => 3000, }; let router = Router::new() .route("/", get(home::home_route)) .route("/posts", get(posts::posts_route)) .route("/posts/:post", get(posts::post_route)) .nest_service("/assets", ServeDir::new("public")); let listener = tokio::net::TcpListener::bind(format!("{}:{}", Ipv4Addr::LOCALHOST, port)) .await .unwrap(); axum::serve(listener, router).await.unwrap(); } ``` **home.rs** Example of compiling a template for rendering. Templates accept rendering parameters. ```rust pub async fn home_route() -> impl IntoResponse { HtmlTemplate(templates::HomeTemplate {}) } ``` ## Templating ([askama](https://github.com/djc/askama)) Askama is compiling the html and is able to detect misuse of variables and templating syntax, though the errors may be fairly confusing. ### Template Declaration Templates are be defined & typed in rust. **templates.rs** ```rust pub struct HtmlTemplate<T>(pub T); impl<T> IntoResponse for HtmlTemplate<T> where T: Template, { fn into_response(self) -> Response { match self.0.render() { Ok(html) => Html(html).into_response(), Err(err) => ( StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to render template. Error: {err}"), ) .into_response(), } } } #[derive(askama::Template, Debug)] #[template(path = "home.html")] pub struct HomeTemplate {} #[derive(askama::Template, Debug)] #[template(path = "posts.html")] pub struct BlogPostsTemplate { pub posts: Vec<BlogPost>, } #[derive(askama::Template, Debug)] #[template(path = "post.html")] pub struct BlogPostTemplate { pub post: Option<BlogPost>, } ``` ### Templating Syntax Example of using rust pattern matching and a loop inside a template. ``` {% match data.tags %} {% when Some with (tags) %} <p> Tags: {% for tag in tags %} <span>{{ tag }}</span> {% endfor %} </p> {% when None %} {% endmatch %} ``` **templates/_base.html** ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Blog Posts</title> <script src="https://cdn.tailwindcss.com"></script> </head> <body> <nav class="bg-gray-800 p-4"> <div class="container mx-auto flex justify-between items-center"> <a href="/" class="text-white text-lg font-semibold">Home</a> <div class="space-x-4"> <a href="/" class="text-gray-300 hover:text-white">Home</a> <a href="/posts" class="text-gray-300 hover:text-white">Blog</a> </div> </div> </nav> {% block main %} <!-- main --> {% endblock %} </body> </html> ``` **templates/home.html** An example of a page that is using the partial defined above. ```html {% extends "_base.html" %} <!-- Main --> {% block main %} <main class="container mx-auto"> <div class="flex items-center mt-10 gap-10"> <img class="max-h-52 rounded-full" src="/assets/image.jpg"/> <div> <h1 class="text-xl">Hello World!</h1> <p>We're also able to serve images, however they'll be compiled into the binary.</p> </div> </div> </main> {% endblock %} ``` Thanks for reading ✌️