Introduction

Versions

This documentation is on the latest version of Astra and will store a version for every x.0.0 release. The other versions will be listed below here when available.

About

ArkForge Astra is an easy-to-use, fault-tolerant, extendible, and performant web server targeting LuaJIT (Lua 5.1) and built upon Rust + Axum. Astra takes advantages of the Rust's performance, correctness and memory safety offering of the Tokio async runtime, upon which Axum also runs, to write fault-tolerant and no-build servers with ease.

Currently Astra is used within the ArkForge for some internal and external products. However being very young and early project, it lacks battle testing. However it does not mean you cannot build software with it, as the base, axum, is already mature and Astra is a thin wrapper over it.

Philosophy

The goal is to have the cake and eat it too. Obtaining the low-level advantages of Rust while having the iteration, ease and development speed of Lua. This way you can both have a small runtime and iterate over your products and servers with a very simple CI setup that ships in seconds, or even direct SSH. This is also called no-build, as there is no building and packaging stage required.

In an ideal world, Astra will be able to handle majority of the common use cases, which are basic REST servers that uses a PostgreSQL or SQLite DB if needed, single server instance, and manage hundreds of thousands of users per second upon it.

Astra's development style is to be as minimalist and simple as we can afford. Simplicity means decreasing as many steps as possible between the core developers and someone completely new to the project being able to pick it up and start changing it to their needs. However we do add complexity when it is required as well. Keeping the minimalistic development style also means we use minimal number of tools, and if we do use a tool, it should not be too foreign from the source. The result of this is an output of a single binary, a single lua prelude that includes batteries along with it; still having all of the goodies that the Lua language provides.

Getting Started

Dev environment

For development, we recommend a linux machine with visual studio code (or alternative) along with the lua extension.

After your setup is complete, you will want to obtain a prebuilt binary of the runtime from the releases page. Alternatively you can get it with wget as well. For example for version 0.6.0:

wget https://github.com/ArkForgeLabs/Astra/releases/download/v0.6.0/astra-linux-amd64

There are also windows binaries available if you are working on Windows, however we mostly assume your server instance will likely run linux, hence more support is geared towards it.

After getting your binary on linux, you'll want to change permissions to make it executable:

chmod +x astra-linux-amd64

Each release likely contains updates to the packaged bundle lua code that contains definitions you might need during development which you can omit and ignore during production release. You can obtain them through:

./astra-linux-amd64 export-bundle

which will create a new file called astra_bundle.lua. You can include it in your code to get intellisense and references for the available functions. The import will be ignored during runtime as Astra will use it's own packaged bundle internally instead.

The reason for this is because Astra includes many global functions that includes the server itself along utilities. These are loaded at start of a runtime. These functions are not written in lua, and are written in Rust and intended to be used by the runtime binary. Hence changing the bundled lua code does not affect anything. There are some pure lua utilities for example JSON parser, table validation, pretty printing, ... which are also included in the bundle by default, but you can ignore them if you wish.

From here on, you can begin writing your server code in a fresh lua file and enjoying everything Astra has to offer. Everything you do locally can be replicated exactly in the server environment as well since they both use the same binary and there are no build stages present.

Interal dev environment

If you want to extend or modify Astra, you will need Rust to be installed on the latest version. You will also need to make sure Cargo, clippy and rust-analyzer components are also installed. These components often are packaged alongside the basic installation. Your IDE may depend on whichever you are comfortable with as Rust have amazing support everywhere.

Then you'll need to clone the repository:

git clone https://github.com/ArkForgeLabs/Astra

You may also want to install LuaJIT as well for some tasks as well such as packing the lua bundle for runtime binary. If you wish to write/extend the docs, you'll need mdbook.

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.

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

Astra.compression = false
Astra.port = 8000
Astra.hostname = "0.0.0.0"

There are some more configuration that can only happen upon building the runtime's binary. For example content compression and PostgreSQL driver (compression and sqlx flags respectively). These can be enabled or disabled by feature flags during build. For example to build without sqlx but with compression:

cargo build --release --no-default-features --features compression

The flags are comma separated: feature1,feature2.

In the future, there may be binaries with different combinations prebuilt as well!

Routes

The Astra global table 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 Astra table that makes it easy to add new routes. For example:

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

The syntax are as follows:

Astra.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
  • method: string
  • multipart: Multipart

where Body has:

  • text: string
  • json: table

and where Multipart has:

  • save_file(file_path: string)

Example:

Astra.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:

Astra.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.

Extending Astra

Through Utils

Your point of interest will be the src directory. If your aim is to extend the engine, you may look into the utils folder upon which you can add a new file of your choice.

For extensions, you need a struct that implements LuaUtils trait implemented which gives an async lua runtime context. Within it you can create a new lua function that adds your function to the global context at runtime.