Rework the layout of the page: instead sections with procedure signature, use one (or maybe two levels) with plain old natural language small description;
hyper
server does not aim to support bigger than memory request
or response bodies; one way to work around it is to propose a way
to interop with a low-level http
reader and
writer library;
Rework hyper-spawn
argument called proc
to take two arguments:
app
, and request
; and describe procedure to access method
,
path
, params
, headers
, and body
;
hyper
(hyper-spawn ip port init proc)
Start listening for HTTP request on Internet address represented as a
string IP
, and port number PORT
. INIT
is called only once, and
its result is passed as first argument of every call to PROC
.
For each incoming request, execute PROC
with the following
arguments:
app
: the return value of INIT
;method
: a symbol built from the downcased HTTP method;path
: a list of strings that build the path of the HTTP request line;params
: an association list where keys are symbols and values are
lists of strings that is the result of parsing the "query string"
part of the HTTP request line's path;headers
: an association list where keys are symbols and values
are strings, representing the HTTP headers of the request;body
: a bytevector that represent the body of the HTTP request,
it can be a bytevector of length zero;PROC
is expected to return three values:
(hyper-html5-write sxml)
Returns a bytevector representing SXML
encoded in UTF-8
HTML5. Raises an object that satisfy hyper-html5-error?
when sxml
can not be encoded into JSON.
(hyper-json-read bytevector)
Returns a Scheme object according to the specification SRFI-180 of the
JSON object encoded in BYTEVECTOR
. Raises an object that satisfy
hyper-json-error?
when bytevector
can not be read as a Scheme object.
(hyper-json-write obj)
Returns a bytevector encoding OBJ
into a JSON object according to
the specification of SRFI-180. Raises an object that satisfy
hyper-json-error?
when OBJ
can not be encoded into JSON.
(define my-app
(lambda (_ method path params headers body)
(match (cons method path)
(('GET "n" "compendium-of-web-patterns")
(values 200
'((content-type . "text/html"))
(hyper-html-write '(html (body "Azul dunith!"))))))))
(hyper "192.168.0.1" 1337 list my-app)
Using exceptions to validate input is an anti-pattern: it is unlikely that 1) you call shallow, and deep validation 2) forget about it, and proceed to transfer 42 millions euros without checking that it is my bank account. A more relatable example: say you have a route to handle username allocation, 1) you make two shallow checks: a) the username contains only alphanumeric ascii chars b) the username is at least 3 chars long, and at most 30 chars long; 2) you check the username is not already used; if you did both 1 and 2, and at least one of the check fail, why will you nonetheless create a user with that username? That would be a bug, that is an error, but not an exceptional condition since you know that it can happen.
Calling raise
slow down the program, and you can avoid it
without workarounds.
A good reason to use raise
is when a condition or various
relatable conditions may happen in several procedure calls deep
in the callstack, and the way to handle that behavior in a way
that is clean, proper is to implement the handling guard
somewhere a few procedure calls above where it happens. Example:
imagine you have to make remote procedure calls over the network
against one or more other programs; all those calls are related,
they all succceed or the whole operation will be considered a
failure; because it is a complex workflow, the actual network calls
are two or more level deeper in the stack, passing around the error
to push back up the error state is cluttering the code, and you
have to do that with calls that may me nested at different levels:
imagine several nested if
, cond
, case
, match
. A precise
case of that is the communication with a remote SQL database, every
call to the database whether it is a SELECT
or INSERT
or
DELETE
or whatnot will try to call the database over the network,
all of those calls can fail because the network link can break;
imagine for example you check that an username is available, it is,
then the network link is broken, when the network is back, and to
preserve consistency you need to restart the transaction from
scratch ie. check again the username is available, then allocate
that username. That would look something like:
(define (maybe-allocate-username username client)
(if (username-available? username)
(allocate-username username client)
#f))
Both username-available?
and allocate-username
can fail because
of a network failure. Try to imagine the number of if
to include
to check for network failures, and propagate that in a way that can
be processed up the stack in a meaningful way, that is: another way
to express the above code. Both username-available?
and
allocate-username
may raise an object that explains there is a
network failure, while the purpose of the procedure is very
understandable.
Only use raise
, if an error can happen further down the stack.
Otherwise, if it is doable to locally handle errors such as input
validation, or domain specific errors, do it locally with return
values, or an accumulator.
Systematically wrapping a whole request-response with a transaction
begin, then commit, otherwise rollback; prefer to use of ad-hoc and
explicit (call-with-transaction database proc)
inside the path
handler.
What this anti-pattern could look like using the hyper
server is
something like:
(define (some-boilerplate-ish-framework ip port init proc)
(hyper ip port init
(lambda (app method path params headers body)
(call-with-transaction (app-database app)
(lambda (tx)
(let ((app/tx (make-app/tx app tx)))
(proc app/tx method path params headers body)))))))
The above will systematically start a transaction called tx
, and
commit it inside call-with-transaction
when there is nothing that
is raised that bubbles up. One might say, let's bubble-up errors,
and avoid to commit dubious data; that does not work in the general
case... why?
Using only auto-commit is an anti-pattern.
Form rendering, and form validation are orthogonal concerns.
Using strings as intermediate representation of HTML5 fragmentsh.