sph-web-app is a library for processing requests and responses from sockets with helpers for handling http web requests. the most basic function is to start a server, call a custom handler procedure with request objects and send response objects to the client
content of file example.scm
(import (sph web app) (sph web app http))
(define (app-respond request) (respond "test"))
(define swa-app (swa-app-new app-respond))
(swa-start swa-app #f #f swa-server-guile)
shell command
guile example.scm
shell standard output
listening on 127.0.0.1:6500
exit with ctrl+c
application browser accessible at
http://127.0.0.1:6500
(sph web app)
core features and servers(sph web app http)
http request and response features(sph web app client)
client code or asset processing (css, js, etc)
a swa-app object contains the request handler procedure to be used and optionally procedures called before or after the server runs
(swa-app-new app-respond)
signatures
swa-app-new :: procedure:respond [#:init procedure #:deinit procedure] -> swa-app
respond :: request -> response
init :: swa-env -> any/swa-env
deinit :: swa-env -> any/swa-env
example with all options
(swa-app-new app-respond #:init app-init #:deinit app-deinit)
app-init is called before the server starts, with a swa-env object that contains for example the parsed configuration. app-init can return a new swa-env object to update it. app-deinit is like app-init but called after the server finished listening
swa-app-init :: swa-app -> procedure
swa-app-deinit :: swa-app -> procedure
swa-app-respond :: swa-app -> procedure
to start a server running an app:
(swa-start swa-app #f #f swa-server-scgi)
signature
swa-start :: swa-app directory configuration server server-arguments ... -> unspecified
swa-start :: vector string false/hashtable/string procedure any ... -> unspecified
server :: swa-env swa-app server-arguments ...
- directory: a root directory for the application (swa-root), relative to which configuration or client files are searched
- configuration: false for no configuration object, a hashtable for the configuration object or a string for a configuration file name
- server: a server implementation procedure like the included swa-server-scgi, swa-server-guile or swa-server-internal
on startup a swa-env
object is created. swa-app handlers receive the swa-env
as an argument or inside the request object.
if a swa-env object is modified, changes are available to all requests.
- root: string: the full path to the app directory
- config: hashtable: user configuration passed to swa-start or loaded from configuration files
- data: hashtable: for custom run-time values, for example values calculated when the application was initialised
(swa-env-data swa-env)
app configuration can be stored in files that are parsed on startup and available to swa-app handlers. some keys are recognised and used by sph-web-app features, like for example keys for server configuration used by server implementations. otherwise key associations are custom and can be freely made
configuration files are expected to be under {swa-root}/config/
- all elements are scheme expressions
- key and value are specified alternatingly
- keys are symbols
- indent of two spaces is used for nesting
- all scheme syntax including comments works
example config/default
default-title "project-name"
server
socket-name "project-name"
socket-permissions #o770
socket-group "http"
types (2 4 5)
configuration files derive from a configuration file "default" it that exists. in other configuration files, any key from default that is not overwritten keeps its value from default. for example, if there is a file "config/development" that contains only:
mode development
and swa-start is instructed to use this configuration with the name development, then the configuration will contain all values from default, overwritten with all values from development. this can be used to create multiple configuration files with minor differences for different environments, for example for different server hosts
configuration will be available as a hashtable in swa-env in the field "config". it can be retrieved by calling (swa-env-config swa-env)
example
(let (config (swa-env-config swa-env))
(hashtable-ref config (quote option-name)))
the following servers are available by default and can be used with swa-start
starts a "simple common gateway interface" (scgi) server to be used as a backend for an http proxy that supports scgi. if you have heard of fastcgi, scgi is a bit like fastcgi but much simpler
swa-server-scgi
a scgi server that uses non-blocking input/output with fibers. usage
(import (sph web app) (sph server fibers))
(swa-start swa-app "default" swa-server-scgi
#:server-listen server-listen-fibers
#:server-socket server-socket-fibers)
caveats of using guile fibers apply (handler should not use any blocking features, need to use rnrs port writers and readers, multiple requests might be processed in the same kernel thread which makes it incompatible with sph-db)
the http server that comes with guile
swa-server-guile
does not create a socket and calls a procedure with the initialised application. can be used for testing
example
(swa-start swa-app (getcwd) "default" swa-server-internal
(lambda (swa-env app-respond)
(app-respond (swa-http-request-new path arguments headers client swa-env #f))))
server
listen-port 1234
listen-address "::1"
socket-name "testname"
socket-group "http"
socket-permissions #o777
without those options specified the default is to create a local unix socket at the following path
/tmp/$UID/scgi
set the listen-address
option in the configuration file to the desired filesystem path
set listen-address
to a string with the ip4 or ip6 address to bind to and listen-port to the port number to use
server
listen-address "::1"
listen-port 6500
the swa-app respond procedure is called for each request with a request object that contains information about the current request.
the type of the object depends on the server implementation used. for swa-server-scgi
and swa-server-guile
, it is a request object from (sph web app http)
.
- path:
string
, current request path - query:
false/((string . any) ...)
, parsed query string. only available if#:parse-query? #t
was passed toswa-start
- headers:
((string . any) ...)
, alist of parsed http headers - client:
input-output-port
, can be used for reading from the client. should not be used for writing as sending the response is done when processing the response object - swa-env:
vector
- data:
false/hashtable
, custom data can be set here
(import (sph web app) (sph web app http))
(swa-http-request-path request)
(match-path (swa-http-request-path request)
(() (start-page request))
(("c" "browse") (c-browse request #f (list)))
(("c" "browse" type tags ...) (c-browse request type tags))
(("robots.txt") (respond-type (quote text) "User-Agent: *\nDisallow: /c/browse/link/"))
(_ (respond 404)))
match-path
is like (ice-9 match), except that the path is without the root path portion split at slashes before being passed to match
. all features of (ice-9 match)
can be used
for swa-server-scgi and swa-server-guile, the app respond procedure is expected to return swa-app-http-response objects from `(sph web app http).
examples
(respond 200)
(respond "test")
(respond 200 "test")
(respond 200 (list) "test")
(respond (lambda (client) (display "test" client)))
(respond 200 (list "content-type:text/plain\r\n") (lambda (client) (display "test" client)))
form 1
false/integer/string/procedure:{port:client -> unspecified} -> vector
status-code/response-body/response-body-writer -> response
form 2
integer false/string/procedure -> vector
status-code response-body/response-body-writer -> response
form 3
integer list:(string:crlf-terminated-header-line ...) false/string/procedure -> vector
status-code headers response-body/response-body-writer -> response
- response-body-writer: procedure: called with a port for bidirectional communication with the client after the headers have been sent. for example to read a post request body
- integer: http status code
- string: response-body
- false: http status 404
respond-type
is like respond
but sets the content-type header based on a symbol identifier.
type identifiers supported by default are: json
, html
, js
, css
, text
.
the hashtable swa-http-key->mime-type
from (sph web app http)
defines the types and can be extended
examples
(respond-type (quote json) "{\"test\": 1}")
(respond-type (quote text) 200 (list) "test")
signature
symbol:content-type-identifier respond-argument ... -> swa-http-response
- code to be executed by the client can be created directly where needed in scheme procedures or loaded from external files under
client/{format-name}/
- source formats recognised by default: html, css, javascript, sxml, plcss, sescript
- target formats supported by default: html, css, javascript
- import
(sph web app client)
to use the following features
client-output-path "webroot/"
web-base-path "/"
static files are either in the web root and handled by a proxy webserver (possibly using features like x-accel-redirect), or read and send by the application.
for compiled static files, create a client-static
config object and define file bundles with client-static-config-create
(import (sph web app client))
(define client-static-config
(client-static-config-create
(default
js ("main")
css ((example-variable "testvalue" another-variable 3) "main" "otherfile"))
(otherbundle js ("otherfile"))))
sources are specified as for client-file
and paths are used like this {swa-root}/client/{output-format}/{relative-path}
. all available supported formats can be used, for example also plain html and html from sxml. and the source files can also be sjs or plcss, with the file path client/css/main.plccs
for example
in app-init
, call
(client-static-compile swa-env client-static-config)
where needed, get the public path to one or multiple compiled bundle files with client-static
(client-static swa-env (quote css) (quote (bundle-name other-bundle-name)))
(sph web app client)
has exports to pre-process files/templates and sets of those on demand
template variables, which are passed in association lists with symbol keys to client file processors, are made available in unquote in the template code
client-file writes the eventually processed result to a file with an automatically generated file name with an underscore prefix, unless set otherwise using the file-name parameter.
paths are relative to {swa-root}/client/{output-format}/
(client-file swa-env output-format template-variables file-path #:optional file-name)
(client-file swa-env (quote js) (quote ((a . 2) (b . 3))) "relative-path")
like client-file but writes output to a port
(client-port swa-env output-format port-output bindings sources)
both the static and dynamic client-
bindings read s-expression based formats like sxml, plcss and sescript as templates.
templates are interpreted as if being quasiquoted, and unquote can be used to insert results of scheme expressions and access template variables.
example file content with insert of a template variable a
(div (@ (class test)) (unquote a))
also
(div (@ (class test)) ,a)
example 2
(div
(unquote
(if example-variable
(qq (span "a"))
(qq (span (@ (class test-class)) "b")))))
modify the client-ac-config
hashtable, which maps output-format-symbol
to (ac-output ac-input ...)
, see also modules/sph/filesystem/asset-compiler.scm
and client.scm
example
(import (sph filesystem asset-compiler) (sph web app client) (rnrs hashtables))
(hashtable-set! client-ac-config (quote tar)
(list
(ac-config-output (quote tar) ".tar" ac-output-copy)
(ac-config-input (quote tgz) ".tgz"
(lambda (source next-port options)
(call-with-input-file source (lambda (file) (decompress file next-port)))))))
import application modules as scheme libraries where needed
for example in modules/sph-mn.scm
(import (sph web app) (prefix (sph-cms) cms-))
(define (app-init swa-env)
; calls the init routine of the other project to use its special features
((swa-app-init cms-swa-app) swa-env))
(define (app-respond request)
(respond "i'm derived"))
(define swa-app
(swa-app-new app-respond
#:init app-init #:deinit (swa-app-deinit cms-swa-app)))
this imports the swa-app object from (sph-cms) with the identifier prefixed with cms- to avoid a name conflict in the current module, then uses the swa-app object accessor swa-app-init to retrieve the other app-init procedure and calls it with the swa-env. all client files that are compiled are saved in the current/parent swa-root
a proxy is not necessary if instead of swa-server-scgi, swa-server-guile is used. however, scgi is the recommended way because scgi is an interface that allows an established http server to do the work of http request parsing and connection management - and there are servers that do this well, stable and fast. for example nginx allows for fast file transfers without redirection through the application (x-accel), https, keep-alive requests, chunked encoding, gzip compression, load-balancing and much more. to do this, set up a server to interface with the socket that is created when starting the application with the scgi server (the path is displayed when the server starts). nginx, apache and lighttp support it
see nginx example configuration for scgi and web-app
the target host needs to have guile and sph-web-app set up. setup is the same as for local development environments
example systemd service file /etc/systemd/system/sph-info.service
[Unit]
Description=sph-info scgi application
[Service]
Environment="GUILE_LOAD_PATH=/usr/share/guile/2.2:/usr/share/guile/site"
WorkingDirectory=/opt/sph-info
ExecStart=/opt/sph-info/exe/start-dynamic
Type=simple
Restart=on-failure
User=sph-info
[Install]
WantedBy=multi-user.target
example project directory with client, config, webroot and custom other directories
exe
start
client
css
main.css
js
main.js
config
default
development
modules
project-name.scm
readme.md
webroot
assets
css/_main.css
js/_main.js
- webroot: the only directory directly accessible for web server clients. the document root for a (proxy) web server. contains compiled client code files under assets and any static files that should be accessible to clients without redirection through the application. the name "root" was chosen in favor of "public" or "shared"
- exe: contains executable files for general project management. for example for starting the application or custom helper scripts. the name "exe" was chosen in favor of "bin" or "script"
- client: the client directory contains code or sources and templates for code that is to be evaluated by the client. for example html, css or javascript
example content for a file exe/start
#!/usr/bin/guile
!#
(add-to-load-path (string-append (getcwd) "/modules"))
(import (sph) (sph web app) (sph-info))
(apply
(l* (#:optional (config "production"))
(swa-start swa-app (getcwd) config swa-server-scgi #:parse-query #t))
(tail (program-arguments)))
example modules/project-name.scm
(library (project-name)
(export swa-app)
(import
(rnrs base)
(sph web app)
(sph web app http))
(define (app-respond request)
(respond "test"))
(define swa-app (swa-app-new app-respond)))
start the application
./exe/start