hello-http/src/main.nika

// A small web server that answers a greeting, a header and a POST.
//
//     cd examples/hello-http
//     nikaia run
//     http: listening on 127.0.0.1:8080
//
// Then, from another terminal:
//
//     curl 'localhost:8080/?name=Ada'    Hello, Ada
//     curl localhost:8080/who             you asked localhost:8080
//     curl -d hello localhost:8080/       you sent 5 bytes
//     curl localhost:8080/source          this file, which the program never reads
//     curl localhost:8080/refreshed       how often the refresher ran
//     curl localhost:8080/crash           the refresher crashes, and is restarted
//     curl localhost:8080/crashes         how often it did
//     curl localhost:8080/elsewhere       not found
//
// Shows: a package reached by a path (`../http`, named in `nikaia.toml`), a
// function passed as the last argument after the call, `??` for a value that
// may be missing, and a supervisor (Part II 12.8): the server and a refresher
// run side by side, and the refresher is started again when it crashes while
// the server goes on answering.

use http
use std::cli
use std::fs
use std::supervisor
use std::time

fn main() throws {
    // An address may be given as an argument. Port `0` picks any free port,
    // and `listen` prints the one it got. The server's child takes it with it:
    // every restart is lent the same text, and nothing copies it.
    let at = cli::args().nth(1) ?? "127.0.0.1:8080"

    // What the two children share: how often the refresher ran, and how often
    // it was asked to crash and did. A `SharedMut` is changed only through its
    // methods.
    let refreshed = SharedMut(0)
    let asked = SharedMut(0)
    let crashed = SharedMut(0)

    // Both children are restarted whenever they end; `OneForOne` restarts only
    // the one that did.
    supervisor::run([
        supervisor::child(fn { serve(at, refreshed, asked, crashed) }; restart: supervisor::Kind::Permanent),
        supervisor::child(fn { refresh(refreshed, asked, crashed) }; restart: supervisor::Kind::Permanent),
    ]; strategy: supervisor::Strategy::OneForOne)
}

// A cache refresher, standing for any work a server does beside answering:
// it counts its rounds, and crashes when it is asked to.
fn refresh(refreshed: SharedMut[i64], asked: SharedMut[i64], crashed: SharedMut[i64]) {
    while true {
        time::sleep(20.millis())
        if asked.get() > crashed.get() {
            crashed.update fn(mut v) { v += 1 }
            panic("the refresher was asked to crash")
        }
        refreshed.update fn(mut v) { v += 1 }
    }
}

fn serve(at: String, refreshed: SharedMut[i64], asked: SharedMut[i64], crashed: SharedMut[i64]) throws {
    // One function answers every request and decides by method and path.
    // Nothing is served that this function does not name.
    http::listen(at) fn(request) {
        let path = request.path()
        if request.method() == http::Method::Post {
            let size = request.body().len()
            return http::Response::text(f"you sent {size} bytes")
        }
        // A missing value is not an empty string: `?name=` gives "" and no
        // `name` at all gives nothing. `??` supplies a value for the second
        // case only.
        if path == "/" {
            let who = request.query("name") ?? "world"
            return http::Response::text(f"Hello, {who}")
        }
        if path == "/who" {
            let host = request.header("host") ?? "nobody"
            return http::Response::text(f"you asked {host}")
        }
        if path == "/refreshed" {
            let rounds = refreshed.get()
            return http::Response::text(f"{rounds}")
        }
        if path == "/crash" {
            asked.update fn(mut v) { v += 1 }
            return http::Response::text("the refresher will crash")
        }
        if path == "/crashes" {
            let times = crashed.get()
            return http::Response::text(f"{times}")
        }
        // A file the program never reads (ADR-289 D23): looked at when the
        // answer is made, read only when it is sent. Its name is resolved
        // under the directory the server runs in, and may not leave it.
        if path == "/source" {
            return http::Response::file(http::File("src/main.nika", fs::Root::Dir(".")))
        }
        if path == "/missing" {
            return http::Response::file(http::File("no-such-file.txt", fs::Root::Dir(".")))
        }
        if path == "/outside" {
            return http::Response::file(http::File("../hello-http/src/main.nika", fs::Root::Dir("src")))
        }
        return http::Response::not_found()
    }
}

The file itself · all the examples

This site is open source. Improve this page.