HTTP Server

Astra offers HTTP1/2 web server through the axum project. On the Lua's side, the server holds configuration and route details, which are then sent to the Rust for running them. Since it is running on Tokio, it can take advantage of all available resources automatically, making it easy for vertical scaling. Throughout this documentation, the word server is used to describe an HTTP web server table on the Lua's side. You can create one as such:

-- create a new server with
local server = Astra.http.server.new()

-- run the server with
server:run()

Configuration

Astra can be configured in a few ways for runtime. As of now there is no native TLS/SSL support and needs a reverse proxy such as Caddy to handle that. Check Deployment for more information.

However every configuration option will be available at the server instead. For example, changing the compression, port and hostname is as such:

-- configure the server with
server.compression = false
server.port = 8000
server.hostname = "0.0.0.0"

You can also configure other languages that compiles to Lua such as Fennel. Astra's api is for pure Lua however, so it will be up to you to make type definitions and make sure it can call the right functions and tables.

Routes

The server holds all of the route details. The routes are loaded at the start of the runtime and cannot be dynamically modified later on. There are also methods within the server that makes it easy to add new routes. For example:

-- A simple GET index route with text return
server:get("/", function()
    return "hello from default Astra instance! " .. Astra.version
end)

The syntax are as follows:

server:ROUTE_TYPE(ROUTE_PATH, CALLBACK);

-- Where callback is:
function(request?, response?);

The following route types are supported as of now:

  • GET
  • POST
  • PUT
  • PATCH
  • PARSE
  • DELETE
  • OPTIONS
  • TRACE

All lowercase and snake_case when calling with astra of course. There are two additional ones available:

  • STATIC_DIR
  • STATIC_FILE

Which does as expected, serves a file or directory over a route.

Route Logic

Each route function needs a callback which contains a route's logic. This callback function optionally can have two arguments: request and response respectively, and may optionally have a return.

Interally requests and responses are each a struct in Rust initialized but not parsed/deserialized beforehand. This is to save performance overhead of serialization. However its content and be modified or accessed through their methods. We will discuss them later on.

Return types of the callback can optionally be either empty, string, or a table. The table responses are parsed in Rust and serialized to JSON, and then returned. Empty responses does not include any content. Responses, or lack of them, are by default sent with status code of 200.

Requests

Requests are provided as the first argument of the route callbacks as a table (not deseralized). Each request in the route callbacks can be accessed through its methods. The following methods are available:

  • body: Body
  • headers: table<string, string>
  • uri: string
  • queries: table<any, any>
  • method: string
  • multipart: Multipart

where Body has:

  • text: string
  • json: table

and where Multipart has:

  • save_file(file_path: string | nil)

Example:

server:get("/", function(req)
    -- access the headers
    pretty_print(req:headers())

    -- print the body as text
    print(req:body():text())
end)

Responses

Responses are the second argument provided in the route callback. They allow you to modify the response to the way you want. Each response has the default 200 OK status along content header based on your response. The following methods are available:

  • set_status_code(status_code: number)
  • set_header(key: string, value: string)
  • remove_header(key: string)
  • get_headers(): table<string, string>

Example:

server:get("/", function(req, res)
    -- set header code
    res:set_status_code(300)
    -- set headers
    res:set_header("header-key", "header-value")

    return "Responding with Code 300 cuz why not"
end)

The headers, as stated, will include content type when sending to user, but can be changed while setting the type yourself.

Cookies

Cookies allow you to store data on each HTTP request, if supported. Astra does not currently support signed and private cookies. You can create a new cookie by getting it from a request:

server:get("/", function(request)
    local cookie = request:new_cookie("key", "value")

    return "HEY"
end)

You can also get a previously set cookie:

local cookie = request:get_cookie("key")

After modification or creation, they will have no effect unless you set them to the response

response:set_cookie("key", cookie)

And similary, remove them with

response:remove_cookie("key")

Each cookie contains extra details and functions which are as follows:

set_name(cookie: Cookie, name: string)
set_value(cookie: Cookie, value: string)
set_domain(cookie: Cookie, domain: string)
set_path(cookie: Cookie, path: string)
set_expiration(cookie: Cookie, expiration: number)
set_http_only(cookie: Cookie, http_only: boolean)
set_max_age(cookie: Cookie, max_age: number)
set_permanent(cookie: Cookie)
get_name(cookie: Cookie): string?
get_value(cookie: Cookie): string?
get_domain(cookie: Cookie): string?
get_path(cookie: Cookie): string?
get_expiration(cookie: Cookie): number?
get_http_only(cookie: Cookie): boolean?
get_max_age(cookie: Cookie): number?

Deployment

You can follow the steps covered in Configuration to setup the Astra itself.

Astra does not support TLS/SSL as of yet, but may support by the 1.0 release. However generally a reverse proxy service is recommended for deployment. We recommend Caddy as it is easy to setup and use, especially for majority of our, and hopefully your, usecases. What caddy also does is automatically fetching TLS certificates for your domain as well which is always a good idea. You can install caddy through your system's package manager.

Then open a new file with the name Caddyfile with the following content:

your_domain.tld {
    encode zstd gzip
    reverse_proxy :8080
}

and change your_domain.tld to your domain, and :8080 to the port you have set for your server. After this, make sure your 443 and 80 ports are open through your firewall. For a linux server running ufw you can open them by:

sudo ufw allow 80
sudo ufw allow 443

And finally run the caddy:

caddy run

Make sure your server is running before that. That is pretty much it for the basic deployment.

Fault Tolerance

Astra ensures fault tolerance through several methods internally and offers guidence on how you can ensure it on the Lua's endpoint as well.

Fault-tolerance essentially describes the ability to tolerate crashing errors and continue execution whereas otherwise caused the server to shut down. In Astra's internal case, this is ensured by removing all of the crashing points and handle every error that could occur during runtime. This was achieved through denying unwraps and expects throughout the codebase for the runtime. However there are still crashes on startup for cases that needs to be looked into, such as Lua parsing and errors, bad path, and/or system issues such as port being unavailable or unauthorized for Astra.

In Lua however, the errors are usually crash by default, which are still tolerated with Astra and does not shutdown the server. To handle the errors as values, where it allows you to ensure the server does not crash and the issues are handled, you can use features such as the pcall. This is always recommended over any other method. For Astra's case, there are usually chained calls that each can faily on their own as well, hence wrapping them in lambda functions or individually pcall wrapping them always is a good idea.