浏览代码

Add majavat templating engine (#10158)

Only clojure ring-http-exchange and kit are impacted

Co-authored-by: jj <[email protected]>
ruroru 1 周之前
父节点
当前提交
e3ea8e693b

+ 18 - 0
frameworks/Clojure/kit/benchmark_config.json

@@ -25,6 +25,24 @@
         "display_name": "Kit",
         "notes": "",
         "versus": "None"
+      },
+      "majavat": {
+        "fortune_url": "/majavat-fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "postgres",
+        "framework": "None",
+        "language": "Clojure",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "kit-majavat",
+        "notes": "",
+        "versus": "kit"
       }
     }
   ]

+ 13 - 11
frameworks/Clojure/kit/deps.edn

@@ -1,29 +1,31 @@
 {:paths   ["src/clj"
            "resources"]
 
- :deps    {org.clojure/clojure              {:mvn/version "1.11.1"}
+ :deps    {org.clojure/clojure              {:mvn/version "1.12.3"}
 
            ;; Routing
-           metosin/reitit                   {:mvn/version "0.5.18"}
+           metosin/reitit                   {:mvn/version "0.9.1"}
 
            ;; Ring
-           metosin/ring-http-response       {:mvn/version "0.9.3"}
-           ring/ring-core                   {:mvn/version "1.9.5"}
+           metosin/ring-http-response       {:mvn/version "0.9.5"}
+           ring/ring-core                   {:mvn/version "1.15.3"}
 
            ;; Data coercion
-           metosin/muuntaja                 {:mvn/version "0.6.8"}
+           metosin/muuntaja                 {:mvn/version "0.6.11"}
 
            ;; HTML templating
-           selmer/selmer                    {:mvn/version "1.12.55"}
+           selmer/selmer                    {:mvn/version "1.12.62"}
+           org.clojars.jj/majavat           {:mvn/version "1.12.1"}
 
            ;; Database
-           org.postgresql/postgresql        {:mvn/version "42.5.1"}
+           org.postgresql/postgresql        {:mvn/version "42.7.8"}
+
 
            ;; kit Libs
-           io.github.kit-clj/kit-core       {:mvn/version "1.0.3"}
-           io.github.kit-clj/kit-undertow   {:mvn/version "1.0.4"}
-           io.github.kit-clj/kit-sql-hikari {:mvn/version "1.0.2"}
-           org.clojure/core.cache           {:mvn/version "1.0.225"}
+           io.github.kit-clj/kit-core       {:mvn/version "1.0.9"}
+           io.github.kit-clj/kit-undertow   {:mvn/version "1.0.9"}
+           io.github.kit-clj/kit-sql-hikari {:mvn/version "1.0.6"}
+           org.clojure/core.cache           {:mvn/version "1.1.234"}
 
            }
 

+ 19 - 0
frameworks/Clojure/kit/kit-majavat.dockerfile

@@ -0,0 +1,19 @@
+# syntax = docker/dockerfile:1.2
+FROM clojure:openjdk-17 AS build
+
+WORKDIR /
+COPY . /
+
+RUN clj -Sforce -T:build all
+
+FROM azul/zulu-openjdk-alpine:17
+
+COPY --from=build /target/te-bench-standalone.jar /te-bench/te-bench-standalone.jar
+
+EXPOSE 8080
+
+ENV PORT=8080
+ENV JAVA_OPTS="-XX:+UseContainerSupport -Dfile.encoding=UTF-8"
+ENV JDBC_URL="jdbc:postgresql://tfb-database/hello_world?user=benchmarkdbuser&password=benchmarkdbpass"
+
+ENTRYPOINT exec java $JAVA_OPTS -jar /te-bench/te-bench-standalone.jar

+ 4 - 5
frameworks/Clojure/kit/src/clj/io/github/kit_clj/te_bench/core.clj

@@ -17,11 +17,10 @@
 
 ;; log uncaught exceptions in threads
 (Thread/setDefaultUncaughtExceptionHandler
-  (reify Thread$UncaughtExceptionHandler
-    (uncaughtException [_ thread ex]
-      (log/error {:what :uncaught-exception
-                  :exception ex
-                  :where (str "Uncaught exception on" (.getName thread))}))))
+  (fn [thread ex]
+    (log/error {:what      :uncaught-exception
+                :exception ex
+                :where     (str "Uncaught exception on" (.getName thread))})))
 
 (defonce system (atom nil))
 

+ 25 - 6
frameworks/Clojure/kit/src/clj/io/github/kit_clj/te_bench/web/controllers/bench.clj

@@ -3,6 +3,9 @@
     [clojure.core.cache :as cache]
     [next.jdbc :as jdbc]
     [next.jdbc.result-set :as rs]
+    [jj.majavat :as majavat]
+    [jj.majavat.renderer :refer [->InputStreamRenderer]]
+    [jj.majavat.renderer.sanitizer :refer [->Html]]
     [ring.util.http-response :as http-response]
     [selmer.parser :as parser]))
 
@@ -13,15 +16,24 @@
 (def ^:const HELLO_WORLD "Hello, World!")
 (def ^:const MAX_ID_ZERO_IDX 9999)
 (def ^:const CACHE_TTL (* 24 60 60))
-
+(def ^:private render-fortune (majavat/build-renderer "html/fortunes.html"
+                                                      {:renderer (->InputStreamRenderer
+                                                                   {:sanitizer (->Html)})}))
 (def selmer-opts {:custom-resource-path (clojure.java.io/resource "html")})
 
-(defn html-response
+(defn selmer-html-response
   [template & [params]]
   (-> (parser/render-file template params selmer-opts)
       (http-response/ok)
       (http-response/content-type "text/html; charset=utf-8")))
 
+(defn majavat-html-response
+  [context]
+  (-> (render-fortune context)
+      (http-response/ok)
+      (http-response/content-type "text/html; charset=utf-8")))
+
+
 (defn rand-id
   [n]
   (inc (rand-int n)))
@@ -31,7 +43,7 @@
   "Parse provided string value of query count, clamping values to between 1 and 500."
   [^String queries]
   (let [n (try (Integer/parseInt queries)
-               (catch Exception _ 1))]                ; default to 1 on parse failure
+               (catch Exception _ 1))]                      ; default to 1 on parse failure
     (cond
       (< n 1) 1
       (> n 500) 500
@@ -101,7 +113,7 @@
 
 (defn update-db-handler
   [db-conn request]
-  (let [items   (db-multi-query-world! db-conn request)]
+  (let [items (db-multi-query-world! db-conn request)]
     (http-response/ok
       (mapv
         (fn [{:keys [id]}]
@@ -122,9 +134,16 @@
       []
       (range-from-req request))))
 
-(defn fortune-handler
+(defn selmer-fortune-handler
+  [db-conn _request]
+  (as-> (jdbc/execute! db-conn ["select * from \"Fortune\";"] jdbc-opts) fortunes
+        (conj fortunes {:id 0 :message "Additional fortune added at request time."})
+        (sort-by :message fortunes)
+        (selmer-html-response "fortunes.html" {:messages fortunes})))
+
+(defn majavat-fortune-handler
   [db-conn _request]
   (as-> (jdbc/execute! db-conn ["select * from \"Fortune\";"] jdbc-opts) fortunes
         (conj fortunes {:id 0 :message "Additional fortune added at request time."})
         (sort-by :message fortunes)
-        (html-response "fortunes.html" {:messages fortunes})))
+        (majavat-html-response {:messages fortunes})))

+ 3 - 5
frameworks/Clojure/kit/src/clj/io/github/kit_clj/te_bench/web/routes/bench.clj

@@ -17,7 +17,8 @@
    ["/queries" {:get (partial bench/multi-db-handler db-conn)}]
    ["/updates" {:get (partial bench/update-db-handler db-conn)}]
    ["/cached-queries" {:get (partial bench/cached-query-handler db-conn cache)}]
-   ["/fortunes" {:get (partial bench/fortune-handler db-conn)}]])
+   ["/fortunes" {:get (partial bench/selmer-fortune-handler db-conn)}]
+   ["/majavat-fortunes" {:get (partial bench/majavat-fortune-handler db-conn)}]])
 
 (defmethod ig/init-key :reitit.routes/bench
   [_ {:keys [base-path]
@@ -25,10 +26,7 @@
       :as   opts}]
   [base-path
    {:muuntaja   formats/instance
-    :middleware [;; query-params & form-params
-                 parameters/parameters-middleware
-                 ;; encoding response body
+    :middleware [parameters/parameters-middleware
                  muuntaja/format-response-middleware
-                 ;; default header middleware
                  default-headers/default-headers-middleware]}
    (bench-routes opts)])

+ 12 - 8
frameworks/Clojure/ring-http-exchange/benchmark_config.json

@@ -5,14 +5,15 @@
       "default": {
         "json_url": "/json",
         "plaintext_url": "/plaintext",
+        "fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Platform",
-        "database": "None",
+        "database": "postgres",
         "framework": "None",
         "language": "Clojure",
         "flavor": "None",
-        "orm": "None",
+        "orm": "Raw",
         "platform": "None",
         "webserver": "None",
         "os": "Linux",
@@ -24,14 +25,15 @@
       "robaho": {
         "json_url": "/json",
         "plaintext_url": "/plaintext",
+        "fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Platform",
-        "database": "None",
+        "database": "postgres",
         "framework": "None",
         "language": "Clojure",
         "flavor": "None",
-        "orm": "None",
+        "orm": "Raw",
         "platform": "None",
         "webserver": "None",
         "os": "Linux",
@@ -43,14 +45,15 @@
       "graalvm": {
         "json_url": "/json",
         "plaintext_url": "/plaintext",
+        "fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Platform",
-        "database": "None",
+        "database": "postgres",
         "framework": "None",
         "language": "Clojure",
         "flavor": "None",
-        "orm": "None",
+        "orm": "Raw",
         "platform": "None",
         "webserver": "None",
         "os": "Linux",
@@ -62,14 +65,15 @@
       "robaho-graalvm": {
         "json_url": "/json",
         "plaintext_url": "/plaintext",
+        "fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Platform",
-        "database": "None",
+        "database": "postgres",
         "framework": "None",
         "language": "Clojure",
         "flavor": "None",
-        "orm": "None",
+        "orm": "Raw",
         "platform": "None",
         "webserver": "None",
         "os": "Linux",

+ 8 - 4
frameworks/Clojure/ring-http-exchange/config.toml

@@ -4,9 +4,10 @@ name = "ring-http-exchange"
 [main]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Platform"
-database = "None"
+database = "postgres"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"
@@ -18,9 +19,10 @@ versus = "httpserver"
 [graalvm]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Platform"
-database = "None"
+database = "postgres"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"
@@ -31,9 +33,10 @@ versus = "httpserver-graalvm"
 [robaho]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Platform"
-database = "None"
+database = "postgres"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"
@@ -44,9 +47,10 @@ versus = "httpserver-robaho"
 [robaho-graalvm]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Platform"
-database = "None"
+database = "postgres"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"

+ 0 - 140
frameworks/Clojure/ring-http-exchange/pom.xml

@@ -1,140 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>ring-http-server</groupId>
-    <artifactId>ring-http-server</artifactId>
-    <packaging>jar</packaging>
-    <version>1.0.0</version>
-    <name>ring-http-server</name>
-    <licenses>
-        <license>
-            <name>EPL-2.0</name>
-            <url>https://www.eclipse.org/legal/epl-2.0/</url>
-        </license>
-    </licenses>
-    <build>
-        <sourceDirectory>java</sourceDirectory>
-        <testSourceDirectory>java-test</testSourceDirectory>
-        <resources>
-            <resource>
-                <directory>resources</directory>
-            </resource>
-        </resources>
-        <testResources>
-            <testResource>
-                <directory>resources</directory>
-            </testResource>
-        </testResources>
-        <directory>target</directory>
-        <outputDirectory>target\classes</outputDirectory>
-        <plugins>
-            <plugin>
-                <groupId>com.theoryinpractise</groupId>
-                <artifactId>clojure-maven-plugin</artifactId>
-                <version>1.8.3</version>
-                <extensions>true</extensions>
-                <executions>
-                    <execution>
-                        <id>compile-clojure</id>
-                        <phase>compile</phase>
-                        <goals>
-                            <goal>compile</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>test-clojure</id>
-                        <phase>test</phase>
-                        <goals>
-                            <goal>test</goal>
-                        </goals>
-                    </execution>
-                </executions>
-                <configuration>
-                    <sourceDirectories>
-                        <sourceDirectory>src/</sourceDirectory>
-                    </sourceDirectories>
-                </configuration>
-            </plugin>
-            <plugin>
-                <artifactId>maven-assembly-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <manifest>
-                            <mainClass>ring_http_exchange.benchmark</mainClass>
-                        </manifest>
-                    </archive>
-                    <descriptorRefs>
-                        <descriptorRef>jar-with-dependencies</descriptorRef>
-                    </descriptorRefs>
-                </configuration>
-                <executions>
-                    <execution>
-                        <id>make-assembly</id>
-                        <phase>package</phase>
-                        <goals>
-                            <goal>single</goal>
-                        </goals>
-                    </execution>
-                </executions>
-            </plugin>
-        </plugins>
-    </build>
-    <repositories>
-        <repository>
-            <id>central</id>
-            <url>https://repo1.maven.org/maven2/</url>
-            <snapshots>
-                <enabled>false</enabled>
-            </snapshots>
-            <releases>
-                <enabled>true</enabled>
-            </releases>
-        </repository>
-        <repository>
-            <id>clojars</id>
-            <url>https://repo.clojars.org/</url>
-            <snapshots>
-                <enabled>true</enabled>
-            </snapshots>
-            <releases>
-                <enabled>true</enabled>
-            </releases>
-        </repository>
-    </repositories>
-    <dependencyManagement>
-        <dependencies/>
-    </dependencyManagement>
-    <dependencies>
-        <dependency>
-            <groupId>org.clojure</groupId>
-            <artifactId>clojure</artifactId>
-            <version>1.11.2</version>
-        </dependency>
-        <dependency>
-            <groupId>org.clojars.jj</groupId>
-            <artifactId>ring-http-exchange</artifactId>
-            <version>1.2.0</version>
-        </dependency>
-        <dependency>
-            <groupId>metosin</groupId>
-            <artifactId>jsonista</artifactId>
-            <version>0.3.13</version>
-        </dependency>
-    </dependencies>
-    <profiles>
-        <profile>
-            <id>robaho</id>
-            <activation>
-                <activeByDefault>false</activeByDefault>
-            </activation>
-            <dependencies>
-                <dependency>
-                    <groupId>io.github.robaho</groupId>
-                    <artifactId>httpserver</artifactId>
-                    <version>1.0.23</version>
-                </dependency>
-            </dependencies>
-        </profile>
-    </profiles>
-</project>

+ 21 - 0
frameworks/Clojure/ring-http-exchange/project.clj

@@ -0,0 +1,21 @@
+(defproject ring-http-server "1.0.0"
+  :description "ring benchmark"
+  :url ""
+  :license {:name "EPL-2.0"
+            :url  "https://www.eclipse.org/legal/epl-2.0/"}
+
+  :dependencies [[org.clojure/clojure "1.12.3"]
+                 [org.clojure/tools.logging "1.3.0"]
+                 [org.clojars.jj/ring-http-exchange "1.2.4"]
+                 [seancorfield/next.jdbc "1.2.659"]
+                 [org.clojars.jj/majavat "1.12.1"]
+                 [hikari-cp "3.3.0"]
+                 [org.postgresql/postgresql "42.7.8"]
+                 [metosin/jsonista "0.3.13"]
+                 ]
+
+  :profiles {:robaho {:dependencies [[io.github.robaho/httpserver "1.0.28"]]}}
+  :resource-paths ["resources"]
+  :main ring-http-exchange.benchmark
+
+  )

+ 15 - 0
frameworks/Clojure/ring-http-exchange/resources/fortune.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+    <table>
+    <tr><th>id</th><th>message</th></tr>
+    {% for message in messages %}
+    <tr>
+        <td>{{message.id}}</td>
+        <td>{{message.message}}</td>
+    </tr>
+    {% endfor %}
+</table>
+</body>
+</html>

+ 5 - 4
frameworks/Clojure/ring-http-exchange/ring-http-exchange-graalvm.dockerfile

@@ -1,12 +1,13 @@
-FROM maven:3-eclipse-temurin-24-alpine as maven
+FROM clojure:lein as lein
 WORKDIR /ring-http-exchange
-COPY pom.xml pom.xml
+COPY project.clj project.clj
+COPY resources resources
 COPY src src
-RUN mvn clean clojure:compile package
+RUN lein uberjar
 
 FROM ghcr.io/graalvm/graalvm-community:24
 WORKDIR /ring-http-exchange
-COPY --from=maven /ring-http-exchange/target/ring-http-server-1.0.0-jar-with-dependencies.jar app.jar
+COPY --from=lein /ring-http-exchange/target/ring-http-server-1.0.0-standalone.jar app.jar
 
 EXPOSE 8080
 

+ 5 - 4
frameworks/Clojure/ring-http-exchange/ring-http-exchange-robaho-graalvm.dockerfile

@@ -1,12 +1,13 @@
-FROM maven:3-eclipse-temurin-24-alpine as maven
+FROM clojure:lein as lein
 WORKDIR /ring-http-exchange
-COPY pom.xml pom.xml
+COPY project.clj project.clj
+COPY resources resources
 COPY src src
-RUN mvn clean clojure:compile -P robaho package
+RUN lein with-profile robaho uberjar
 
 FROM ghcr.io/graalvm/graalvm-community:24
 WORKDIR /ring-http-exchange
-COPY --from=maven /ring-http-exchange/target/ring-http-server-1.0.0-jar-with-dependencies.jar app.jar
+COPY --from=lein /ring-http-exchange/target/ring-http-server-1.0.0-standalone.jar app.jar
 
 EXPOSE 8080
 

+ 5 - 4
frameworks/Clojure/ring-http-exchange/ring-http-exchange-robaho.dockerfile

@@ -1,12 +1,13 @@
-FROM maven:3-eclipse-temurin-24-alpine as maven
+FROM clojure:lein as lein
 WORKDIR /ring-http-exchange
-COPY pom.xml pom.xml
+COPY project.clj project.clj
+COPY resources resources
 COPY src src
-RUN mvn clean clojure:compile -P robaho package
+RUN lein with-profile robaho uberjar
 
 FROM openjdk:25-jdk-slim
 WORKDIR /ring-http-exchange
-COPY --from=maven /ring-http-exchange/target/ring-http-server-1.0.0-jar-with-dependencies.jar app.jar
+COPY --from=lein /ring-http-exchange/target/ring-http-server-1.0.0-standalone.jar app.jar
 
 EXPOSE 8080
 

+ 5 - 4
frameworks/Clojure/ring-http-exchange/ring-http-exchange.dockerfile

@@ -1,12 +1,13 @@
-FROM maven:3-eclipse-temurin-24-alpine as maven
+FROM clojure:lein as lein
 WORKDIR /ring-http-exchange
-COPY pom.xml pom.xml
+COPY project.clj project.clj
+COPY resources resources
 COPY src src
-RUN mvn clean clojure:compile package
+RUN lein uberjar
 
 FROM openjdk:25-jdk-slim
 WORKDIR /ring-http-exchange
-COPY --from=maven /ring-http-exchange/target/ring-http-server-1.0.0-jar-with-dependencies.jar app.jar
+COPY --from=lein /ring-http-exchange/target/ring-http-server-1.0.0-standalone.jar app.jar
 
 EXPOSE 8080
 

+ 53 - 23
frameworks/Clojure/ring-http-exchange/src/ring_http_exchange/benchmark.clj

@@ -1,29 +1,59 @@
 (ns ring-http-exchange.benchmark
-    (:gen-class)
-    (:require [jsonista.core :as json]
-      [ring-http-exchange.core :as server])
-    (:import
-      (java.util.concurrent Executors)))
+  (:gen-class)
+  (:require
+    [jsonista.core :as json]
+    [jj.majavat :as majavat]
+    [jj.majavat.renderer :refer [->InputStreamRenderer]]
+    [jj.majavat.renderer.sanitizer :refer [->Html]]
+    [ring-http-exchange.core :as server]
+    [next.jdbc :as jdbc]
+    [next.jdbc.connection :as connection])
+  (:import
+    (com.zaxxer.hikari HikariDataSource)
+    (java.util.concurrent Executors)))
 
+(def db-spec
+  {:jdbcUrl "jdbc:postgresql://tfb-database/hello_world?user=benchmarkdbuser&password=benchmarkdbpass"})
+
+(def datasource
+  (connection/->pool HikariDataSource db-spec))
+
+(defn query-fortunes []
+  (jdbc/execute! datasource
+                 ["SELECT * FROM \"Fortune\""]
+                 {:builder-fn next.jdbc.result-set/as-unqualified-lower-maps}))
+
+(def ^:private ^:const additional-message {:id      0
+                                           :message "Additional fortune added at request time."})
+(def ^:private ^:const fortune-headers {"Server"       "ring-http-exchange"
+                                        "Content-Type" "text/html; charset=UTF-8"})
 (def ^:private ^:const json-headers {"Server"       "ring-http-exchange"
                                      "Content-Type" "application/json"})
-(def ^:private ^:const plaintext-response
-  {:status  200
-   :headers {
-             "Server"       "ring-http-exchange"
-             "Content-Type" "text/plain"}
-   :body    "Hello, World!"})
+(def ^:private ^:const plaintext-response {:status  200
+                                           :headers {"Server"       "ring-http-exchange"
+                                                     "Content-Type" "text/plain"}
+                                           :body    "Hello, World!"})
+(def ^:private render-fortune (majavat/build-renderer "fortune.html"
+                                                      {:renderer (->InputStreamRenderer
+                                                                   {:sanitizer (->Html)})}))
 
 (defn -main
-      [& args]
-      (println "Starting server on port 8080")
-      (server/run-http-server
-        (fn [req]
-            (case (req :uri)
-                  "/json" {:status  200
-                           :headers json-headers
-                           :body    (json/write-value-as-bytes {:message "Hello, World!"})}
-                  plaintext-response))
-        {:port     8080
-         :host     "0.0.0.0"
-         :executor (Executors/newVirtualThreadPerTaskExecutor)}))
+  [& _]
+  (println "Starting server on port 8080")
+  (server/run-http-server
+    (fn [req]
+      (case (req :uri)
+        "/json" {:status  200
+                 :headers json-headers
+                 :body    (json/write-value-as-bytes {:message "Hello, World!"})}
+        "/fortunes" (let [input (as-> (query-fortunes) fortunes
+                                      (conj fortunes additional-message)
+                                      (sort-by :message fortunes))
+                          body (render-fortune {:messages input})]
+                      {:status  200
+                       :headers fortune-headers
+                       :body    body})
+        plaintext-response))
+    {:port     8080
+     :host     "0.0.0.0"
+     :executor (Executors/newVirtualThreadPerTaskExecutor)}))