(robusta)

warning: this library is a huge work in progress.

main TODOs:


serving stuff: (robusta server)

this library implements a basic http server

Exported values:

l10n: (robusta l10n)

l10nizer should be started before calls to make-l10n

as threads don't survive between compile & runtime (when compiling to binaries), l10nizer can dump its map as a thunk to later recover data at runtime

for example:

;; -*- mode: owl; compile-command: "ol -x c -o - temp.scm | tcc -run -" -*-
(import (robusta l10n))

(start-l10nizer)

(make-l10n (EN)
  main.greet   => "Hello, World!"
  main.farwell => "Goodbye, World!")

(make-l10n (RU)
  main.greet   => "Привет, мир!"
  main.farwell => "Прощай, мир!")

(print-to stderr "  compile-time")

(print-to stderr (l10n-get 'EN 'main.greet))
(print-to stderr (l10n-get 'RU 'main.greet))

(define restart! (interact 'l10n (tuple 'dump)))

(λ (_)
  (print-to stderr "  run-time")
  (restart!)
  (print-to stderr (l10n-get 'EN 'main.farwell))
  (print-to stderr (l10n-get 'RU 'main.farwell))
  0)

Exported values:

fastcgi server: (robusta fastcgi)

safer and faster way to host robusta web applications.

Example configurations, assuming web app running at 127.0.0.1:8080:

<VirtualHost *:80>
  DocumentRoot /var/www/html

  ProxyPassMatch ^/(.*)$ fcgi://127.0.0.1:8080/

  <Directory />
    Require all granted
  </Directory>
</VirtualHost>
server "robusta.local" {
  listen on 0.0.0.0 port 80
  log style forwarded
  location "*" {
    fastcgi socket tcp 127.0.0.1 8080
  }
}

Exported values:

creating dispatchers: (robusta dispatcher)

Exported values:

{encoding,decoding} json: (robusta encoding json)

this loosely implements the ECMA-404 standard for json encoding and decoding

it can also encode json, where objects are defined like this: '((a . b) (c . d)), so a list with key-value pairs.

https://ecma-international.org/publications-and-standards/standards/ecma-404/

Exported values:

b64 encoding, decoding: (robusta encoding base64)

Exported values:

Examples:

urlencode, urldecode: (robusta encoding url)

Exported values:

s-expressions → html: (robusta encoding html)

this library was originally written for chai but development will continue here.

Exported values:

MIME types: (robusta mime)

Exported values:

http stuff: (robusta http)

you probably shouldn't use this library by itself. it's nicely packaged inside of (robusta dispatcher)

Exported values:

common functions: (robusta common)

Exported values:

this rocks actually: (robusta experimental db)

Exported values:

middleware defining loggers compatible with api used by bind and fastcgi-bind: (robusta log)

Exported values:

./robusta/full.scm: (robusta full)

No documentation provided.

Exported values:

examples

simple hello server without dispatchers

(import (robusta server))

(bind
  8080
  (λ (r)
    (respond
     r (response
        code    => 200
        content => "hello!"
        headers => '((Content-type . "text/html"))))))

simple server with dispatchers

(import
 (robusta server)
 (robusta dispatcher))

(define (index request)
  (response
   code    => 200
   headers => '((Content-type . "text/html"))
   content => "this is the index page"))

(define (about request)
  (response
   code => 200
   headers => '((Content-type . "text/html"))
   content => "this is an about page"))

(define dispatcher (make-dispatcher
                    "/"      => index
                    "/about" => about))

(bind 8080 dispatcher)

streamed response from a route

(import
 (robusta full))

(define app
  (make-dispatcher
   "/" => (λ (req)
            (response
             content => (λ (stream)
                          (let loop ((n 10))
                            (if (= n 0)
                                #t
                                (begin
                                  (stream '(#\a 10))
                                  (sleep 500)
                                  (loop (- n 1))))))))))

(bind 8080 app)

http basic auth

(import
 (robusta server)
 (robusta dispatcher))

(define app
  (make-dispatcher
   "/" => (λ (req)
            (with-http-basic-auth (req "login" "password")
              (response
               code    => 200
               headers => '((Content-type . "text/html"))
               content => "ok")))))

(bind 8080 app)

using (robusta encoding json)

(import (prefix (robusta encoding json) json/))

(define json-string "[1, 2, [3], [[4]], [[[5]]], [[[[6]]]], 7]")
(define json-structure '((a . "10") (b . #f)))
(print (json/decode json-string)) ; → (1 2 (3) ((4)) (((5))) ((((6)))) 7)
(print (json/encode json-structure)) ; → {"a":"10","b":false}

using (robusta encoding html)

(import
 (prefix (robusta server) robusta/)
 (prefix (robusta dispatcher) robusta/)
 (prefix (robusta encoding html) html/))

(define (idx request)
  (robusta/response
   code    => 200
   headers => '((Content-type . "text/html"))
   content => (html/encode '(html (head) (body (p "this the only page"))))))

(robusta/bind 8080 (robusta/make-dispatcher "m/^.*$/" => idx))

using (robusta encoding html) at scale

If you have a bigger document, you should probably "stream" it instead of constructing a big string with the final html code. This will be faster, as constructing strings like it is done in html/encode is a both memory and cpu heavy operation in owl.

(import
 (robusta full))

(define html
  `(html
    (head ((link (rel . "stylesheet") (href . "style.css"))))
    (body
     ,@(map
        (λ (i) `((p (class . "klass")) ,(str "test" i)))
        (iota 0 1 1000)))))

(define I* (λ (_) I*))

(print ";; Defined html")

(print ";; html/encode ->string")
,time (html/encode html)

(print ";; html/encode/printer")
,time (html/encode/printer html I*)
;;                              ^
;; this function is the "streaming" function
;; it is of a form (λ (s) whatever), where s is either
;;   * a string of "something" that is the continuation of a html document, or
;;   * eof-object
;; it MUST return either itself, or another function. its return value will be used in every consequent call
;; as the function getting called, so it's possible to have user data flowing

;;; This is also an example of what can be used as (λ (s) whatever) in encode/printer context (as a printer)
(define (displayer-to fd)
  (letrec ((f (λ (s)
                (display-to fd s)
                f))) ; it returns itself
    f))

(print ";; html/encode/printer w/ 2kb streamer")

,time (html/encode/printer html (html/make-streamer I (<< 1 11)))
;;                                                  ^- this is ANY λs.whatever, it does not need to return anything meaningful
;;                                                     it may be `display' or whatever you please

static pages with an index dispatcher

(import
 (robusta server)
 (robusta dispatcher)
 (prefix (robusta encoding html) html/))

(define index-html
  (html/encode
   `(html
     (head)
     (body "see the static pages" ((a (href . "static/")) "here")))))

(define (static! req) (static-dispatcher "static" "/static/" req))
;;                                        ^       ^
;;                                        |       \_ static route url base
;;                                        \_ directory with static files


(define dis (make-dispatcher
             "m/static(\\/.*)?/" => static!
             "m/\\/?/"           => (λ (req)
                                      (response
                                       code    => 200
                                       headers => '((Content-type . "text/html"))
                                       content => index-html))))

(bind 8080 dis)

simple app with a logger

(import
 (robusta server)
 (robusta log)
 (robusta dispatcher))

(define logger (make-stdout-logger))
(define app (make-dispatcher "/" =>_ (response content => "hello"))))

(bind 8080 app logger)

POST example

(import
 (robusta server)
 (prefix (robusta dispatcher) D/)
 (prefix (robusta encoding html) html/))

(define dispatcher
  (D/make-dispatcher
   "/" => (λ (req)
            (let* ((M (get req 'method #f))
                   (n (if (eqv? M 'GET)
                          0
                          (string->number (cdr (assq 'n (get req 'post-data #n)))))))
              (response
               code    => 200
               headers => '((Content-type . "text/html"))
               content => (html/encode
                           `(body
                             (p "n: " ,n)
                             ((form (method . "POST"))
                              ((input (type . "hidden")
                                      (name . "n")
                                      (value . ,(number->string (+ n 1)))))
                              (button "+"))
                             ((form (method . "POST"))
                              ((input (type . "hidden")
                                      (name . "n")
                                      (value . ,(number->string (- n 1)))))
                              (button "-")))))))))

(bind 8080 dispatcher)

tsv database

(import
 (prefix (robusta db tsv) tsv/)
 (prefix (owl sys) sys/))

(define (self s) s)
(define dbname "db.tsv")

(define schema
;;   col-name  type-pred  thing->string  string->thing
  `((uname      ,string? ,self           ,self)
    (passwd     ,string? ,self           ,self)
    (secret-num ,number? ,number->string ,string->number)))

(when (sys/file? dbname)
  (sys/unlink dbname))

;; th = tsv handle
(define th (tsv/open "db.tsv" schema))

(tsv/insert-into th '("admin" "zaq1@WSX" 10))
(tsv/insert-into th '("user0" "helloworld" 120))

(print (tsv/filter-by th (λ (u p s) (= s 120))))
(print (tsv/get-column th 'passwd))
(tsv/delete-from th (λ (u p s) (string-ci=? u "USER0")))
(print (tsv/get-all th))

(tsv/close th)

url encoding/decoding

(import (prefix (robusta encoding url) url/))

(define al '((a . b) (c . d) (e . 10) (f . "1") (g . #false)))

;; everything str'd as types get stripped
(print (string=? (str (url/decode-form (url/encode al))) (str al)))