v hyper.dev · Petit Cloud - 2 - Kernel

We jumped into an adventure to build the simplest way possible to deploy a Scheme web application. In the previous article, we identified two procedures, petit-cloud-http-connect, and the kernel procedure make-petit-cloud-application. We will dive into the latter.

Any resemblance to competitor technology is wholly, and fully coincidental.

Continuation

We want to deploy http web application built with a library (hello) with a procedure main:

;; hello.scm
(library (hello)
  (export main)
  (import (petit-cloud application))

  (define main
    (lambda (method path headers body)
      (values 200
              (list (cons 'content-type "text/plain"))
              (string->utf8 "Hello schemer!\n")))))

Given an application server running at DOMAIN:PORT, it must be possible to deploy it with the following shell invokation:

#;sh> petit-cloud deploy DOMAIN:PORT hello.scm

Short of logging, monitoring devices, and error handling, the application server code is:

(import (petit-cloud))


(define petit-cloud-deploy?
  (lambda (method path)
    ...))

(define client-accept
  (lambda (read write close)
    (define-values (method url headers body) (read))
    (if (petit-cloud-deploy? method path)
        (let ((application (make-petit-cloud-application body)))
          (petit-cloud-current-application application)
          (values 201 (list) (bytevector)))
        (let ((application (petit-cloud-current-application)))
          (application method url headers body)))))
           
(petit-cloud-http-connect DOMAIN PORT client-accept)

The procedure make-petit-cloud-application takes a bytevector as argument that is read into a symbolic expression that is the same as the library (hello):

;; hello.scm
(library (hello)
  (export main)
  (import (petit-cloud application))

  (define main
    (lambda (method path headers body)
      (values 200
              (list (cons 'content-type "text/plain"))
              (string->utf8 "Hello schemer!\n")))))

We want to translate the library (hello) into executable code, in other words convert data into code.

Turning data, into a program can be done with an interpreter.

In the case of Scheme, is to use the readily available eval.1

Even if they are ways to make it safer, or safe, the procedure eval is unsafe, it can have side effects, and other effects that will have significant security consequences: do not expose such an application server in open Internet without the careful review of an expert.

As of 2023, R7RS specify eval as follow:

(eval expr-or-definition environment-specifier)

The form library is neither an expression, or definition it is in most Scheme implementation a meta-syntaxic device hardcoded in the macro expander, the same component responsible for interpreting macros.

We could rename, and write (hello) on disk, but there is a more straightforward way, a transformation relying on define, set!, and let. The library (hello) can be rewritten into an expression that can be handed to eval to return the procedure main.

The transformation will yield the following expression called module:

(begin
  (define main #f)

  (let ()
    
    (set! main
          (lambda (method path headers body)
            (values 200
                    (list (cons 'content-type "text/plain"))
                    (string->utf8 "Hello schemer!\n")))))

  main)

Then, the following snippet, will return main:

(eval module '(petit-cloud application))

That is enough to known to implement a simple make-petit-cloud-application.

Backtracking

We described how to transform an library definition, into an expression that can be fed into eval, that will return the procedure that will eventually reply to end-users.

That is not a complete implementation, more code are necessary to make this outline fit for production. If you want to use such an application server, and self-host it, the code is within reach.


  1. The procedure eval can be implemented as an interpreter, or compiler↩︎