woo.ros 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. #|-*- mode:lisp -*-|#
  2. #|
  3. exec ros -Q -- $0 "$@"
  4. |#
  5. ;; Woo is a fast non-blocking HTTP server built on top of
  6. ;; libev. Although Woo is written in Common Lisp, it aims
  7. ;; to be the fastest web server written in any programming
  8. ;; language.
  9. ;; https://github.com/fukamachi/woo
  10. ;; Quicklisp is a library manager for Common Lisp. Use
  11. ;; QuickLisp's quickload function to retrieve external
  12. ;; packages. These packages are automatically curl'd when
  13. ;; the program runs.
  14. ;; Woo - https://github.com/fukamachi/woo
  15. ;; Jonathan - https://github.com/fukamachi/jonathan
  16. ;; CL-MARKUP - https://github.com/arielnetworks/cl-markup
  17. ;; Postmodern - https://github.com/marijnh/Postmodern
  18. ;; QURI - https://github.com/fukamachi/quri
  19. (ql:quickload '(:cl-markup :jonathan :postmodern :quri :uiop :woo) :silent t)
  20. (declaim (optimize (debug 0) (safety 0) (speed 3)))
  21. (load "./helpers/starts-with.lisp")
  22. (load "./helpers/parse-argv.lisp")
  23. ;; Initialize the global random state by "some means" (e.g. current time)
  24. (setf *random-state* (make-random-state t))
  25. (defun plaintext ()
  26. "Plaintext handler."
  27. '(200 (:content-type "text/plain" :server "Woo") ("Hello, World!")))
  28. (defun json ()
  29. "JSON handler using Jonathan to encode JSON"
  30. `(200 (:content-type "application/json" :server "Woo") (,(jonathan:to-json '(:message "Hello, World!")))))
  31. (defun get-a-random-record (id)
  32. (declare (integer id))
  33. `(:|id| ,id :|randomNumber| ,(postmodern:query (:select 'randomnumber :from 'world :where (:= 'id id)) :single!)))
  34. (defun db ()
  35. "DB handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  36. (let ((id (+ 1 (random 10000))))
  37. `(
  38. 200
  39. (:content-type "application/json" :server "Woo")
  40. (,(jonathan:to-json (get-a-random-record id)))
  41. )))
  42. (defun ensure-integer-is-between-one-and-five-hundreds (n)
  43. (declare (integer n))
  44. (if (< n 1)
  45. (values 1 nil)
  46. (if (> n 500)
  47. (values 500 nil)
  48. (values n t))))
  49. (defun extract-number-of-records-to-fetch (env)
  50. (let ((n (handler-case
  51. (parse-integer (cdr (assoc "queries" (quri:url-decode-params (getf env :query-string)) :test #'equal)))
  52. (error (c) (values 1 c)))))
  53. (ensure-integer-is-between-one-and-five-hundreds n)))
  54. (defun get-some-random-integers-between-one-and-ten-thousand (n)
  55. (declare (integer n))
  56. (loop :repeat n
  57. :collect (+ 1 (random 10000))))
  58. (defun get-some-random-records (n)
  59. (declare (integer n))
  60. (let ((ids (get-some-random-integers-between-one-and-ten-thousand n)))
  61. (mapcar #'get-a-random-record ids)))
  62. (defun queries (env)
  63. "QUERIES handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  64. `(
  65. 200
  66. (:content-type "application/json" :server "Woo")
  67. (,(jonathan:to-json (get-some-random-records (extract-number-of-records-to-fetch env))))
  68. ))
  69. (defun get-all-fortunes ()
  70. (postmodern:query (:select 'id 'message :from 'fortune) :rows))
  71. (defun get-all-fortunes-plus-one ()
  72. (let* ((records (get-all-fortunes))
  73. (records-p-one (append records '((0 "Additional fortune added at request time.")))))
  74. (sort (copy-list records-p-one) #'string-lessp :key #'second)))
  75. (defun fortunes ()
  76. "FORTUNES handler using Jonathan to encode JSON, Postmodern to access PostgreSQL and CL-Markup to build the HTML"
  77. `(
  78. 200
  79. (:content-type "text/html; charset=UTF-8" :server "Woo")
  80. (,(cl-markup:html5
  81. (:head
  82. (:title "Fortunes"))
  83. (:body
  84. (:table
  85. (:tr
  86. (:th "id")
  87. (:th "message"))
  88. (loop for fortune-row in (get-all-fortunes-plus-one)
  89. collect (cl-markup:markup
  90. (:tr
  91. (:td (format nil "~d" (first fortune-row)))
  92. (:td (second fortune-row)))))))))
  93. ))
  94. (defun get-and-update-some-random-records (n)
  95. (declare (integer n))
  96. (let* ((random-records (get-some-random-records n))
  97. (random-numbers (get-some-random-integers-between-one-and-ten-thousand n))
  98. (index -1)
  99. (updated-records (map 'list
  100. (lambda (row)
  101. (incf index)
  102. (list :|id| (getf row :|id| )
  103. :|randomNumber| (nth index random-numbers)))
  104. random-records))
  105. (record-list (map 'list
  106. (lambda (row)
  107. (list (nth 1 row)
  108. (nth 3 row)))
  109. updated-records)))
  110. (postmodern:query (format nil "UPDATE world AS ori SET randomnumber = new.randomnumber FROM (VALUES ~{(~{~a~^, ~})~^, ~}) AS new (id, randomnumber) WHERE ori.id = new.id" record-list))
  111. (values updated-records)))
  112. (defun updates (env)
  113. "UPDATES handler using Jonathan to encode JSON and Postmodern to access PostgreSQL"
  114. `(
  115. 200
  116. (:content-type "application/json" :server "Woo")
  117. (,(jonathan:to-json (get-and-update-some-random-records (extract-number-of-records-to-fetch env))))
  118. ))
  119. (defun handler (env)
  120. "Router"
  121. (let ((path (getf env :path-info)))
  122. (cond ((starts-with path "/plaintext") (funcall 'plaintext ))
  123. ((starts-with path "/json" ) (funcall 'json ))
  124. ((starts-with path "/db" ) (funcall 'db ))
  125. ((starts-with path "/queries" ) (funcall 'queries env))
  126. ((starts-with path "/fortunes" ) (funcall 'fortunes ))
  127. ((starts-with path "/updates" ) (funcall 'updates env)))))
  128. (defun main (&rest argv)
  129. "Create and start the server, applying argv to the env"
  130. (let ((args (parse-argv argv)))
  131. (apply #'woo:run
  132. (lambda (env)
  133. ;; preprocessing
  134. (let ((res (postmodern:with-connection '("hello_world" "benchmarkdbuser" "benchmarkdbpass" "tfb-database" :pooled-p t)
  135. (funcall 'handler env))))
  136. ;; postprocessing
  137. res))
  138. :debug nil
  139. args)))