Flack the Planet

The web framework no one asked for.

mjones (@numinit)

What is this?

A history of web frameworks

Network Working Group                                        D. Robinson
Request for Comments: 3875                                       K. Coar
Category: Informational                   The Apache Software Foundation
                                                            October 2004


             The Common Gateway Interface (CGI) Version 1.1

So, how did it work?

Problems

FastCGI and reverse app proxies

How this works is pretty simple

This got more popular in the late 2000s and 2010s

The rise of the webapp

"Everyone (dis)liked that"

The rise of the webapp

"Everyone (dis)liked that"

What would a persistent Nix evaluator look like?

Enter Flack

Because naming your webapp framework after a singer is what the cool kids do

[Roberta Flack] produced the single and her 1975 album of the same name under the pseudonym "Rubina Flake"

What does it look like?

I hope you like HTNL and the module system

{ lib, flack, inputs, ... }:

let
  # ...

  mkSlide = body:
    h "html" [
      (h "head" [
        (h "title" "Flack the Planet!")
      ])
      (h "body" body)
    ];

  presentation = mkSlidesHtml {
    index = mkSlide [
      (h "article" [
        ...
      ])
    ];
  };

  servePresentation = req:
    "${presentation req}/www/${joinPathToIndex (req.params.path or ["/"])}";
in
{
  route = {
    GET."/" = req: req.res 200 { } (servePresentation req);
    GET."/talk/:...path" = req: req.res 200 { } (servePresentation req);
  };
}

Middlewares, mountpoints, and routes

Rejected name: Nixpress

use = {
  /*
    This is a middleware.
    If X-Auth-Token isn't "supersecret" then it'll return a 401 for all paths under /foo.
    Obviously there is a timing sidechannel here, don't actually do this.
  */
  "/foo" =
    req: if req.get "X-Auth-Token" != "supersecret" then req.res 401 { } "Unauthorized" else req;
};
route = {
  /*
    This is a route.
    bar is available in req.params.
    Note the auth token above!
    `curl -H 'X-Auth-Token: supersecret' http://localhost:2019/foo/myBar`
  */
  GET."/foo/:...bar" =
    req:
    req.res 200 { "X-My-Header" = "value"; } {
      inherit (req) pathComponents;
      inherit (req.params) bar;
    };
};

Using the nixops4 Rust bindings

https://github.com/nixops4/nix-bindings-rust

Multithreading

Just do this:

nix_bindings_expr::eval_state::gc_register_my_thread()

Cloning values using the Rust bindings atomically modifies their refcount

/// Evals a string and then calls the result.
fn call_fn(func: &str, st: &mut EvalState, value: &Value, dir: &str) -> std::io::Result<Value> {
    let func_val = st
        .eval_from_string(func, dir)
        .map_err(std::io::Error::other)?;
    st.call(func_val, value.clone()) // not an actual copy!
        .map_err(std::io::Error::other)
}

Has to be a catch, right?

/// These need to be added if you want to use EvalState and Value in multiple threads
unsafe impl Send for EvalState { }
unsafe impl Send for Value { }

/// Gets a new EvalState for the specified store.
fn init_get_state(
    store: Store,
) -> std::io::Result<(EvalState, ThreadRegistrationGuard)> {
    let gc_guard = init_get_gc_guard()?;

    let mut state_builder = nix_bindings_expr::eval_state::EvalStateBuilder::new(store)
        .map_err(std::io::Error::other)?;

    let state = state_builder.build().map_err(std::io::Error::other)?;

    Ok((state, gc_guard))
  }

What about the store?

So, we need Determinate Nix... what else can we turn on by default?

How you can make your own Nix frontend

Can we use it to write things where we'd ordinarily use nix repl?

Hmmmm... 🤔