¶The Viriatum Web Server is simple and lightweight web server aimed at providing a simple yet powerful solution for static file serving, reverse proxy serving and dynamic language handling.
The current implementation support both x86/x64 and ARM architectures.
Building viriatum using automium is quite simple even for complex builds like cross compilation. Just use the appropriate 'build.json' located under 'scripts/build' and under such directory execute:
atm
For production purposes one should use the proper optimization flags:
atm --cflags=-O3
For cross compilation (eg: arm-rasp-linux) use the following command:
atm --cflags=-O3 --cross=arm-rasp-linux-gnueabi
If you want to know more about cross compilation please refer to the Cross Compilation document.
CMake is the recommended build system for local development and IDE integration.
cmake -B build cmake --build build
The binary is placed under build/bin/viriatum.
For a debug build (enables debug logging with file/line info and V_DEBUG output):
cmake -DCMAKE_BUILD_TYPE=Debug -B build cmake --build build
Debug logging can also be enabled independently of the optimization level:
cmake -D VIRIATUM_DEBUG=ON -B build cmake --build build
For a release build with debug symbols (-O2 -g):
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build cmake --build build
To set a custom www root path (equivalent to --with-wwwroot in Autoconf):
cmake -D VIRIATUM_WWW_ROOT=/var/viriatum/www -B build cmake --build build
To leave HTTP/2 out of the build (equivalent to --disable-http2 in Autoconf):
cmake -D VIRIATUM_HTTP2=OFF -B build cmake --build build
If the project has Conan-managed dependencies, install them first:
conan install . --output-folder=build --build=missing -s build_type=Debug cmake -B build -DCMAKE_BUILD_TYPE=Debug --toolchain build/conan_toolchain.cmake cmake --build build
If you're going to build viriatum using the git repository you first need to generate the configure files using autoconf.
./autogen.sh
For building under unix simple instructions are used:
./configure make && make install
In order to provide a correct (system wide) configuration path use:
./configure --sysconfdir=/etc
In order to get the most performance out of viriatum set the CFLAGS variable for optimization:
CFLAGS="-O3" ./configure
apt-get libc6-dev-i386 export CC="cc -m32" ./configure make && make install
Building viriatum for android involves cross compilation and the android ndk toolkit must be used.
Check the instructions for downloading the android ndk from the official website, then unpack the android-ndk package file into the current directory.
The recommended NDK version is r8e as compatibility is ensured for that version. NDK is currently known to have problems compiling some of the packages (including PHP, Python and Lua). To download that version of the NDK for Linux use this link.
Because of problems building the static versions of PHP, Python, Lua, etc. some of the modules are currently not compatible with Android.
The r8d or preivous versions of the NDK are meant to be run only under x86 machines and should be used with care unnder x86-64 based machines.
tar -xvf android-ndk-r[x]-linux-[arch].tar.bz2
Create the standalone toolchain using the script for that purpose.
android-ndk-r[x]/build/tools/make-standalone-toolchain.sh --system=linux-x86_64\
--platform=android-4 --install-dir=/opt/android-toolchainAnd then you may use the "just" created toolchain to build Viriatum with:
export PATH=/opt/android-toolchain/bin:$PATH
export CFLAGS="-L/opt/android-toolchain/lib -I/opt/android-toolchain/include\
-L/opt/android-root/lib -I/opt/android-root/include"
./configure --host=arm-linux-androideabi --build=arm --prefix=/opt/android-root
make && make installFor building using the default mingw32 toolchain.
apt-get install mingw32 mingw32-binutils ./configure --host=i586-mingw32msvc --build=i686-linux --prefix=/opt/i586-mingw32 make && make install
For building using the mingw-w64 toolchain for 64 and 32 bit options.
apt-get install mingw-w64 gcc-mingw-w64 binutils-mingw-w64 ./configure --host=x86_64-w64-mingw32 --build=i686-linux --prefix=/opt/x86_64-w64-mingw32 ./configure --host=i686-w64-mingw32 --build=i686-linux --prefix=/opt/i686-w64-mingw32 make && make install
WINVER <= 0x0500 (Windows 2000 or older) disable IPv6 support (#undef VIRIATUM_IP6)FreeBSD ignores the /usr/local directory by default so it must be included in order to conform with dependencies.
setenv CFLAGS "-L/usr/local/lib -I/usr/local/include"
There are a lot of possible building features to enable
--with-moduleroot=path - Sets the path to be used to load the modules --with-wwwroot=path - Sets the path to be used to install and serve the default content --enable-debug - Enables the extra debugging capabilities in Viriatum --enable-defaults - Enables the default paths in the viriatum server, ignoring wwwroot and moduleroot at runtime --disable-ipv6 - Disables the support for the IPv6 protocol stack --disable-epoll - Disables the support for the epoll mechanism --enable-mpool- Enables the memory pool support (optimized for windows only) --enable-prefork - Enables the prefork support so that viriatum can create workers --disable-http2 - Disables the support for HTTP/2, leaving only the previous version of the protocolViriatum serves HTTP/2 alongside the previous version of the protocol, on the very same port, and decides between the two from the bytes that open a connection.
A connection that opens with the preface of the protocol is handed to a session of it, which is the cleartext form that a client asks for with curl --http2-prior-knowledge. A connection that opens with anything else carries a message of HTTP/1.1 and is parsed as one. The detection happens once per connection and is turned off through the http2 entry of the general section:
[general] http2 = On
The very same setting is turned off for a single run with --no-http2.
Over the transport the version is negotiated rather than guessed. The server announces h2 and http/1.1 through ALPN and honours the order the client announces, so a client that prefers the older version is served it. The transport is off in the configuration that ships, so the two commands that reach it need it turned on first, either through --ssl for a single run or through the general section:
[general] ssl = On ssl_csr = cert/server.crt ssl_key = cert/server.key
curl --http2-prior-knowledge http://localhost:9090/ curl -k --http2 https://localhost:9090/ openssl s_client -connect localhost:9090 -alpn h2
The negotiation needs a library of the transport that carries ALPN, which is OpenSSL 1.0.2 onwards, and the floor of the handshake is raised to TLS 1.2 and to the cipher suites that the protocol accepts, so a connection is never negotiated into one it would have to be torn down for.
The resources that travel together with a request are listed in the location that serves it, each one of them promised on a stream of its own:
[location:/] handler = file push = /static/style.css /static/main.js
Every handler of the tree writes its response through the operations that the connection carries, so the file, the default, the dispatch and the proxy handlers, the pages of the errors and both of the Python interfaces are served by either version of the protocol without knowing which one is in use.
Viriatum may be imported from the Python interpreter and used to serve either a WSGI or an ASGI application on its own event loop, no separate server process is required:
pip install .
A WSGI application is served by passing it to the server, the interface is detected from the shape of the application:
import viriatum
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello World"]
viriatum.serve(application, port=8080)
An ASGI application is served in exactly the same way, a coroutine function is recognised as such and driven on the asyncio loop that the server advances once per iteration of its own:
import viriatum
async def application(scope, receive, send):
await receive()
await send(
{
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain")],
}
)
await send({"type": "http.response.body", "body": b"Hello World"})
viriatum.serve(application, port=8080)
The http, lifespan and websocket scopes are all supported, response bodies are streamed as each chunk is sent.
Both versions of the ASGI interface are supported. The single callable shape above is the third version, while the second one hands the scope to an outer callable and the pair of callables to the awaitable it returns:
import viriatum
def application(scope):
async def handle(receive, send):
await receive()
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"Hello World"})return handle
viriatum.serve(application, port=8080, interface="asgi")
The version is detected from the shape of the application, honouring the _asgi_single_callable and _asgi_double_callable markers when present. The interface may also be forced with interface set to wsgi, asgi2 or asgi3. Note that auto only tells a single callable ASGI application apart from a WSGI one, as a double callable application is indistinguishable from a WSGI callable, so a second version application needs asgi, asgi2 or one of the markers.
There are a series of modules for the viriatum server that are used to extend functionality of the base server, in order to compile then some rules apply. Current modules include:
mod_lua - For interaction with the Lua interpreter mod_php - For interaction with the PHP interpreter (complex compilation) mod_python - For interaction with the Python interpreter using either the WSGI or the ASGI specification, the second one requiring an interpreter that carries the event loop it rests on.For an Ubuntu/Debian environment the Lua 5.1 development packages must be included using:
apt-get install liblua5.1-0-dev
On Alpine Linux:
apk add lua5.1-dev
Requires PHP 8.x compiled with the embed SAPI. On Alpine Linux the simplest approach is to use the system packages:
apk add php84-dev php84-embed
When building PHP from source, compile with embed SAPI support (the library is now named libphp.so instead of the old libphp5.so):
./configure --enable-embed
For static linking, useful for package distribution:
export CFLAGS="-fpic"
./configure --enable-embed=static --disable-libxml --disable-dom --disable-simplexml\
--disable-xml --disable-xmlreader --disable-xmlwriter --without-pear --without-iconvThe PHP module must then be compiled with CFLAGS pointing to the PHP include directories. The easiest way is to use php-config:
export CFLAGS="$(php-config --includes)"
Or manually:
export CFLAGS="-I/usr/local/include/php -I/usr/local/include/php/main\
-I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend"Additional information about PHP compilation flags may be found here.
Serves an application of either of the two interfaces, the older one through the wsgi handler and the more recent through the asgi one, which is only built when the interpreter it is compiled against carries the event loop that interface rests on.
Requires Python 3 development headers and shared library. On Ubuntu/Debian:
apt-get install python3-dev
On Alpine Linux:
apk add python3-dev
On macOS (Homebrew):
brew install python3
The module is compiled with flags from python3-config:
export CFLAGS="$(python3-config --includes)" export LDFLAGS="$(python3-config --ldflags --embed)"
It's possible to run viriatum inside a Docker container. Three Dockerfiles are provided:
Dockerfile - Core server only (~11 MB) Dockerfile.php - Core + mod_php with PHP 8.4 (~22 MB) Dockerfile.all - All modules: diag, gif, lua, php, python (~112 MB)To build and run:
docker build -t viriatum . docker run -p 9090:9090 viriatum
To build the full image with all modules:
docker build -f Dockerfile.all -t viriatum-all . docker run -p 9090:9090 viriatum-all
Viriatum is measured against the reference servers by a harness of its own, which starts the server and the references on the same machine, drives them through the same workloads and reports them side by side:
./scripts/benchmark.sh
A single workload may be selected, and every setting of a run is overridable:
ONLY=static-small-alive ./scripts/benchmark.sh DURATION=10 REPEATS=5 CONNECTIONS=128 ./scripts/benchmark.sh
The reports land under benchmark/, with the table in benchmark/summary.md. wrk is required; nginx, Caddy, HAProxy, gunicorn and uvicorn are each used when they are available and skipped with a note when they are not.
Absolute numbers are not the point, and a figure taken on one machine says nothing about another. The number to read is the ratio of Viriatum against a reference measured in the same run, which is the only comparison that survives a noisy machine. The accepted numbers live in scripts/benchmark/baseline.json and every run is compared against them.
The methodology, the configuration given to each server and the reasoning behind each of those choices are written down in scripts/benchmark/README.md. A scheduled workflow runs the harness and reports it; it never fails a build on a performance figure.
#include <vld.h> statement valgrind --tool=memcheck --leak-check=full viriatumThe current version numbering in viriatum follows the following wildcard based structure ${MAJ}.${MIN}.${MIC}${STA}${STA_VER} and in order to change any of these values the definitions.h and the build.json files must be changed.
In case any one of the version numbers or the stage value changes a new tag must be created in the git repository, so that the version is correctly identified for the repository contributors.
Viriatum is currently licensed under the Apache License, Version 2.0.