// 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