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 ✌️