Browse Source

Added Agoo, Racket, Ningle and Ninglex (#4709)

* Refactored the entire code to add add database bound handlers

* Updated `.travis.yml`

* Replaced the Dockerfile to update the dependencies (Linux, Roswell, Quicklibs, SBCL, …) to the (al)most recent versions

* Updated the Dockerfile. Reduced the image size from 491MB to 255MB

* Updated the Dockerfile again. Reduced the image size from 255MB to 191MB

* Optimised the code: The handler is now way more readable and even slightly faster albeit not much. Two libraries removed. I instructed the compiler to optimise for maximum speed.

This commit shoves another MB from the Docker image ;-)

* Started to add the Racket servlet framework to the TFB

* Finished adding the Racket servlet framework to the TFB

* Added Racket to `.travis.yml`

* Updated the Racket servlet framework to the TFB

* Fixed the Racket servlet framework to the TFB

* Updated the Racket servlet framework to the TFB to use a connection pool

* Added Ningle and Ninglex to the TFB

* Trying to fix `ninglex.dockerfile` not building in TFB`s Travis

* Updated `.travis.yml` for separate error reports per Lisp framework

* Uncommented `(declaim (optimize (debug 0) (safety 0) (speed 3)))`

* Refactored `ninglex.dockerfile`

* Fixed a regression only seen on TFB’s Travis:

```
query-value: query returned wrong number of rows
  statement: "select randomnumber from world where id = $1"
  expected: 1
  got: 0
```

* Add Agoo - a High Performance HTTP Server for Ruby <https://github.com/ohler55/agoo>

* Updated `.travis.yml` as per request. Moved Racket into a separate directory. Common Lisp and Racket Scheme are quite different things and their `dockerfile`s reflect that

* Updated the `benchmark_config.json`s to reflect the proper name of the programming language used: Common Lisp

* Moved Racket into a separate directory, second take ;-)
Gert Thiel 6 years ago
parent
commit
37e320f8e9
31 changed files with 1146 additions and 8 deletions
  1. 3 2
      .travis.yml
  2. 5 0
      frameworks/Lisp/ningle/README.md
  3. 30 0
      frameworks/Lisp/ningle/benchmark_config.json
  4. 21 0
      frameworks/Lisp/ningle/helpers/parse-argv.lisp
  5. 6 0
      frameworks/Lisp/ningle/helpers/starts-with.lisp
  6. 56 0
      frameworks/Lisp/ningle/ningle.dockerfile
  7. 174 0
      frameworks/Lisp/ningle/ningle.ros
  8. 5 0
      frameworks/Lisp/ninglex/README.md
  9. 30 0
      frameworks/Lisp/ninglex/benchmark_config.json
  10. 21 0
      frameworks/Lisp/ninglex/helpers/parse-argv.lisp
  11. 32 0
      frameworks/Lisp/ninglex/helpers/response.lisp
  12. 6 0
      frameworks/Lisp/ninglex/helpers/starts-with.lisp
  13. 61 0
      frameworks/Lisp/ninglex/ninglex.dockerfile
  14. 155 0
      frameworks/Lisp/ninglex/ninglex.ros
  15. 1 2
      frameworks/Lisp/woo/README.md
  16. 2 2
      frameworks/Lisp/woo/benchmark_config.json
  17. 1 2
      frameworks/Lisp/woo/woo.ros
  18. 2 0
      frameworks/Racket/racket/.gitignore
  19. 6 0
      frameworks/Racket/racket/README.md
  20. 30 0
      frameworks/Racket/racket/benchmark_config.json
  21. 13 0
      frameworks/Racket/racket/helpers/response-json.rkt
  22. 54 0
      frameworks/Racket/racket/racket.dockerfile
  23. 140 0
      frameworks/Racket/racket/servlet.rkt
  24. 1 0
      frameworks/Ruby/agoo/.ruby-gemset
  25. 1 0
      frameworks/Ruby/agoo/.ruby-version
  26. 9 0
      frameworks/Ruby/agoo/Gemfile
  27. 21 0
      frameworks/Ruby/agoo/Gemfile.lock
  28. 5 0
      frameworks/Ruby/agoo/README.md
  29. 15 0
      frameworks/Ruby/agoo/agoo.dockerfile
  30. 215 0
      frameworks/Ruby/agoo/app.rb
  31. 25 0
      frameworks/Ruby/agoo/benchmark_config.json

+ 3 - 2
.travis.yml

@@ -93,7 +93,7 @@ env:
     - "TESTDIR=Java/wizzardo-http"
     - "TESTLANG=JavaScript"
     - "TESTLANG=Kotlin"
-    - "TESTDIR=Lisp/woo"
+    - "TESTLANG=Lisp"
     - "TESTLANG=Lua"
     - "TESTLANG=Nim"
     - "TESTLANG=Perl"
@@ -103,7 +103,7 @@ env:
     - 'TESTDIR="Python/aiohttp Python/api_hour Python/blacksheep Python/bottle Python/cherrypy Python/django Python/eve Python/falcon Python/fastapi Python/flask"'
     - 'TESTDIR="Python/hug Python/japronto Python/klein Python/morepath Python/pyramid Python/quart Python/responder Python/sanic Python/spyne Python/starlette"'
     - 'TESTDIR="Python/tornado Python/turbogears Python/uvicorn Python/uwsgi Python/vibora Python/web2py Python/webware Python/weppy Python/wsgi"'
-    - 'TESTDIR="Ruby/grape Ruby/h2o_mruby Ruby/hanami Ruby/padrino Ruby/rack Ruby/rack-sequel"'
+    - 'TESTDIR="Ruby/agoo Ruby/grape Ruby/h2o_mruby Ruby/hanami Ruby/padrino Ruby/rack Ruby/rack-sequel"'
     - 'TESTDIR="Ruby/rails Ruby/roda-sequel Ruby/sinatra Ruby/sinatra-sequel"'
     - 'TESTDIR="Rust/actix Rust/gotham Rust/hyper Rust/iron Rust/saphir"'
     - 'TESTDIR="Rust/may-minihttp Rust/nickel Rust/rocket"'
@@ -111,6 +111,7 @@ env:
     - 'TESTDIR="Scala/akka-http Scala/blaze Scala/cask Scala/colossus Scala/finagle"'
     - 'TESTDIR="Scala/finatra Scala/finch Scala/http4s"'
     - 'TESTDIR="Scala/play2-scala Scala/youi"'
+    - "TESTLANG=Scheme"
     - "TESTLANG=Swift"
     - "TESTLANG=TypeScript"
     - "TESTLANG=Ur"

+ 5 - 0
frameworks/Lisp/ningle/README.md

@@ -0,0 +1,5 @@
+# Ningle
+
+Super micro framework for Common Lisp
+
+https://github.com/fukamachi/ningle

+ 30 - 0
frameworks/Lisp/ningle/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+    "framework": "ningle",
+    "tests": [
+        {
+            "default": {
+                "plaintext_url": "/plaintext",
+                "json_url": "/json",
+                "fortune_url": "/fortunes",
+                "db_url": "/db",
+                "query_url": "/queries?queries=",
+                "update_url": "/updates?queries=",
+                "port": 8080,
+                "approach": "Stripped",
+                "classification": "Micro",
+                "database": "Postgres",
+                "framework": "Ningle",
+                "language": "Common Lisp",
+                "flavor": "None",
+                "orm": "Raw",
+                "platform": "Lisp",
+                "webserver": "Woo",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "Ningle",
+                "notes": "",
+                "versus": ""
+            }
+        }
+    ]
+}

+ 21 - 0
frameworks/Lisp/ningle/helpers/parse-argv.lisp

@@ -0,0 +1,21 @@
+(defun parse-argv (args)
+  (flet ((parse-int-value (option value)
+           (handler-case (parse-integer value)
+             (error (e)
+               (error "Invalid value for ~S: ~S~%  ~A" option value e)))))
+    (loop for option = (pop args)
+          for value = (pop args)
+          while option
+          if (not (starts-with option "--"))
+            do (error "Invalid option: ~S" option)
+          else
+            if (equal option "--address")
+              append (list :address value)
+          else
+            if (equal option "--port")
+              append (list :port (parse-int-value option value))
+          else
+            if (equal option "--worker")
+              append (list :worker-num (parse-int-value option value))
+          else
+            do (error "Unknown option: ~S" option))))

+ 6 - 0
frameworks/Lisp/ningle/helpers/starts-with.lisp

@@ -0,0 +1,6 @@
+(defun starts-with (search-in search-for)
+  "Determine whether `str` starts with `search-for`"
+  (declare (string search-in)
+           (string search-for))
+  (and (<= (length search-for) (length search-in))
+       (string= search-in search-for :end1 (length search-for))))

+ 56 - 0
frameworks/Lisp/ningle/ningle.dockerfile

@@ -0,0 +1,56 @@
+FROM debian:stretch-slim AS debian
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG TERM=linux
+
+RUN echo 'APT::Get::Install-Recommends "false";' > /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Install-Suggests "false";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::force-yes "true";' >> /etc/apt/apt.conf.d/00-general
+
+
+FROM debian AS roswell
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         bzip2 \
+         ca-certificates curl libcurl3-gnutls \
+         make \
+    && rm -rf /var/lib/apt/lists/* \
+    && curl -L -O https://github.com/roswell/roswell/releases/download/v19.4.10.98/roswell_19.4.10.98-1_amd64.deb \
+    && dpkg -i roswell_19.4.10.98-1_amd64.deb \
+    && ros setup \
+    && rm roswell_19.4.10.98-1_amd64.deb
+
+RUN echo 'export PATH=$HOME/.roswell/bin:$PATH' >> ~/.bashrc
+
+
+FROM roswell AS builder
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         build-essential \
+         libev-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /ningle
+ADD  . .
+
+RUN ros build ningle.ros
+
+
+FROM debian
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         libev4 \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /ningle
+COPY --from=builder /ningle/ningle .
+
+RUN ["chmod", "+x", "./ningle"]
+
+EXPOSE 8080
+
+CMD ./ningle --worker $(nproc) --address 0.0.0.0 --port 8080

+ 174 - 0
frameworks/Lisp/ningle/ningle.ros

@@ -0,0 +1,174 @@
+#|-*- mode:lisp -*-|#
+#|
+exec ros -Q -- $0 "$@"
+|#
+
+;; Woo is a fast non-blocking HTTP server built on top of
+;; libev. Although Woo is written in Common Lisp, it aims
+;; to be the fastest web server written in any programming
+;; language.
+
+;; https://github.com/fukamachi/woo
+
+;; Quicklisp is a library manager for Common Lisp. Use
+;; QuickLisp's quickload function to retrieve external
+;; packages. These packages are automatically curl'd when
+;; the program runs.
+
+;; Woo - https://github.com/fukamachi/woo
+;; Ningle - https://github.com/fukamachi/ningle
+;; Jonathan - https://github.com/fukamachi/jonathan
+;; CL-MARKUP - https://github.com/arielnetworks/cl-markup
+;; Postmodern - https://github.com/marijnh/Postmodern
+;; QURI - https://github.com/fukamachi/quri
+
+(ql:quickload '(:cl-markup :jonathan :ningle :postmodern :quri :uiop :woo) :silent t)
+(use-package :ningle)
+
+
+;(declaim (optimize (debug 0) (safety 0) (speed 3)))
+
+
+(load "./helpers/starts-with.lisp")
+(load "./helpers/parse-argv.lisp")
+
+
+;; Initialize the global random state by "some means" (e.g. current time)
+(setf *random-state* (make-random-state t))
+
+
+(defvar *app* (make-instance 'ningle:<app>))
+
+
+(setf (ningle:route *app* "/plaintext")
+  #'(lambda (params)
+    (declare (ignore params))
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "text/plain"
+                        :server "Woo")))
+    "Hello, World!"))
+
+(setf (ningle:route *app* "/json")
+  #'(lambda (params)
+    (declare (ignore params))
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "application/json; charset=utf-8"
+                        :server "Woo")))
+    (jonathan:to-json '(:message "Hello, World!"))))
+
+(defun get-a-random-record (id)
+  (declare (integer id))
+  `(:|id| ,id :|randomNumber| ,(postmodern:query (:select 'randomnumber :from 'world :where (:= 'id id)) :single!)))
+
+(setf (ningle:route *app* "/db")
+  #'(lambda (params)
+    (declare (ignore params))
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "application/json; charset=utf-8"
+                        :server "Woo")))
+    (let ((id (+ 1 (random 10000))))
+      (jonathan:to-json (get-a-random-record id)))))
+
+(defun ensure-integer-is-between-one-and-five-hundreds (n)
+  (declare (integer n))
+  (if (< n 1)
+    (values 1 nil)
+    (if (> n 500)
+      (values 500 nil)
+      (values n t))))
+
+(defun extract-number-of-records-to-fetch (params)
+  (let ((n (handler-case
+            (parse-integer (cdr (assoc "queries" params :test #'equal)))
+            (error (c) (values 1 c)))))
+    (ensure-integer-is-between-one-and-five-hundreds n)))
+
+(defun get-some-random-integers-between-one-and-ten-thousand (n)
+  (declare (integer n))
+  (loop :repeat n
+        :collect (+ 1 (random 10000))))
+
+(defun get-some-random-records (n)
+  (declare (integer n))
+  (let ((ids (get-some-random-integers-between-one-and-ten-thousand n)))
+    (mapcar #'get-a-random-record ids)))
+
+(setf (ningle:route *app* "/queries")
+  #'(lambda (params)
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "application/json; charset=utf-8"
+                        :server "Woo")))
+    (jonathan:to-json (get-some-random-records (extract-number-of-records-to-fetch params)))))
+
+(defun get-all-fortunes ()
+  (postmodern:query (:select 'id 'message :from 'fortune) :rows))
+
+(defun get-all-fortunes-plus-one ()
+  (let* ((records       (get-all-fortunes))
+         (records-p-one (append records '((0 "Additional fortune added at request time.")))))
+    (sort (copy-list records-p-one) #'string-lessp :key #'second)))
+
+(setf (ningle:route *app* "/fortunes")
+  #'(lambda (params)
+    (declare (ignore params))
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "text/html; charset=utf-8"
+                        :server "Woo")))
+    (cl-markup:html5
+      (:head
+        (:title "Fortunes"))
+      (:body
+        (:table
+          (:tr
+            (:th "id")
+            (:th "message"))
+          (loop for fortune-row in (get-all-fortunes-plus-one)
+                collect (cl-markup:markup
+                          (:tr
+                            (:td (format nil "~d" (first fortune-row)))
+                            (:td (second fortune-row))))))))))
+
+(defun get-and-update-some-random-records (n)
+  (declare (integer n))
+  (let* ((random-records (get-some-random-records n))
+         (random-numbers (get-some-random-integers-between-one-and-ten-thousand n))
+         (index -1)
+         (updated-records (map 'list
+                               (lambda (row)
+                                       (incf index)
+                                       (list :|id|           (getf row :|id|          )
+                                             :|randomNumber| (nth index random-numbers)))
+                               random-records))
+         (record-list     (map 'list
+                               (lambda (row)
+                                       (list (nth 1 row)
+                                             (nth 3 row)))
+                               updated-records)))
+    (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))
+    (values updated-records)))
+
+(setf (ningle:route *app* "/updates")
+  #'(lambda (params)
+    (setf (lack.response:response-headers *response*)
+          (append (lack.response:response-headers *response*)
+                  (list :content-type "application/json; charset=utf-8"
+                        :server "Woo")))
+    (jonathan:to-json (get-and-update-some-random-records (extract-number-of-records-to-fetch params)))))
+
+(defun main (&rest argv)
+  "Create and start the server, applying argv to the env"
+  (let ((args (parse-argv argv)))
+    (apply #'woo:run
+      (lambda (env)
+        ;; preprocessing
+        (let ((res (postmodern:with-connection '("hello_world" "benchmarkdbuser" "benchmarkdbpass" "tfb-database")
+                     (ningle.app::call *app* env))))
+          ;; postprocessing
+          res))
+      :debug nil
+      args)))

+ 5 - 0
frameworks/Lisp/ninglex/README.md

@@ -0,0 +1,5 @@
+# Ninglex
+
+Easy to learn, quick and dirty, bare-bones web framework for Common Lisp
+
+https://github.com/defunkydrummer/ninglex

+ 30 - 0
frameworks/Lisp/ninglex/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+    "framework": "ninglex",
+    "tests": [
+        {
+            "default": {
+                "plaintext_url": "/plaintext",
+                "json_url": "/json",
+                "fortune_url": "/fortunes",
+                "db_url": "/db",
+                "query_url": "/queries?queries=",
+                "update_url": "/updates?queries=",
+                "port": 8080,
+                "approach": "Stripped",
+                "classification": "Micro",
+                "database": "Postgres",
+                "framework": "Ninglex",
+                "language": "Common Lisp",
+                "flavor": "None",
+                "orm": "Raw",
+                "platform": "Lisp",
+                "webserver": "Woo",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "Ninglex",
+                "notes": "",
+                "versus": ""
+            }
+        }
+    ]
+}

+ 21 - 0
frameworks/Lisp/ninglex/helpers/parse-argv.lisp

@@ -0,0 +1,21 @@
+(defun parse-argv (args)
+  (flet ((parse-int-value (option value)
+           (handler-case (parse-integer value)
+             (error (e)
+               (error "Invalid value for ~S: ~S~%  ~A" option value e)))))
+    (loop for option = (pop args)
+          for value = (pop args)
+          while option
+          if (not (starts-with option "--"))
+            do (error "Invalid option: ~S" option)
+          else
+            if (equal option "--address")
+              append (list :address value)
+          else
+            if (equal option "--port")
+              append (list :port (parse-int-value option value))
+          else
+            if (equal option "--worker")
+              append (list :worker-num (parse-int-value option value))
+          else
+            do (error "Unknown option: ~S" option))))

+ 32 - 0
frameworks/Lisp/ninglex/helpers/response.lisp

@@ -0,0 +1,32 @@
+(defun merge-plist (p1 p2)
+  (loop with notfound = '#:notfound
+        for (indicator value) on p1 by #'cddr
+        when (eq (getf p2 indicator notfound) notfound)
+        do (progn
+             (push value p2)
+             (push indicator p2)))
+  p2)
+
+(defun html-response* (response &optional headers)
+  "This hould be a docstring"
+  `(
+     200
+     ,(merge-plist '(:content-type "text/html; charset=utf-8" :server "Woo") headers)
+     (,response)
+   ))
+
+(defun json-response* (response &optional headers)
+  "This hould be a docstring"
+  `(
+     200
+     ,(merge-plist '(:content-type "application/json; charset=utf-8" :server "Woo") headers)
+     (,response)
+   ))
+
+(defun plain-response* (response &optional headers)
+  "This hould be a docstring"
+  `(
+     200
+     ,(merge-plist '(:content-type "text/plain; charset=utf-8" :server "Woo") headers)
+     (,response)
+   ))

+ 6 - 0
frameworks/Lisp/ninglex/helpers/starts-with.lisp

@@ -0,0 +1,6 @@
+(defun starts-with (search-in search-for)
+  "Determine whether `str` starts with `search-for`"
+  (declare (string search-in)
+           (string search-for))
+  (and (<= (length search-for) (length search-in))
+       (string= search-in search-for :end1 (length search-for))))

+ 61 - 0
frameworks/Lisp/ninglex/ninglex.dockerfile

@@ -0,0 +1,61 @@
+FROM debian:stretch-slim AS debian
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG TERM=linux
+
+RUN echo 'APT::Get::Install-Recommends "false";' > /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Install-Suggests "false";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::force-yes "true";' >> /etc/apt/apt.conf.d/00-general
+
+
+FROM debian AS roswell
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         bzip2 \
+         ca-certificates curl libcurl3-gnutls \
+         make \
+    && rm -rf /var/lib/apt/lists/* \
+    && curl -L -O https://github.com/roswell/roswell/releases/download/v19.4.10.98/roswell_19.4.10.98-1_amd64.deb \
+    && dpkg -i roswell_19.4.10.98-1_amd64.deb \
+    && ros setup \
+    && rm roswell_19.4.10.98-1_amd64.deb
+
+RUN echo 'export PATH=$HOME/.roswell/bin:$PATH' >> ~/.bashrc
+
+
+FROM roswell AS builder
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         build-essential \
+         libev-dev \
+         git-core \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN mkdir -p ${HOME}/common-lisp/ \
+    && cd ${HOME}/common-lisp/ \
+    && git clone https://github.com/defunkydrummer/ninglex.git
+
+WORKDIR /ninglex
+ADD  . .
+
+RUN ros build ninglex.ros
+
+
+FROM debian
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         libev4 \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /ninglex
+COPY --from=builder /ninglex/ninglex .
+
+RUN ["chmod", "+x", "./ninglex"]
+
+EXPOSE 8080
+
+CMD ./ninglex --worker $(nproc) --address 0.0.0.0 --port 8080

+ 155 - 0
frameworks/Lisp/ninglex/ninglex.ros

@@ -0,0 +1,155 @@
+#|-*- mode:lisp -*-|#
+#|
+exec ros -Q -- $0 "$@"
+|#
+
+;; Woo is a fast non-blocking HTTP server built on top of
+;; libev. Although Woo is written in Common Lisp, it aims
+;; to be the fastest web server written in any programming
+;; language.
+
+;; https://github.com/fukamachi/woo
+
+;; Quicklisp is a library manager for Common Lisp. Use
+;; QuickLisp's quickload function to retrieve external
+;; packages. These packages are automatically curl'd when
+;; the program runs.
+
+;; Woo - https://github.com/fukamachi/woo
+;; Ninglex - https://github.com/defunkydrummer/ninglex
+;; Jonathan - https://github.com/fukamachi/jonathan
+;; CL-MARKUP - https://github.com/arielnetworks/cl-markup
+;; Postmodern - https://github.com/marijnh/Postmodern
+;; QURI - https://github.com/fukamachi/quri
+
+(ql:quickload '(:cl-markup :jonathan :ninglex :postmodern :quri :uiop :woo) :silent t)
+(use-package :ninglex)
+
+
+(declaim (optimize (debug 0) (safety 0) (speed 3)))
+
+
+(load "./helpers/starts-with.lisp")
+(load "./helpers/parse-argv.lisp")
+
+; Ninglex's original `html-response`, `json-response` and `string-response`
+; are very simplistic and unflexible regarding headers
+(load "./helpers/response.lisp")
+
+
+;; Initialize the global random state by "some means" (e.g. current time)
+(setf *random-state* (make-random-state t))
+
+
+(defvar *app* (make-instance 'ningle:<app>))
+
+
+(with-route ("/plaintext" params)
+  (declare (ignore params))
+  (plain-response* "Hello, World!"))
+
+(with-route ("/json" params)
+  (declare (ignore params))
+  (json-response*
+    (jonathan:to-json '(:message "Hello, World!"))))
+
+(defun get-a-random-record (id)
+  (declare (integer id))
+  `(:|id| ,id :|randomNumber| ,(postmodern:query (:select 'randomnumber :from 'world :where (:= 'id id)) :single!)))
+
+(with-route ("/db" params)
+  (declare (ignore params))
+  (let ((id (+ 1 (random 10000))))
+    (json-response*
+      (jonathan:to-json (get-a-random-record id)))))
+
+(defun ensure-integer-is-between-one-and-five-hundreds (n)
+  (declare (integer n))
+  (if (< n 1)
+    (values 1 nil)
+    (if (> n 500)
+      (values 500 nil)
+      (values n t))))
+
+(defun extract-number-of-records-to-fetch (n)
+  (let ((n (handler-case
+             (parse-integer n)
+             (error (c) (values 1 c)))))
+    (ensure-integer-is-between-one-and-five-hundreds n)))
+
+(defun get-some-random-integers-between-one-and-ten-thousand (n)
+  (declare (integer n))
+  (loop :repeat n
+        :collect (+ 1 (random 10000))))
+
+(defun get-some-random-records (n)
+  (declare (integer n))
+  (let ((ids (get-some-random-integers-between-one-and-ten-thousand n)))
+    (mapcar #'get-a-random-record ids)))
+
+(with-route ("/queries" params)
+  (with-request-params params ((n "queries"))
+    (json-response*
+      (jonathan:to-json (get-some-random-records (extract-number-of-records-to-fetch n))))))
+
+(defun get-all-fortunes ()
+  (postmodern:query (:select 'id 'message :from 'fortune) :rows))
+
+(defun get-all-fortunes-plus-one ()
+  (let* ((records       (get-all-fortunes))
+         (records-p-one (append records '((0 "Additional fortune added at request time.")))))
+    (sort (copy-list records-p-one) #'string-lessp :key #'second)))
+
+(with-route ("/fortunes" params)
+  (declare (ignore params))
+  (html-response*
+    (cl-markup:html5
+      (:head
+        (:title "Fortunes"))
+      (:body
+        (:table
+          (:tr
+            (:th "id")
+            (:th "message"))
+          (loop for fortune-row in (get-all-fortunes-plus-one)
+                collect (cl-markup:markup
+                          (:tr
+                            (:td (format nil "~d" (first fortune-row)))
+                            (:td (second fortune-row))))))))))
+
+(defun get-and-update-some-random-records (n)
+  (declare (integer n))
+  (let* ((random-records (get-some-random-records n))
+         (random-numbers (get-some-random-integers-between-one-and-ten-thousand n))
+         (index -1)
+         (updated-records (map 'list
+                               (lambda (row)
+                                       (incf index)
+                                       (list :|id|           (getf row :|id|          )
+                                             :|randomNumber| (nth index random-numbers)))
+                               random-records))
+         (record-list     (map 'list
+                               (lambda (row)
+                                       (list (nth 1 row)
+                                             (nth 3 row)))
+                               updated-records)))
+    (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))
+    (values updated-records)))
+
+(with-route ("/updates" params)
+  (with-request-params params ((n "queries"))
+    (json-response*
+      (jonathan:to-json (get-and-update-some-random-records (extract-number-of-records-to-fetch n))))))
+
+(defun main (&rest argv)
+  "Create and start the server, applying argv to the env"
+  (let ((args (parse-argv argv)))
+    (apply #'woo:run
+      (lambda (env)
+        ;; preprocessing
+        (let ((res (postmodern:with-connection '("hello_world" "benchmarkdbuser" "benchmarkdbpass" "tfb-database")
+                     (ningle.app::call *app* env))))
+          ;; postprocessing
+          res))
+      :debug nil
+      args)))

+ 1 - 2
frameworks/Lisp/woo/README.md

@@ -1,6 +1,5 @@
 # Woo
 
-Woo is a fast non-blocking HTTP server built on top of
-libev.
+A fast non-blocking HTTP server on top of libev
 
 https://github.com/fukamachi/woo

+ 2 - 2
frameworks/Lisp/woo/benchmark_config.json

@@ -10,11 +10,11 @@
                 "query_url": "/queries?queries=",
                 "update_url": "/updates?queries=",
                 "port": 8080,
-                "approach": "Realistic",
+                "approach": "Stripped",
                 "classification": "Micro",
                 "database": "Postgres",
                 "framework": "Woo",
-                "language": "Lisp",
+                "language": "Common Lisp",
                 "flavor": "None",
                 "orm": "Raw",
                 "platform": "Lisp",

+ 1 - 2
frameworks/Lisp/woo/woo.ros

@@ -16,7 +16,6 @@ exec ros -Q -- $0 "$@"
 ;; the program runs.
 
 ;; Woo - https://github.com/fukamachi/woo
-;; Clack - https://github.com/fukamachi/clack
 ;; Jonathan - https://github.com/fukamachi/jonathan
 ;; CL-MARKUP - https://github.com/arielnetworks/cl-markup
 ;; Postmodern - https://github.com/marijnh/Postmodern
@@ -145,7 +144,7 @@ exec ros -Q -- $0 "$@"
    ))
 
 (defun handler (env)
-  "Woo router"
+  "Router"
   (let ((path (getf env :path-info)))
     (cond ((starts-with path "/plaintext") (funcall 'plaintext  ))
           ((starts-with path "/json"     ) (funcall 'json       ))

+ 2 - 0
frameworks/Racket/racket/.gitignore

@@ -0,0 +1,2 @@
+*.dep
+*.zo

+ 6 - 0
frameworks/Racket/racket/README.md

@@ -0,0 +1,6 @@
+# Racket
+
+Racket includes a web framework and server.
+
+https://docs.racket-lang.org/web-server/
+https://docs.racket-lang.org/web-server/stateless.html

+ 30 - 0
frameworks/Racket/racket/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+    "framework": "racket",
+    "tests": [
+        {
+            "default": {
+                "plaintext_url": "/plaintext",
+                "json_url": "/json",
+                "fortune_url": "/fortunes",
+                "db_url": "/db",
+                "query_url": "/queries?queries=",
+                "update_url": "/updates?queries=",
+                "port": 8080,
+                "approach": "Stripped",
+                "classification": "Micro",
+                "database": "Postgres",
+                "framework": "Racket",
+                "language": "Racket",
+                "flavor": "None",
+                "orm": "Raw",
+                "platform": "Racket",
+                "webserver": "Racket",
+                "os": "Linux",
+                "database_os": "Linux",
+                "display_name": "Racket",
+                "notes": "",
+                "versus": ""
+            }
+        }
+    ]
+}

+ 13 - 0
frameworks/Racket/racket/helpers/response-json.rkt

@@ -0,0 +1,13 @@
+#lang racket/base
+
+(require json
+         racket/list
+         web-server/http)
+
+(define (response/json output)
+  (response
+    200 #"Okay" (current-seconds) #"application/json; charset=utf-8" empty
+    (λ (out)
+      (write-bytes (jsexpr->bytes output) out))))
+
+(provide (all-defined-out))

+ 54 - 0
frameworks/Racket/racket/racket.dockerfile

@@ -0,0 +1,54 @@
+FROM debian:stretch-slim AS debian
+
+ARG DEBIAN_FRONTEND=noninteractive
+ARG TERM=linux
+
+RUN echo 'APT::Get::Install-Recommends "false";' > /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Install-Suggests "false";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/00-general \
+    && echo 'APT::Get::force-yes "true";' >> /etc/apt/apt.conf.d/00-general
+
+
+FROM debian AS racket
+
+ARG RACKET_VERSION=7.2
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         ca-certificates curl libcurl3-gnutls \
+    && rm -rf /var/lib/apt/lists/* \
+    && curl -L -o racket-install.sh \
+         -O http://mirror.racket-lang.org/installers/${RACKET_VERSION}/racket-minimal-${RACKET_VERSION}-x86_64-linux-natipkg.sh \
+    && echo "yes\n1\n" | sh racket-install.sh --create-dir --unix-style --dest /usr/ \
+    && rm racket-install.sh
+
+ENV SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"
+ENV SSL_CERT_DIR="/etc/ssl/certs"
+
+RUN raco setup
+RUN raco pkg config --set catalogs \
+    "https://download.racket-lang.org/releases/${RACKET_VERSION}/catalog/"
+
+
+FROM racket AS builder
+
+WORKDIR /racket
+ADD  . .
+
+RUN raco pkg install --auto compiler-lib
+
+RUN raco pkg install --auto db
+
+RUN raco exe servlet.rkt
+
+
+FROM racket
+
+WORKDIR /racket
+COPY --from=builder /racket/servlet .
+
+RUN ["chmod", "+x", "./servlet"]
+
+EXPOSE 8080
+
+CMD ./servlet

+ 140 - 0
frameworks/Racket/racket/servlet.rkt

@@ -0,0 +1,140 @@
+#lang racket/base
+
+(require db
+         racket/list
+         racket/string
+         net/url-structs
+         web-server/dispatch
+         web-server/http
+         web-server/servlet-env
+         "./helpers/response-json.rkt")
+
+(define (plaintext req)
+  (response
+    200 #"OK" (current-seconds) #"text/plain" empty
+    (λ (out)
+      (write-bytes #"Hello, World!" out))))
+
+(define (json req)
+  (response/json (hash 'message "Hello, World!")))
+
+(define (get-a-random-number)
+  (+ 1 (random 10000)))
+
+(define pgc
+  (virtual-connection
+    (λ ()
+      (postgresql-connect #:database "hello_world"
+                          #:user "benchmarkdbuser"
+                          #:password "benchmarkdbpass"
+                          #:server "tfb-database"))))
+
+(define (get-a-random-record id)
+  (hash 'id id 'randomNumber (query-maybe-value pgc "select randomnumber from world where id = $1" id)))
+
+(define (db req)
+  (response/json (get-a-random-record (get-a-random-number))))
+
+(define (ensure-integer-is-between-one-and-five-hundreds n)
+  (if (number? n)
+      (if (< n 1)
+        1
+        (if (> n 500)
+          500
+          n))
+      1))
+
+(define (extract-number-of-records-to-fetch req)
+  (let* ([uri            (request-uri req)]
+         [all-parameters (url-query uri)]
+         [queries-param  (assoc 'queries all-parameters)]
+         [queries        (cdr queries-param)])
+    (ensure-integer-is-between-one-and-five-hundreds (string->number queries))))
+
+(define (get-some-random-integers-between-one-and-ten-thousand n)
+  (for/list ((i n))
+    (add1 (get-a-random-number))))
+
+(define (get-some-random-records n)
+  (let ([ids (get-some-random-integers-between-one-and-ten-thousand n)])
+    (map (λ (id) (get-a-random-record id)) ids)))
+
+(define (queries req)
+  (response/json (get-some-random-records (extract-number-of-records-to-fetch req))))
+
+(define (get-all-fortunes)
+  (query-rows pgc "select id, message from fortune"))
+
+(define (get-all-fortunes-plus-one)
+  (let* ([records       (get-all-fortunes)]
+         [records-p-one (append records '(#(0 "Additional fortune added at request time.")))])
+    (sort records-p-one string<? #:key (λ (e)
+                                         (vector-ref e 1)))))
+
+(define (fortunes req)
+  (response/xexpr
+    #:preamble #"<!DOCTYPE html>"
+    `(html
+       (head
+         (title "Fortunes"))
+       (body
+         (table
+           (tr
+             (th "id")
+             (th "message"))
+           ,@(for/list ([fortune-row (get-all-fortunes-plus-one)])
+               `(tr
+                  (td ,(format "~v" (vector-ref fortune-row 0)))
+                  (td              ,(vector-ref fortune-row 1))))
+           )))))
+
+(define (get-and-update-some-random-records n)
+  (let* ([random-records (get-some-random-records n)]
+         [random-numbers (get-some-random-integers-between-one-and-ten-thousand n)]
+         [index -1]
+         [updated-records (map (λ (row)
+                                 (set! index (add1 index))
+                                 (hash 'id           (hash-ref row 'id)
+                                       'randomNumber (list-ref random-numbers index)))
+                               random-records)]
+         [record-list     (map (λ (row)
+                                 (list (hash-ref row 'id)
+                                       (hash-ref row 'randomNumber)))
+                               updated-records)]
+         [sql-values      (string-join
+                             (map (λ (rec)
+                                    (format "(~a, ~a)" (car rec) (car (cdr rec))))
+                                  record-list)
+                             ", ")]
+         [sql-stmt        (string-join
+                             `("UPDATE world AS ori SET randomnumber = new.randomnumber FROM (VALUES "
+                               ,sql-values
+                               ") AS new (id, randomnumber) WHERE ori.id = new.id")
+                             "")])
+    (query-exec pgc sql-stmt)
+    updated-records))
+
+(define (updates req)
+  (response/json (get-and-update-some-random-records (extract-number-of-records-to-fetch req))))
+
+(define-values (tfb-dispatch tfb-url)
+  (dispatch-rules
+    [("plaintext") #:method "get" plaintext]
+    [("json")      #:method "get" json]
+    [("db")        #:method "get" db]
+    [("queries")   #:method "get" queries]
+    [("fortunes")  #:method "get" fortunes]
+    [("updates")   #:method "get" updates]
+    [else plaintext]))
+
+(define (start request)
+  (tfb-dispatch request))
+
+(serve/servlet start
+               #:command-line? #t
+               #:launch-browser? #f
+               #:listen-ip #f
+               #:port 8080
+               #:servlet-path "/"
+               #:servlet-regexp #rx""
+               #:stateless? #t)

+ 1 - 0
frameworks/Ruby/agoo/.ruby-gemset

@@ -0,0 +1 @@
+agoo

+ 1 - 0
frameworks/Ruby/agoo/.ruby-version

@@ -0,0 +1 @@
+ruby-2.6.3

+ 9 - 0
frameworks/Ruby/agoo/Gemfile

@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+gem 'agoo'
+gem 'connection_pool'
+gem 'oj'
+gem 'pg'
+gem 'rack'

+ 21 - 0
frameworks/Ruby/agoo/Gemfile.lock

@@ -0,0 +1,21 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    agoo (2.8.3)
+    connection_pool (2.2.2)
+    oj (3.7.12)
+    pg (1.1.4)
+    rack (2.0.7)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  agoo
+  connection_pool
+  oj
+  pg
+  rack
+
+BUNDLED WITH
+   1.17.2

+ 5 - 0
frameworks/Ruby/agoo/README.md

@@ -0,0 +1,5 @@
+# Agoo
+
+A High Performance HTTP Server for Ruby
+
+https://github.com/ohler55/agoo

+ 15 - 0
frameworks/Ruby/agoo/agoo.dockerfile

@@ -0,0 +1,15 @@
+FROM ruby:2.6.3-slim-stretch
+
+RUN apt-get update -q \
+    && apt-get install --no-install-recommends -q -y \
+         build-essential \
+         libpq-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /rack
+
+COPY Gemfile app.rb ./
+
+RUN bundle install --jobs=4
+
+CMD AGOO_WORKER_COUNT=$(nproc) ruby app.rb

+ 215 - 0
frameworks/Ruby/agoo/app.rb

@@ -0,0 +1,215 @@
+# frozen_string_literal: true
+
+require 'agoo'
+require 'connection_pool'
+require 'oj'
+require 'pg'
+require 'rack'
+
+$pool = ConnectionPool.new(size: 256, timeout: 5) do
+          PG::Connection.new({
+                                 dbname:   'hello_world',
+                                 host:     'tfb-database',
+                                 user:     'benchmarkdbuser',
+                                 password: 'benchmarkdbpass'
+                             })
+        end
+
+class BaseHandler
+  def self.extract_queries_param(request = nil)
+    queries = Rack::Utils.parse_query(request['QUERY_STRING'])['queries'].to_i rescue 1
+
+    return   1 if queries <   1
+    return 500 if queries > 500
+
+    queries
+  end
+
+  def self.get_one_random_number
+    1 + Random.rand(10000)
+  end
+
+  def self.get_one_record(id = get_one_random_number)
+    $pool.with do |conn|
+      conn.exec_params(<<-SQL, [id]).first
+
+        SELECT * FROM world WHERE id = $1
+
+      SQL
+    end
+  end
+
+  def self.html_response(str = '')
+    [
+        200,
+        {
+            'Content-Type' => 'text/html; charset=utf-8',
+            'Date'         => Time.now.utc.httpdate,
+            'Server'       => 'Agoo'
+        },
+        [str]
+    ]
+  end
+
+  def self.json_response(obj = {})
+    [
+        200,
+        {
+            'Content-Type' => 'application/json',
+            'Date'         => Time.now.utc.httpdate,
+            'Server'       => 'Agoo'
+        },
+        [Oj.dump(obj, { :mode => :strict })]
+    ]
+  end
+
+  def self.plain_response(str = '')
+    [
+        200,
+        {
+            'Content-Type' => 'text/plain',
+            'Date'         => Time.now.utc.httpdate,
+            'Server'       => 'Agoo'
+        },
+        [str]
+    ]
+  end
+end
+
+class PlaintextHandler < BaseHandler
+  def self.call(_req)
+    plain_response('Hello, World!')
+  end
+
+  def static?
+    true
+  end
+end
+
+class JsonHandler < BaseHandler
+  def self.call(_req)
+    json_response({ :message => "Hello, World!" })
+  end
+
+  def static?
+    true
+  end
+end
+
+class DbHandler < BaseHandler
+  def self.call(_req)
+    json_response(get_one_record)
+  end
+end
+
+class FortunesHandler < BaseHandler
+  def self.call(_req)
+    f_1 = $pool.with do |conn|
+            conn.exec(<<-SQL)
+
+              SELECT id, message FROM fortune
+
+            SQL
+          end
+
+    f_2 = f_1.map(&:to_h).
+            append({ 'id' => '0', 'message' => 'Additional fortune added at request time.' }).
+              sort { |x,y| x['message'] <=> y['message'] }.
+                map { |f| "<tr><td>#{ f['id'] }</td><td>#{ Rack::Utils.escape_html(f['message']) }</td></tr>" }.
+                  join
+
+    html_response(<<-HTML)
+      <!DOCTYPE html>
+      <html>
+        <head>
+          <title>Fortune</title>
+        </head>
+        <body>
+          <table>
+            <tr>
+              <th>id</th>
+              <th>message</th>
+            </tr>
+            #{ f_2 }
+          </table>
+        </body
+      </html>
+    HTML
+  end
+end
+
+class QueriesHandler < BaseHandler
+  def self.call(req)
+    records =
+        [].tap do|r|
+          (extract_queries_param req).times do
+            r << get_one_record()
+          end
+        end
+
+    json_response(records)
+  end
+end
+
+class UpdatesHandler < BaseHandler
+  def self.call(req)
+    records =
+        [].tap do|r|
+          (extract_queries_param req).times do
+            r << get_one_record()
+          end
+        end
+
+    updated_records =
+        records.map { |r| r['randomnumber'] = get_one_random_number; r }
+
+    sql_values =
+        updated_records.
+          map { |r| "(#{ r['id'] }, #{ r['randomnumber'] })"}.
+            join(', ')
+
+    $pool.with do |conn|
+      conn.exec(<<-SQL)
+
+        UPDATE world AS ori
+           SET randomnumber = new.randomnumber
+          FROM (VALUES #{ sql_values }) AS new (id, randomnumber)
+         WHERE ori.id = new.id
+
+      SQL
+    end
+
+    json_response(updated_records)
+  end
+end
+
+Agoo::Log.configure({
+                        classic:  true,
+                        colorize: true,
+                        console:  true,
+                        dir:      '',
+                        states:   {
+                            DEBUG:    false,
+                            INFO:     false,
+
+                            connect:  false,
+                            eval:     false,
+                            push:     false,
+                            request:  false,
+                            response: false
+                        }
+                    })
+
+worker_count = 4
+worker_count = ENV['AGOO_WORKER_COUNT'].to_i if ENV.key?('AGOO_WORKER_COUNT')
+
+Agoo::Server.init(8080, '.', thread_count: 0, worker_count: worker_count)
+
+Agoo::Server.handle(:GET, '/plaintext', PlaintextHandler)
+Agoo::Server.handle(:GET, '/json',      JsonHandler)
+Agoo::Server.handle(:GET, '/db',        DbHandler)
+Agoo::Server.handle(:GET, '/fortunes',  FortunesHandler)
+Agoo::Server.handle(:GET, '/queries',   QueriesHandler)
+Agoo::Server.handle(:GET, '/updates',   UpdatesHandler)
+
+Agoo::Server.start

+ 25 - 0
frameworks/Ruby/agoo/benchmark_config.json

@@ -0,0 +1,25 @@
+{
+  "framework": "agoo",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Stripped",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "rack",
+      "language": "Ruby",
+      "orm": "Raw",
+      "platform": "Rack",
+      "webserver": "Agoo",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "rack-agoo-mri",
+      "notes": ""
+    }
+  }]
+}