tree-routing-example.scm 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #|
  2. Note: This is an attempt of using a tree structure to manage routes,
  3. where sub routes can have different handlers than super routes. It is
  4. experimental and might not cover many edge cases. The workings of the
  5. lookup of routes inside the tree and how it is decided whether or not
  6. to look up sub routes in the tree, is described in ~simple-tree.scm~.
  7. |#
  8. (add-to-load-path (dirname (current-filename)))
  9. (use-modules (web server))
  10. (use-modules (web request)
  11. (web response)
  12. (web uri))
  13. (use-modules (sxml simple))
  14. (use-modules (ice-9 hash-table))
  15. (use-modules ((simple-tree) #:prefix simtree:))
  16. ;; =========================
  17. ;; REQUEST/RESPONSE HANDLING
  18. ;; =========================
  19. (define* (respond #:optional body #:key
  20. (status 200)
  21. (title "This is my title!")
  22. (doctype "<!DOCTYPE html>\n")
  23. (content-type-params '((charset . "utf-8")))
  24. (content-type 'text/html)
  25. (extra-headers '())
  26. ;; if a body is provided use its templatized form
  27. (sxml (and body (templatize title body))))
  28. ;; as before, answer in two parts, headers and body
  29. (values (build-response #:code status
  30. ;; headers are an alist
  31. #:headers `((content-type . (,content-type ,@content-type-params))
  32. ,@extra-headers))
  33. ;; instead of returning the body as a string, respond gives
  34. ;; a procedure, which will be called by the web server to
  35. ;; write out the response to the client.
  36. ;; So you have 2 options: return string or return procedure which takes a port.
  37. (λ (port)
  38. (if sxml
  39. (begin
  40. (if doctype (display doctype port))
  41. (sxml->xml sxml port))))))
  42. (define (request-path-components request)
  43. ;; just for showing what the functions do
  44. ;; (display (simple-format #f "(request-uri request): ~a\n"
  45. ;; (request-uri request)))
  46. ;; (display (simple-format #f "(uri-path ...): ~a\n"
  47. ;; (uri-path (request-uri request))))
  48. ;; (display (simple-format #f "(split-and-decode-uri-path ...): ~a\n"
  49. ;; (split-and-decode-uri-path (uri-path (request-uri request)))))
  50. ;; actual logic
  51. ;; split the string that represents the uri and decode any url-endoced things
  52. (split-and-decode-uri-path
  53. ;; get the uri path as a string from the request struct
  54. (uri-path
  55. ;; get the request struct
  56. (request-uri request))))
  57. (define (debug-handler request body)
  58. ;; use respond helper
  59. (respond
  60. ;; will be templatized
  61. `((h1 "hello world!")
  62. (table
  63. (tr (th "header") (th "value"))
  64. ;; splice in all request headers
  65. ,@(map (lambda (pair)
  66. `(tr (td (tt ,(with-output-to-string
  67. (lambda () (display (car pair))))))
  68. (td (tt ,(with-output-to-string
  69. (lambda ()
  70. (write (cdr pair))))))))
  71. (request-headers request))))))
  72. (define (hello-world-handler request request-body)
  73. (values
  74. ;; headers first
  75. '((content-type . (text/plain)))
  76. ;; then the response body
  77. "Hello World!"))
  78. (define (index-handler request request-body)
  79. (values
  80. ;; headers first
  81. '((content-type . (text/plain)))
  82. ;; then the response body
  83. "Welcome to the index page"))
  84. ;; =========
  85. ;; TEMPLATES
  86. ;; =========
  87. (define (templatize title body)
  88. `(html (head (title ,title))
  89. (body ,@body)))
  90. ;; ======
  91. ;; SERVER
  92. ;; ======
  93. ;; A tree that contains routes to handler associations
  94. (define routes-config
  95. (simtree:list->tree
  96. `(root ,index-handler
  97. (("hello" ,debug-handler
  98. (("world" ,hello-world-handler ())))
  99. ("debug" ,debug-handler ()))))
  100. #;(alist->hash-table
  101. ;; using (quote ...) would not evaluate the handlers in the list
  102. ;; so we need quasiquote unquote
  103. `((("hello" "world") . ,hello-world-handler)
  104. (("debug") . ,debug-handler))))
  105. (define (logging-middleware request body)
  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. (logging-middleware request body)
  113. (let* ([route-parts (request-path-components request)]
  114. [handler
  115. (simtree:node-val
  116. (simtree:get-node-by-path routes-config
  117. (cons 'root route-parts)
  118. default-handler
  119. #:use-longest-prefix #t))])
  120. #;(display (simple-format #f "handler: ~s\n" handler))
  121. (handler request body))))
  122. (run-server (make-routes-dispatcher routes-config
  123. #:default-handler debug-handler))