Next: , Previous: , Up: API  

3.7 (web middleware simple)

3.7.1 Macros

Macro: match-route stx

Match incoming request against a set of defined routes.

The first argument is a list of three bindings: a request, a request body and a continuation. Those are usual arguments any middleware would receive. The created bindings may be used in routes’ definitions. Routes definitions are passed as rest of parameters to this macro.

(match-route (request body cont) route route* ...)

Each route takes a form of:

(method route-name-or-nothing path exp* ...)
  • method - is a plain word with http request method (case-insensitive) or any, meaning matching any method.
  • route-name-or-nothing - if not ommitted, this is a symbol of the route’s name. Using this symbol, the URL of this route can be built.

    As a convenience feature, this symbol may be equal to the route’s handler procedure name. It this case, if the route’s body exp* expressions are missing, then the handler procedure will be applied with keyword arguments. The handler’s keyword arguments are meant to be named after the middleware argument bindings’ names and (if any) the path bindings. For example:

    (define* (handler #:key request body cont id)
      ;; code
      )
    (define app (compose-middlewares 
                  (match-route (request body cont)
                    (get 'handler ("page" id)))))
    
  • path - a list of strings and/or named bindings, matching the result of splitting the request URI path by /. The empty list matches the root path. At the end of the list the "match all" operator * can be used. E.g.:
    (define* (handle-admin-routes #:key request body cont)
      ;; code
      )
    (any 'handle-admin-routes ("admin" *))
    ;; matches every path, every method under /admin/
    
  • exp* ... - expressions, handling the request. Can be omitted if the route name is given and the corresponding procedure is defined.

The (web middleware route-url) package defines the route-url procedure to make URLs by route name and parameters. For example, assuming the 'handler example route from above is defined:

  (route-url 'handler #:id "1")
  ;; => "/page/1"
Macro: use-parameters x

Parameterize values to be used within the continuation.

This middleware seems to make code more expressive. So, instead of:

;; without use-parameters
(define app (compose-middlewares
              ;; middleware closure can refer to defined parameter
              (show-demo-with (request-context:some-first-parameter))
              (lambda (request body cont)
                (values '((content-type text/plain))
                        ;; middleware itself can refer to defined parameter
                        (request-context:some-second-parameter)))))
(run-server 
  (lambda (request body)
    (parameterize ((request-context:some-first-parameter  some-first-value)
                   (request-context:some-second-parameter some-second-value))
      (app request body))))

the code, using use-parameters macro may be expressed like this:

;; with use-parameters
(define app 
  (compose-middlewares
    (use-parameters ((request-context:some-first-parameter  some-first-value)
                     (request-context:some-second-parameter some-second-value)))
    ;; middleware closure can refer to defined parameter
    (show-demo-with (request-context:some-first-parameter))
    (lambda (request body cont)
      (values '((content-type text/plain))
      ;; middleware itself can refer to defined parameter
      (request-context:some-second-parameter)))))
(run-server app)

param param* (syntax) parameter assignment syntax, e.g.

(define app 
  (compose-middlewares
    (use-parameters ((request-context:some-first-parameter  some-first-value)
                     (request-context:some-second-parameter some-second-value)))

    ;; ...
    ))

3.7.2 Procedures

Procedure: filter-request pred . pred*

Check a request to match all predicates.

pred . pred* (procedure): Predicate procedures checking the request object.

Return values response and response body.

Procedure: handle-error KEY: #:debug? #:print-exception #:render-data #:build-response

Set an exception handler, render error data if an exception is caught.

The caught exception is printed to the (current-error-port) using print-exception procedure (display, by default). If debug? is #t, then don’t unwind the stack. While in development, it is useful not to unwind the stack to be able to inspect and even fix some things in the REPL’s debugger. As the result, return the response as built with the build-response procedure. See Default error response builder

debug? (boolean): whether to unwind the stack. #f for unwind (default).

print-exception (procedure) how the exception is printed to standard error, display by default.

render-data (mixed): rendered error data, passed to build-response.

build-response (procedure): error response builder, sxml renderer by default. See Default error response builder.

Return values response and response body.

Procedure: log-request KEY: #:output-port #:format-string #:getters #:additional-env

Print incoming request information to the output-port with a customizable message format.

By default, the log output goes to (current-output-port). Message is formatted accordingly to the format-string, "~a ~a" by default, and it logs the request method and the uri path. The data being logged is selected by the getters argument. This argument takes a list of procedures, each receiving an environment using a property list. The default environment consists of (#:request request #:body body). It can be augmented by specifying additional-env property list.

There is an implicit requirement: the number of format-string placeholders must be equal to the number of getters elements.

Here is an example to demonstrate log message customization by adding a getter and using additional environment:

(use-modules ((web middlewares logger-env) 
              #:select (get-request-method 
                        get-uri-path)) 
;; Procedure, expecting custom "service-prefix" and "default-prefix" keys 
;; from the environment 
(define* (get-prefix #:key 
                     request
                     service-prefix 
                     default-prefix 
                     #:allow-other-keys) 
 (if (string-prefix? "/service" (uri-path (request-uri request))) 
     service-prefix 
     default-prefix))

(define app (compose-middlewares 
             (log-request #:format-string "~a: ~a ~a"
                          #:getters (list get-prefix 
                                          get-request-method 
                                          get-uri-path) 
                          #:additional-env '(#:service-prefix "Service request" 
                                             #:default-prefix "Request"))) 
             ;; ...
            ))

output-port (port): the log output port (default: (current-output-port)).

format-string (string): the log message format string, as accepted by Guile’s format procedure (default: "~a ~a").

getters (list of procedures): procedures to get each format-string’s placeholder values.

additional-env (property list): additional parameters for getters procedures (default '()).

Return values response and response body.

Procedure: passthru request body cont

Do nothing, continue to the next middleware.

Procedure: serve-static url-path folder-path KEY: #:tag #:mime-detection

Serve directory contents under the path folder-path within url-path URL-prefix.

This middleware may be declared for several folders within a single app. Each folder should be given it’s unique tag. The default tag is 'assets. Tags are useful for resource URL creation, and should be created with a helper procedure static-url from (web middleware static-url):

(use-modules (web middleware static-url))
(static-url "css/style.css" #:tag 'assets)
;; or just
(static-url "css/style.css")

The mime-detection is a list of MIME-type detection methods. Couple of those methods are defined by (web middleware mime-detection). The default one is to detect MIME-type by common file extensions. If not detected, "application/octet-stream" is used. There is a mime-by-file-util method, which requires a Unix file utility to be available in the system. If the first method fails, then second is used, and so on.

This middleware reads the whole file into memory, which may be unwanted in some cases. It’s recommended to use a full blown web server to serve static files in production.

Procedure: strip-subpath user-prefix

Strip the user-prefix subpath from input request.

Middlewares, following this middleware in a chain, would receive a new request with user-prefix being stripped from the URI path. The request must have a user-prefix path prefix, throws otherwise.

Procedure: wrap-handler handler

Adapt the standard Guile handler procedure for the middleware chain.

If handler receives request and request body as arguments, then it may be wrapped as a middleware. It must return a response then.


Next: , Previous: , Up: API