main.scm 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. ;; Instead of using Guile's built in web server, we simply use fibers
  2. ;; web server. The interface for the developer seems to be the
  3. ;; same. Handlers will now handle requests in spawned fibers, instead
  4. ;; of one at a time blocking each other.
  5. (use-modules (fibers web server))
  6. ;; (use-modules (web server)) ; Not using Guile's built in web server.
  7. (use-modules (web request)
  8. (web response)
  9. (web uri))
  10. (use-modules (sxml simple))
  11. (use-modules (ice-9 hash-table)) ; alist->hash-table
  12. ;; =========================
  13. ;; REQUEST/RESPONSE HANDLING
  14. ;; =========================
  15. (define* (respond #:optional body #:key
  16. (status 200)
  17. (title "This is my title!")
  18. (doctype "<!DOCTYPE html>\n")
  19. (content-type-params '((charset . "utf-8")))
  20. (content-type 'text/html)
  21. (extra-headers '())
  22. ;; if a body is provided use its templatized form
  23. (sxml (and body (templatize title body))))
  24. ;; as before, answer in two parts, headers and body
  25. (values (build-response #:code status
  26. ;; headers are an alist
  27. #:headers `((content-type . (,content-type ,@content-type-params))
  28. ,@extra-headers))
  29. ;; instead of returning the body as a string, respond gives
  30. ;; a procedure, which will be called by the web server to
  31. ;; write out the response to the client.
  32. ;; So you have 2 options: return string or return procedure which takes a port.
  33. (λ (port)
  34. (if sxml
  35. (begin
  36. (if doctype (display doctype port))
  37. (sxml->xml sxml port))))))
  38. (define (request-path-components request)
  39. ;; just for showing what the functions do
  40. ;; (display (simple-format #f "(request-uri request): ~a\n"
  41. ;; (request-uri request)))
  42. ;; (display (simple-format #f "(uri-path ...): ~a\n"
  43. ;; (uri-path (request-uri request))))
  44. ;; (display (simple-format #f "(split-and-decode-uri-path ...): ~a\n"
  45. ;; (split-and-decode-uri-path (uri-path (request-uri request)))))
  46. ;; actual logic
  47. ;; split the string that represents the uri and decode any url-endoced things
  48. (split-and-decode-uri-path
  49. ;; get the uri path as a string from the request struct
  50. (uri-path
  51. ;; get the request struct
  52. (request-uri request))))
  53. (define (debug-handler request body)
  54. ;; use respond helper
  55. (respond
  56. ;; will be templatized
  57. `((h1 "hello world!")
  58. (table
  59. (tr (th "header") (th "value"))
  60. ;; splice in all request headers
  61. ,@(map (lambda (pair)
  62. `(tr (td (tt ,(with-output-to-string
  63. (lambda () (display (car pair))))))
  64. (td (tt ,(with-output-to-string
  65. (lambda ()
  66. (write (cdr pair))))))))
  67. (request-headers request))))))
  68. (define (hello-world-handler request request-body)
  69. "A handler for a route."
  70. ;; A handler must return 2 values: The header and body of the
  71. ;; response.
  72. (values
  73. ;; headers first (the bare minimum)
  74. '((content-type . (text/plain)))
  75. ;; then the response body
  76. "Hello World!"))
  77. (define (blocking-wait-handler request request-body)
  78. "A handler for a route which waits blockingly for testing concurrent
  79. connections."
  80. (sleep 10)
  81. (values
  82. ;; headers first (the bare minimum)
  83. '((content-type . (text/plain)))
  84. ;; then the response body
  85. "I've been waiting for you ..."))
  86. ;; =========
  87. ;; TEMPLATES
  88. ;; =========
  89. (define (templatize title body)
  90. `(html (head (title ,title))
  91. (body ,@body)))
  92. ;; ======
  93. ;; SERVER
  94. ;; ======
  95. ;; A hash that contains routes to handler associations
  96. (define routes-config
  97. (alist->hash-table
  98. ;; using (quote ...) would not evaluate the handlers in the list
  99. ;; so we need quasiquote unquote
  100. `((("hello" "world") . ,hello-world-handler)
  101. (("debug") . ,debug-handler)
  102. (("wait") . ,blocking-wait-handler))))
  103. (define (logging-middleware request body)
  104. "The logging middleware takes care of logging to stdout whenever a
  105. request comes in. We can imagine all sorts of logging here."
  106. (display (simple-format #f "responding for request-path-components: ~s\n"
  107. (request-path-components request))))
  108. (define* (make-routes-dispatcher routes-config #:key (default-handler debug-handler))
  109. "return a procedure, which, for each request, looks up the
  110. appropriate handler inside the given routes-config"
  111. (λ (request body)
  112. ;; Here we can have sequential actions. The first action in this
  113. ;; example is a logging middleware. We could make a middleware
  114. ;; return a result, which is then handed to the next middleware
  115. ;; which might in turn manipulate the result of the first one or
  116. ;; create a new result or whatever else we want to implement.
  117. (logging-middleware request body)
  118. ;; Only after logging the real request handling begins. First we
  119. ;; get the appropriate handler and then we hand it the request.
  120. (let* ([route-parts (request-path-components request)]
  121. [handler (hash-ref routes-config route-parts default-handler)])
  122. ;; Hand the handler the request and the body of the request.
  123. (handler request body))))
  124. ;; Start the server.
  125. (run-server (make-routes-dispatcher routes-config #:default-handler debug-handler))