main.scm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. (use-modules (fibers web server))
  2. (use-modules (web request)
  3. (web response)
  4. (web uri))
  5. (use-modules (sxml simple))
  6. (use-modules
  7. ;; alist->hash-table
  8. (ice-9 hash-table)
  9. (ice-9 exceptions))
  10. ;; =========================
  11. ;; REQUEST/RESPONSE HANDLING
  12. ;; =========================
  13. (define* (respond #:optional body #:key
  14. (status 200)
  15. (title "This is my title!")
  16. (doctype "<!DOCTYPE html>\n")
  17. (content-type-params '((charset . "utf-8")))
  18. (content-type 'text/html)
  19. ;; Usually we have no exra headers by default.
  20. (extra-headers '())
  21. ;; If a body is provided use its templatized form. and returns
  22. ;; its last argument, if previous arguments are #t.
  23. (sxml (and body (templatize title body))))
  24. "Respond to a request with the given SXML body. The SXML is put into the HTML
  25. template, which adds html, head, title, and body tag."
  26. ;; as before, answer in two parts, headers and body
  27. (values (build-response #:code status
  28. ;; headers are an alist
  29. #:headers
  30. `((content-type . (,content-type ,@content-type-params))
  31. ,@extra-headers))
  32. ;; Instead of returning the body as a string, respond can be given a
  33. ;; procedure, which will be called by the web server to write out the
  34. ;; response to the client. This procedure gets an output port as an
  35. ;; argument.
  36. ;; So you have 2 options: return string or return procedure which
  37. ;; takes a port.
  38. (λ (port)
  39. (when doctype (display doctype port))
  40. (cond
  41. [sxml
  42. (sxml->xml sxml port)]
  43. [else
  44. (sxml->xml '(p "no HTML body in response") port)]))))
  45. (define (request-path-components request)
  46. "Split a given request path up into its components. A request path is the
  47. route after the domain and host part. For example for
  48. http://localhost:8080/part1/part2/?blub=123 the result will be the list of
  49. containing the string part1 and the string part2."
  50. ;; split the string that represents the uri and decode any url-endoced things
  51. ;; /part1/part2/ --> '("part1" "part2")
  52. (split-and-decode-uri-path
  53. ;; get the uri path as a string from the request struct
  54. ;; http://localhost:8080/part1/part2/?blub=123 --> /part1/part2/
  55. (uri-path
  56. ;; get the request-uri struct:
  57. ;; http://localhost:8080/abc/def/?abc=123 -->
  58. ;; #<<uri> scheme: #f userinfo: #f host: #f port: #f path: "/abc/def/"
  59. ;; query: "abc=123" fragment: #f>
  60. (request-uri request))))
  61. ;; ========
  62. ;; HANDLERS
  63. ;; ========
  64. ;; Next we define some handlers, which take care of handling specific routes.
  65. (define (debug-handler request body)
  66. "The debug-handler will put all request headers into the rendered HTML, so
  67. that we can see them on the page."
  68. (respond
  69. ;; Inside respond the SXML will be put into a template, so there is no need
  70. ;; to add html or body tags.
  71. `((h1 "hello world!")
  72. (table
  73. (tr (th "header") (th "value"))
  74. ;; splice in all request headers
  75. ,@(map (lambda (pair)
  76. `(tr (td (tt ,(with-output-to-string
  77. (lambda () (display (car pair))))))
  78. (td (tt ,(with-output-to-string
  79. (lambda ()
  80. (write (cdr pair))))))))
  81. (request-headers request))))))
  82. (define (hello-world-handler request request-body)
  83. "A handler for a route."
  84. ;; A handler must return 2 values: The header and body of the
  85. ;; response.
  86. (values
  87. ;; Return the headers as first value (the bare minimum).
  88. '((content-type . (text/plain)))
  89. ;; Then the response body. This is an example for returning a string as
  90. ;; second value, instead of a procedure, which takes an output port.
  91. "Hello World!"))
  92. (define (blocking-wait-handler request request-body)
  93. "A handler for a route which waits blockingly for testing concurrent
  94. connections."
  95. (sleep 10)
  96. (values
  97. ;; headers first (the bare minimum)
  98. '((content-type . (text/plain)))
  99. ;; then the response body
  100. "I've been waiting for you ..."))
  101. ;; =========
  102. ;; TEMPLATES
  103. ;; =========
  104. (define (templatize title body)
  105. `(html (head (title ,title))
  106. (body ,@body)))
  107. ;; ======
  108. ;; SERVER
  109. ;; ======
  110. ;; Here we define the routes and other server specific stuff.
  111. ;; A routes-config is a hash that contains associations between route parts and
  112. ;; handlers.
  113. (define routes-config
  114. (alist->hash-table
  115. ;; Using (quote ...) would not evaluate the handlers in the list
  116. ;; so we need quasiquote unquote.
  117. `((("hello" "world") . ,hello-world-handler)
  118. (("debug") . ,debug-handler)
  119. (("wait") . ,blocking-wait-handler))))
  120. (define (logging-middleware request body)
  121. "The logging middleware takes care of logging to stdout whenever a request
  122. comes in. We can imagine all sorts of logging here."
  123. (display
  124. (simple-format
  125. #f "responding for request-path-components: ~s\n"
  126. (request-path-components request))))
  127. (define* (make-routes-dispatcher routes-config #:key (default-handler debug-handler))
  128. "make-routes-dispatcher returns a procedure, which, for each request, looks up
  129. the appropriate handler inside the given routes-config. As a fallback, a
  130. default-handler is given. In this case it is the debug-handler, which will
  131. render all headers."
  132. ;; NOTE: make-routes-dispatcher itself is not the one responsible for
  133. ;; answering to requests. The Guile web server leaves the implementation
  134. ;; details completely to us and thus offers maximum flexibility in this
  135. ;; matter. We made the decision ourselves, that we want to look at the request
  136. ;; URI parts, to determin the appropriate handler.
  137. (λ (request body)
  138. ;; Here we can have sequential actions. The first action in this
  139. ;; example is a logging middleware. We could make a middleware
  140. ;; return a result, which is then handed to the next middleware
  141. ;; which might in turn manipulate the result of the first one or
  142. ;; create a new result or whatever else we want to implement.
  143. (logging-middleware request body)
  144. ;; Only after logging the real request handling begins. First we
  145. ;; get the appropriate handler and then we hand it the request.
  146. (let* ([route-parts (request-path-components request)]
  147. [handler (hash-ref routes-config route-parts default-handler)])
  148. ;; Hand the handler the request and the body of the request.
  149. (handler request body))))
  150. ;; Start the server. The run-server procedure expects to be given a procedure,
  151. ;; which will dispatch requests to whatever is responsible for handling the
  152. ;; requests. Theoretically one could implement everything inside this
  153. ;; dispatcher, but that would be less clean.
  154. (run-server
  155. (make-routes-dispatcher routes-config #:default-handler debug-handler))