In the previous article, we set a goal to deploy a program in one hip command line hop, let’s kickstart the code with an outline of the application server.
Any resemblance to competitor technology is wholly, and fully coincidental.
We want to deploy http web application stuffed into a hello library that looks like the following:
;; 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")))))The expected, so called workflow, is that given an existing application server running at www.example:9999, we want to deploy the above library with the following one-liner:
#;sh> petit-cloud deploy www.example:9999 hello.scmWhen that command is issued, we will be prompted for a password, after a nanowhile, we will be able the manually test it with:
#;sh> curl http://www.example:9999
Hello schemer!The application server will be listening to port 9999 of all available network interfaces, that can be done with:
(define-values (client-accept close)
(petit-cloud-http-connect "0.0.0.0" 9999))The variable client-socket is a generator of three objects read, write, and close, that work as follow:
(read) will produce a representation of an HTTP request;(write object) will write OBJECT as an HTTP response;(close) will close the connection.The simplest way to use it is:
(define-values (client-accept close)
(petit-cloud-http-connect "0.0.0.0" 9999))
(define-values (read write close)
;; client-accept will pause
;; until a HTTP request hit
;; the port 9999
(client-accept))
(define-values (method url headers body) (read))
;; let's reply
(write 200
(list (cons 'content-type "text/plain"))
(string->utf8 "Hello schemers\n"))
(close)It is possible accept multiple clients simultaneously, by passing a procedure to petit-cloud-http-connect:
(define client-accept
(lambda (read write close)
;; where is everyone
(define-values (method url headers body) (read))
;; reply
(write 200
(list (cons 'content-type "text/plain"))
(string->utf8 "Hello schemers\n"))
;; the end
(close)))
(petit-cloud-http-connect "0.0.0.0"
9999
client-accept)The command call petit-cloud deploy will hit the application server with a method that is the symbol 'POST, and an url that is:
(list 9999
(list "example" "www")
("_" "api" "petit" "cloud")
(list)
(list))And the body is the bytevector representation of hello.scm at the start of the article.
We can implement a predicate that returns #t when it is a call by petit-cloud deploy:
(define petit-cloud-deploy?
(lambda (method path)
(equal? (cons 'POST
(list 9999
(list "example" "www")
("_" "api" "petit" "cloud")
(list)
(list)))
(cons method path))))If the request is not a deployment, we fallback to the application procedure returned by the parameter petit-cloud-current-application.
At this point, and without any error handling, our application server code looks like the following:
(import (petit-cloud))
(define petit-cloud-deploy?
(lambda (method path)
(equal (cons 'POST
(list 9999
;; TODO: do not hardcode port, and domain
(list "example" "www")
(list "_" "api" "petit" "cloud")
(list)
(list)))
(cons 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 "0.0.0.0" 9999 client-accept)Let’s summarize:
The evaluation of the expression (petit-cloud-http-connect ip port handler) will call HANDLER on each new request. The definition of petit-cloud-http-connect is interesting for the purpose of building a petit cloud, call that the cherry on the cake.
The procedure mentioned above called HANDLER is passed three arguments: read, write, and close.
Each procedure of read, write, and close follow a precise protocol. The way they are applied, its arguments, and return values, but also the order in which each of them is called is specified. For instance, calling close, thereafter read or write does not make sense, because after (close) there is no-one listening.
The procedure petit-cloud deploy will call the procedure HANDLER, compute a new application with make-petit-cloud-application, with the returned procedure, reset the parameter petit-cloud-current-application.
What (make-petit-cloud-application body) do is very interesting. Tho, at this time, that is only food for thought.