Browse Source

Merge pull request #1514 from zane-techempower/clojure-pedestal

Add Clojure-Pedestal (resolves #583)
Brittany Mazza 10 years ago
parent
commit
461c2c48f1

+ 4 - 0
.gitignore

@@ -3,6 +3,10 @@ installs
 *.log
 node_modules/
 
+# Added for pedestal framework test
+lib/
+.lein-deps-sum
+
 # eclipse
 .classpath
 .project

+ 1 - 0
.travis.yml

@@ -33,6 +33,7 @@ env:
     - "TESTDIR=Clojure/compojure"
     - "TESTDIR=Clojure/http-kit"
     - "TESTDIR=Clojure/luminus"
+    - "TESTDIR=Clojure/pedestal"
     - "TESTDIR=Dart/dart"
     - "TESTDIR=Dart/dart-redstone"
     - "TESTDIR=Dart/dart-start"

+ 5 - 0
frameworks/Clojure/pedestal/.gitignore

@@ -0,0 +1,5 @@
+target/
+tmp/
+logs/
+.lein-repl-history
+.nrepl-port

+ 22 - 0
frameworks/Clojure/pedestal/README.md

@@ -0,0 +1,22 @@
+# Pedestal Benchmarking Test
+
+This is the Pedestal portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+## Test URLs
+### JSON Encoding Test
+`http://localhost:8080/json`
+
+### Single Query Test
+`http://localhost:8080/db`
+
+### Multiple Query Test
+`http://localhost:8080/queries?queries=number`
+
+### Fortune Test
+`http://localhost:8080/fortunes`
+
+### Database Updates
+`http://localhost:8080/updates?queries=number`
+
+### Plaintext
+`http://localhost:8080/plaintext`

+ 28 - 0
frameworks/Clojure/pedestal/benchmark_config.json

@@ -0,0 +1,28 @@
+{
+  "framework": "pedestal",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MySQL",
+      "orm": "micro",
+      "framework": "pedestal",
+      "language": "Clojure",
+      "platform": "Servlet",
+      "webserver": "Jetty",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "pedestal",
+      "notes": "servlet"
+    }
+  }]
+}
+

+ 52 - 0
frameworks/Clojure/pedestal/config/logback.xml

@@ -0,0 +1,52 @@
+<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
+<configuration scan="true" scanPeriod="10 seconds">
+
+  <!-- Simple file output -->
+  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <!-- rollover daily -->
+      <fileNamePattern>logs/pedestal-api-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+      <timeBasedFileNamingAndTriggeringPolicy
+          class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+        <!-- or whenever the file size reaches 64 MB -->
+        <maxFileSize>64 MB</maxFileSize>
+      </timeBasedFileNamingAndTriggeringPolicy>
+    </rollingPolicy>
+
+    <!-- Safely log to the same file from multiple JVMs. Degrades performance! -->
+    <prudent>true</prudent>
+  </appender>
+
+
+  <!-- Console output -->
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
+    <encoder>
+      <pattern>%-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+    <!-- Only log level INFO and above -->
+    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+      <level>INFO</level>
+    </filter>
+  </appender>
+
+
+  <!-- Enable FILE and STDOUT appenders for all log messages.
+       By default, only log at level INFO and above. -->
+  <root level="INFO">
+    <appender-ref ref="FILE" />
+    <appender-ref ref="STDOUT" />
+  </root>
+
+  <!-- For loggers in the these namespaces, log at all levels. -->
+  <logger name="user" level="ALL" />
+  <!-- To log pedestal internals, enable this and change ThresholdFilter to DEBUG
+    <logger name="io.pedestal" level="ALL" />
+  -->
+
+</configuration>

+ 3 - 0
frameworks/Clojure/pedestal/install.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+fw_depends leiningen java7

+ 25 - 0
frameworks/Clojure/pedestal/project.clj

@@ -0,0 +1,25 @@
+(defproject pedestal "0.1"
+  :description "A Clojure-Pedestal server for testing in the Framework Benchmarks"
+  :url "https://github.com/TechEmpower/FrameworkBenchmarks"
+  :license {:name "Eclipse Public License"
+            :url "http://www.eclipse.org/legal/epl-v10.html"}
+  :dependencies [[org.clojure/clojure "1.6.0"]
+                 [io.pedestal/pedestal.service "0.3.1"]
+                 [io.pedestal/pedestal.jetty "0.3.1"]
+                 [ch.qos.logback/logback-classic "1.1.2" :exclusions [org.slf4j/slf4j-api]]
+                 [org.slf4j/jul-to-slf4j "1.7.7"]
+                 [org.slf4j/jcl-over-slf4j "1.7.7"]
+                 [org.slf4j/log4j-over-slf4j "1.7.7"]
+                 [org.clojure/data.json "0.2.5"]
+                 [org.clojure/java.jdbc "0.3.6"]
+                 [korma "0.4.0"]
+                 [mysql/mysql-connector-java "5.1.6"]
+                 [hiccup "1.0.4"]]
+  :min-lein-version "2.0.0"
+  :resource-paths ["config", "resources"]
+  :profiles {:dev {:aliases {"run-dev" ["trampoline" "run" "-m" "pedestal.server/run-dev"]}
+                   :dependencies [[io.pedestal/pedestal.service-tools "0.3.1"]]}}
+  :auto-clean false
+  :main pedestal.server
+  :aot [pedestal.server]
+  :uberjar-name "pedestal-standalone.jar")

+ 14 - 0
frameworks/Clojure/pedestal/setup.sh

@@ -0,0 +1,14 @@
+#!/bin/bash
+
+source $IROOT/java7.installed
+
+source $IROOT/lein.installed
+
+lein clean
+
+rm -rf target
+# pack all dependencies into a single jar: target/pedestal-standalone.jar
+lein uberjar
+# -server is much faster
+# 'lein run' passes '-client -XX:+TieredCompilation -XX:TieredStopAtLevel=1' which make it starts fast, but runs slow
+java -server -jar target/pedestal-standalone.jar &

+ 36 - 0
frameworks/Clojure/pedestal/src/pedestal/server.clj

@@ -0,0 +1,36 @@
+(ns pedestal.server
+  (:gen-class)
+  (:require [io.pedestal.http :as server]
+            [pedestal.service :as service]))
+
+
+;; This is an adapted service map, that can be started and stopped
+;; From the REPL you can call server/start and server/stop on this service
+(defonce runnable-service (server/create-server service/service))
+
+
+(defn run-dev
+  "The entry-point for 'lein run-dev'"
+  [& args]
+  (println "\nCreating your [DEV] server...")
+  (-> service/service ;; start with production configuration
+      (merge {:env :dev
+              ;; do not block thread that starts web server
+              ::server/join? false
+              ;; Routes can be a function that resolve routes,
+              ;;  we can use this to set the routes to be reloadable
+              ::server/routes #(deref #'service/routes)
+              ;; all origins are allowed in dev mode
+              ::server/allowed-origins {:creds true :allowed-origins (constantly true)}})
+      ;; Wire up interceptor chains
+      server/default-interceptors
+      server/dev-interceptors
+      server/create-server
+      server/start))
+
+
+(defn -main
+  "The entry-point for 'lein run'"
+  [& args]
+  (println "\nCreating your server...")
+  (server/start runnable-service))

+ 180 - 0
frameworks/Clojure/pedestal/src/pedestal/service.clj

@@ -0,0 +1,180 @@
+(ns pedestal.service
+  (:import com.mchange.v2.c3p0.ComboPooledDataSource)
+  (:use korma.db
+        korma.core
+        hiccup.core
+        hiccup.util
+        hiccup.page)
+  (:require [io.pedestal.http :as bootstrap]
+            [io.pedestal.http.route :as route]
+            [io.pedestal.http.body-params :as body-params]
+            [io.pedestal.http.route.definition :refer [defroutes]]
+            [ring.util.response :as ring-resp]
+            [clojure.data.json :as json]
+            [clojure.java.jdbc :as jdbc]))
+
+
+(defn json-serialization
+  "Test 1: JSON serialization"
+  [request]
+  (bootstrap/json-response {:message "Hello, World!"}))
+
+
+;; MySQL connection
+(defdb mysql-db
+  (mysql {
+    :classname "com.mysql.jdbc.Driver"
+    :subprotocol "mysql"
+    :subname "//localhost:3306/hello_world"
+    :user "benchmarkdbuser"
+    :password "benchmarkdbpass"
+    ;;OPTIONAL KEYS
+    :delimiters "" ;; remove delimiters
+    :maximum-pool-size 256}))
+
+
+;; Set up entity World and the database representation
+(defentity world
+  (pk :id)
+  (table :world)
+  (entity-fields :id :randomNumber) ;; Default fields for select
+  (database mysql-db))
+
+
+(defn random-world []
+  "Query a random World record from the database"
+  (let [id (inc (rand-int 9999))] ; Num between 1 and 10,000
+    (select world
+      (where {:id id }))))
+
+
+(defn run-queries
+  "Run query repeatedly -- Always returns an array"
+  [queries]
+  (flatten (take queries (repeatedly random-world))))
+
+
+(defn single-query-test
+  "Test 2: Single database query"
+  [request]
+  (bootstrap/json-response (first (run-queries 1))))
+
+
+(defn sanitizeQueriesParam
+  "Sanitizes the `queries` parameter. Caps the value between 1 and 500.
+Invalid (stringy) values become 1"
+  [request]
+  (let [queries (-> request :params :queries)]
+    (let [n
+      (if (= (re-find #"\A-?\d+" queries) nil)
+        1
+        (Integer/parseInt queries))]
+    (cond
+      (< n 1) 1
+      (> n 500) 500
+      :else n))))
+
+
+(defn multiple-query-test
+  "Test 3: Multiple database queries"
+  [request]
+  (-> request
+    (sanitizeQueriesParam)
+    (run-queries)
+    (bootstrap/json-response)))
+
+
+; Set up entity Fortune and the database representation
+(defentity fortune
+  (pk :id)
+  (table :fortune)
+  (entity-fields :id :message)
+  (database mysql-db))
+
+
+(defn get-all-fortunes []
+  "Query all Fortune records from the database."
+  (select fortune
+    (fields :id :message)))
+
+
+(defn get-fortunes []
+  "Fetch the full list of Fortunes from the database, sort them by the fortune
+message text, and then return the results."
+  (sort-by #(:message %)
+    (conj
+      (get-all-fortunes)
+      { :id 0 :message "Additional fortune added at request time." })))
+
+
+(defn fortunes-hiccup [fortunes]
+  "Render the given fortunes to simple HTML using Hiccup."
+  (html5
+   [:head
+    [:title "Fortunes"]]
+   [:body
+    [:table
+     [:tr
+      [:th "id"]
+      [:th "message"]]
+     (for [x fortunes]
+       [:tr
+        [:td (:id x)]
+        [:td (escape-html (:message x))]])
+     ]]))
+
+
+(defn fortune-test [request]
+  "Test 4: Fortunes"
+  (->
+    (get-fortunes)
+    (fortunes-hiccup)
+    (ring-resp/response)
+    (ring-resp/content-type "text/html")
+    (ring-resp/charset "utf-8")))         ;; Apply charset after content type
+
+
+(defn update-and-persist
+  "Changes the :randomNumber of a number of world entities.
+Persists the changes to sql then returns the updated entities"
+  [request]
+  (let [results (-> request (sanitizeQueriesParam) (run-queries))]
+    (for [w results]
+      (update-in w [:randomNumber (inc (rand-int 9999))]
+        (update world
+          (set-fields {:randomNumber (:randomNumber w)})
+          (where {:id [:id w]}))))
+  results))
+
+
+(defn db-updates
+  "Test 5: Database updates"
+  [request]
+  (-> request
+    (update-and-persist)
+    (bootstrap/json-response)))
+
+
+(defn plaintext
+  "Test 6: Plaintext"
+  [request]
+  (ring-resp/response "Hello, World!"))
+
+
+(defroutes routes
+  [[
+  [  "/json"      {:get json-serialization}]
+  [  "/db"        {:get single-query-test}]
+  [  "/queries"   {:get multiple-query-test}]
+  [  "/fortunes"  {:get fortune-test}]
+  [  "/updates"   {:get db-updates}]
+  [  "/plaintext" {:get plaintext}]]])
+
+
+(def service
+  "How the server will look, not the code to start it up"
+  { :env :prod
+    ::bootstrap/routes routes
+    ::bootstrap/resource-path "/public"
+    ::bootstrap/type :jetty
+    ::bootstrap/port 8080})

+ 20 - 0
frameworks/Clojure/pedestal/test/pedestal/service_test.clj

@@ -0,0 +1,20 @@
+(ns pedestal.service-test
+  (:require [clojure.test :refer :all]
+            [io.pedestal.test :refer :all]
+            [io.pedestal.http :as bootstrap]
+            [pedestal.service :as service]))
+
+(def service
+  (::bootstrap/service-fn (bootstrap/create-servlet service/service)))
+
+(deftest home-page-test
+  (is (=
+       (:body (response-for service :get "/json"))
+       "{\"hello\": \"world\""))
+  (is (=
+       (:headers (response-for service :get "/json"))
+       {"Content-Type" "text/html;charset=UTF-8"
+        "Strict-Transport-Security" "max-age=31536000; includeSubdomains"
+        "X-Frame-Options" "DENY"
+        "X-Content-Type-Options" "nosniff"
+        "X-XSS-Protection" "1; mode=block"})))

+ 2 - 1
toolset/setup/linux/systools/leiningen.sh

@@ -8,4 +8,5 @@ fw_get https://raw.github.com/technomancy/leiningen/stable/bin/lein -O leinbin
 mv leinbin lein/bin/lein
 chmod +x lein/bin/lein
 
-touch ${IROOT}/lein.installed
+echo "export LEIN_HOME=$IROOT/lein" > $IROOT/lein.installed
+echo "export PATH=$PATH:$IROOT/lein/bin" >> $IROOT/lein.installed