diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn
index 16cc62948..901ced7d7 100644
--- a/.clj-kondo/config.edn
+++ b/.clj-kondo/config.edn
@@ -1,5 +1,5 @@
{:lint-as
- {blaze.async.comp/do-sync clojure.core/let}
+ {blaze.test-util/with-system clojure.core/with-open}
:linters
{:unsorted-required-namespaces
diff --git a/.github/distributed-test/docker-compose.yml b/.github/distributed-test/docker-compose.yml
index 54f7234c4..74ee6c88e 100644
--- a/.github/distributed-test/docker-compose.yml
+++ b/.github/distributed-test/docker-compose.yml
@@ -82,6 +82,8 @@ services:
DB_CASSANDRA_MAX_CONCURRENT_REQUESTS: "128"
DB_RESOURCE_CACHE_SIZE: "1000"
LOG_LEVEL: "debug"
+ ports:
+ - "8081:8081"
volumes:
- "./blaze.keystore.jks:/app/blaze.keystore.jks:ro"
- "./kafka.truststore.jks:/app/kafka.truststore.jks:ro"
@@ -109,6 +111,8 @@ services:
DB_CASSANDRA_MAX_CONCURRENT_REQUESTS: "128"
DB_RESOURCE_CACHE_SIZE: "1000"
LOG_LEVEL: "debug"
+ ports:
+ - "8082:8081"
volumes:
- "./blaze.keystore.jks:/app/blaze.keystore.jks:ro"
- "./kafka.truststore.jks:/app/kafka.truststore.jks:ro"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index df08d2118..3b71a45a7 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -64,6 +64,7 @@ jobs:
- rocksdb
- scheduler
- search-param-registry
+ - server
- thread-pool-executor-collector
java-version:
@@ -133,6 +134,7 @@ jobs:
- rocksdb
- scheduler
- search-param-registry
+ - server
- thread-pool-executor-collector
runs-on: ubuntu-20.04
@@ -326,7 +328,7 @@ jobs:
run: docker pull ghcr.io/samply/blaze:sha-${{ github.sha }}
- name: Run Blaze
- run: docker run --name blaze -d -e JAVA_TOOL_OPTIONS=-Xmx2g -p 8080:8080 -v blaze-data:/app/data ghcr.io/samply/blaze:sha-${{ github.sha }}
+ run: docker run --name blaze -d -e JAVA_TOOL_OPTIONS=-Xmx2g -p 8080:8080 -p 8081:8081 -v blaze-data:/app/data ghcr.io/samply/blaze:sha-${{ github.sha }}
- name: Sleep 60 Seconds
run: sleep 60
@@ -448,6 +450,9 @@ jobs:
- name: OPTIONS
run: curl -f -XOPTIONS http://localhost:8080/fhir/metadata
+ - name: Prometheus Metrics
+ run: curl -f http://localhost:8081/metrics
+
jepsen-test:
needs: build
runs-on: ubuntu-20.04
@@ -713,6 +718,12 @@ jobs:
- name: OPTIONS
run: curl -f -XOPTIONS http://localhost:8080/fhir/metadata
+ - name: Prometheus Metrics - Blaze 1
+ run: curl -f http://localhost:8081/metrics
+
+ - name: Prometheus Metrics - Blaze 2
+ run: curl -f http://localhost:8082/metrics
+
- name: Docker Stats
run: docker stats --no-stream
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69098fb41..6c1617ca0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## v0.13.3
+
+### Bugfixes
+
+* Fix Failing Metrics Endpoint ([#547](https://github.com/samply/blaze/pull/547))
+
+### Security
+
+* Fix CVE-2021-37137 in Package io.netty:netty-codec ([#548](https://github.com/samply/blaze/pull/548))
+
+The full changelog can be found [here](https://github.com/samply/blaze/milestone/18?closed=1).
+
## v0.13.2
### Security
diff --git a/deps.edn b/deps.edn
index e53c00557..e45c0ee23 100644
--- a/deps.edn
+++ b/deps.edn
@@ -25,6 +25,9 @@
blaze/rocksdb
{:local/root "modules/rocksdb"}
+ blaze/server
+ {:local/root "modules/server"}
+
blaze/structure-definition
{:local/root "modules/structure-definition"}
@@ -40,19 +43,8 @@
org.clojure/tools.reader
{:mvn/version "1.3.6"}
- org.eclipse.jetty/jetty-server
- {:mvn/version "9.4.44.v20210927"}
-
org.slf4j/slf4j-nop
- {:mvn/version "1.7.32"}
-
- ring/ring-jetty-adapter
- {:mvn/version "1.9.4"
- :exclusions
- [clj-time/clj-time
- commons-fileupload/commons-fileupload
- crypto-equality/crypto-equality
- crypto-random/crypto-random]}}
+ {:mvn/version "1.7.32"}}
:aliases
{:depstar
diff --git a/docs/deployment/docker-deployment.md b/docs/deployment/docker-deployment.md
index a2412de0a..c191a965c 100644
--- a/docs/deployment/docker-deployment.md
+++ b/docs/deployment/docker-deployment.md
@@ -27,7 +27,7 @@ Blaze should log something like this:
2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:64] - JVM version: 16.0.2
2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:65] - Maximum available memory: 1738 MiB
2021-06-27T11:02:37.835Z ee086ef908c1 main INFO [blaze.core:66] - Number of available processors: 8
-2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.13.2 in 8.2 seconds
+2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.13.3 in 8.2 seconds
```
In order to test connectivity, query the health endpoint:
@@ -47,7 +47,7 @@ that should return:
```json
{
"name": "Blaze",
- "version": "0.13.2"
+ "version": "0.13.3"
}
```
diff --git a/docs/deployment/manual-deployment.md b/docs/deployment/manual-deployment.md
index 9456e10bf..d85c4f659 100644
--- a/docs/deployment/manual-deployment.md
+++ b/docs/deployment/manual-deployment.md
@@ -2,12 +2,12 @@
The installation works under Windows, Linux and macOS. The only dependency is an installed OpenJDK 11. Blaze is tested with [AdoptOpenJDK][1].
-Blaze runs on the JVM and comes as single JAR file. Download the most recent version [here](https://github.com/samply/blaze/releases/tag/v0.13.1). Look for `blaze-0.13.2-standalone.jar`.
+Blaze runs on the JVM and comes as single JAR file. Download the most recent version [here](https://github.com/samply/blaze/releases/tag/v0.13.1). Look for `blaze-0.13.3-standalone.jar`.
After the download, you can start blaze with the following command (Linux, macOS):
```sh
-java -jar blaze-0.13.2-standalone.jar -m blaze.core
+java -jar blaze-0.13.3-standalone.jar -m blaze.core
```
Blaze will run with an in-memory, volatile database for testing and demo purposes.
@@ -17,14 +17,14 @@ Blaze can be run with durable storage by setting the environment variables `STOR
Under Linux/macOS:
```sh
-STORAGE=standalone java -jar blaze-0.13.2-standalone.jar -m blaze.core
+STORAGE=standalone java -jar blaze-0.13.3-standalone.jar -m blaze.core
```
Under Windows, you need to set the Environment variables in the PowerShell before starting Blaze:
```powershell
$Env:STORAGE="standalone"
-java -jar blaze-0.13.2-standalone.jar -m blaze.core
+java -jar blaze-0.13.3-standalone.jar -m blaze.core
```
This will create three directories called `index`, `transaction` and `resource` inside the current working directory, one for each database part used.
@@ -42,7 +42,7 @@ The output should look like this:
2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:64] - JVM version: 16.0.2
2021-06-27T11:02:37.834Z ee086ef908c1 main INFO [blaze.core:65] - Maximum available memory: 1738 MiB
2021-06-27T11:02:37.835Z ee086ef908c1 main INFO [blaze.core:66] - Number of available processors: 8
-2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.13.2 in 8.2 seconds
+2021-06-27T11:02:37.836Z ee086ef908c1 main INFO [blaze.core:67] - Successfully started Blaze version 0.13.3 in 8.2 seconds
```
In order to test connectivity, query the health endpoint:
@@ -62,7 +62,7 @@ that should return:
```json
{
"name": "Blaze",
- "version": "0.13.2"
+ "version": "0.13.3"
}
```
diff --git a/modules/anomaly/src/blaze/anomaly.clj b/modules/anomaly/src/blaze/anomaly.clj
index 13ca158fd..ee4e84112 100644
--- a/modules/anomaly/src/blaze/anomaly.clj
+++ b/modules/anomaly/src/blaze/anomaly.clj
@@ -78,10 +78,14 @@
(busy (.getMessage e)))
ExceptionInfo
(-anomaly [e]
- (let [data (.getData e)]
- (if (anomaly? data)
- data
- (fault (.getMessage e)))))
+ (cond->
+ (merge
+ (cond-> {::anom/category ::anom/fault}
+ (.getMessage e)
+ (assoc ::anom/message (.getMessage e)))
+ (.getData e))
+ (.getCause e)
+ (assoc :blaze.anomaly/cause (-anomaly (.getCause e)))))
Throwable
(-anomaly [e]
(fault (.getMessage e)))
diff --git a/modules/anomaly/test/blaze/anomaly_test.clj b/modules/anomaly/test/blaze/anomaly_test.clj
index 8c8228eeb..8dd940e96 100644
--- a/modules/anomaly/test/blaze/anomaly_test.clj
+++ b/modules/anomaly/test/blaze/anomaly_test.clj
@@ -180,14 +180,28 @@
(testing "ExceptionInfo"
(testing "without an anomaly in data"
- (given (ba/anomaly (ex-info "msg-184349" {}))
+ (given (ba/anomaly (ex-info "msg-184349" {::foo ::bar}))
::anom/category := ::anom/fault
- ::anom/message := "msg-184349"))
+ ::anom/message := "msg-184349"
+ ::foo := ::bar))
(testing "with an anomaly in data"
(given (ba/anomaly (ex-info "msg-184349" (ba/incorrect "msg-184433")))
::anom/category := ::anom/incorrect
- ::anom/message := "msg-184433")))
+ ::anom/message := "msg-184433"))
+
+ (testing "without message"
+ (is (= ::none ((ba/anomaly (ex-info nil {})) ::anom/message ::none))))
+
+ (testing "with cause"
+ (given (ba/anomaly (ex-info "msg-181247" {} (Exception. "msg-181120")))
+ ::anom/message := "msg-181247"
+ [:blaze.anomaly/cause ::anom/message] := "msg-181120")
+
+ (testing "and nil message"
+ (given (ba/anomaly (ex-info "msg-181247" {} (Exception.)))
+ ::anom/message := "msg-181247"
+ [:blaze.anomaly/cause #(% ::anom/message ::none)] := ::none))))
(testing "Exception"
(given (ba/anomaly (Exception. "msg-120840"))
@@ -203,7 +217,7 @@
(deftest try-one-test
(testing "without message"
(is (= (ba/try-one Exception ::anom/fault (throw (Exception.)))
- {::anom/category ::anom/fault})))
+ {::anom/category ::anom/fault})))
(testing "with message"
(given (ba/try-one Exception ::anom/fault (throw (Exception. "msg-134156")))
diff --git a/modules/cassandra/deps.edn b/modules/cassandra/deps.edn
index 677d4df9b..395d7a62e 100644
--- a/modules/cassandra/deps.edn
+++ b/modules/cassandra/deps.edn
@@ -6,7 +6,11 @@
{:mvn/version "0.3.3"}
com.datastax.oss/java-driver-core
- {:mvn/version "4.13.0"}}
+ {:mvn/version "4.13.0"}
+
+ ;; curreny version of transitive dependency of com.datastax.oss/java-driver-core
+ io.netty/netty-handler
+ {:mvn/version "4.1.70.Final"}}
:aliases
{:test
diff --git a/src/blaze/handler/metrics.clj b/modules/metrics/src/blaze/metrics/handler.clj
similarity index 71%
rename from src/blaze/handler/metrics.clj
rename to modules/metrics/src/blaze/metrics/handler.clj
index debd92380..f784e121b 100644
--- a/src/blaze/handler/metrics.clj
+++ b/modules/metrics/src/blaze/metrics/handler.clj
@@ -1,4 +1,4 @@
-(ns blaze.handler.metrics
+(ns blaze.metrics.handler
(:require
[blaze.metrics.spec]
[clojure.spec.alpha :as s]
@@ -7,11 +7,11 @@
[taoensso.timbre :as log]))
-(defmethod ig/pre-init-spec :blaze.handler/metrics [_]
+(defmethod ig/pre-init-spec :blaze.metrics/handler [_]
(s/keys :req-un [:blaze.metrics/registry]))
-(defmethod ig/init-key :blaze.handler/metrics
+(defmethod ig/init-key :blaze.metrics/handler
[_ {:keys [registry]}]
(log/info "Init metrics handler")
(fn [_]
diff --git a/modules/metrics/src/blaze/metrics/registry.clj b/modules/metrics/src/blaze/metrics/registry.clj
index c3c30f6d1..ee6976e1b 100644
--- a/modules/metrics/src/blaze/metrics/registry.clj
+++ b/modules/metrics/src/blaze/metrics/registry.clj
@@ -17,7 +17,7 @@
(defmethod ig/pre-init-spec :blaze.metrics/registry [_]
- (s/keys :req-un [:blaze.metrics/collectors]))
+ (s/keys :opt-un [:blaze.metrics/collectors]))
(defn- register-collectors! [registry collectors]
@@ -29,7 +29,7 @@
(defmethod ig/init-key :blaze.metrics/registry
- [_ {:keys [collectors]}]
+ [_ {:keys [collectors] :or {collectors []}}]
(log/info "Init metrics registry")
(doto (CollectorRegistry. true)
(.register (StandardExports.))
diff --git a/modules/metrics/src/blaze/metrics/spec.clj b/modules/metrics/src/blaze/metrics/spec.clj
index 5c19341a2..44b652e17 100644
--- a/modules/metrics/src/blaze/metrics/spec.clj
+++ b/modules/metrics/src/blaze/metrics/spec.clj
@@ -13,8 +13,12 @@
(s/coll-of :blaze.metrics/collector))
+(defn registry? [x]
+ (instance? CollectorRegistry x))
+
+
(s/def :blaze.metrics/registry
- #(instance? CollectorRegistry %))
+ registry?)
(s/def :blaze.metrics/metric
diff --git a/modules/metrics/test/blaze/metrics/handler_test.clj b/modules/metrics/test/blaze/metrics/handler_test.clj
new file mode 100644
index 000000000..c1bc0b49e
--- /dev/null
+++ b/modules/metrics/test/blaze/metrics/handler_test.clj
@@ -0,0 +1,60 @@
+(ns blaze.metrics.handler-test
+ (:require
+ [blaze.metrics.handler]
+ [blaze.metrics.registry]
+ [blaze.metrics.spec :as spec]
+ [blaze.test-util :refer [given-thrown with-system]]
+ [clojure.spec.alpha :as s]
+ [clojure.spec.test.alpha :as st]
+ [clojure.string :as str]
+ [clojure.test :as test :refer [deftest testing]]
+ [integrant.core :as ig]
+ [juxt.iota :refer [given]]
+ [taoensso.timbre :as log]))
+
+
+(st/instrument)
+(log/set-level! :trace)
+
+
+(defn- fixture [f]
+ (st/instrument)
+ (f)
+ (st/unstrument))
+
+
+(test/use-fixtures :each fixture)
+
+
+(deftest init-test
+ (testing "nil config"
+ (given-thrown (ig/init {:blaze.metrics/handler nil})
+ :key := :blaze.metrics/handler
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `map?))
+
+ (testing "missing config"
+ (given-thrown (ig/init {:blaze.metrics/handler {}})
+ :key := :blaze.metrics/handler
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :registry))))
+
+ (testing "invalid registry"
+ (given-thrown (ig/init {:blaze.metrics/handler {:registry ::invalid}})
+ :key := :blaze.metrics/handler
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `spec/registry?
+ [:explain ::s/problems 0 :val] := ::invalid)))
+
+
+(def system
+ {:blaze.metrics/handler {:registry (ig/ref :blaze.metrics/registry)}
+ :blaze.metrics/registry {}})
+
+
+(deftest handler-test
+ (with-system [{:blaze.metrics/keys [handler]} system]
+ (given (handler nil)
+ :status := 200
+ [:headers "Content-Type"] := "text/plain; version=0.0.4; charset=utf-8"
+ :body :? #(str/starts-with? % "# HELP"))))
diff --git a/modules/metrics/test/blaze/metrics/registry_test.clj b/modules/metrics/test/blaze/metrics/registry_test.clj
index fa662139b..40dbcbfa4 100644
--- a/modules/metrics/test/blaze/metrics/registry_test.clj
+++ b/modules/metrics/test/blaze/metrics/registry_test.clj
@@ -24,14 +24,6 @@
(test/use-fixtures :each fixture)
-(def collector
- (metrics/collector [(metrics/counter-metric "foo_total" "" [] [])]))
-
-
-(defn- samples [registry]
- (mapv datafy/datafy (iterator-seq (.asIterator (.metricFamilySamples registry)))))
-
-
(deftest init-test
(testing "nil config"
(given-thrown (ig/init {:blaze.metrics/registry nil})
@@ -39,20 +31,27 @@
:reason := ::ig/build-failed-spec
[:explain ::s/problems 0 :pred] := `map?))
- (testing "missing config"
- (given-thrown (ig/init {:blaze.metrics/registry {}})
- :key := :blaze.metrics/registry
- :reason := ::ig/build-failed-spec
- [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :collectors))))
-
(testing "invalid collectors"
(given-thrown (ig/init {:blaze.metrics/registry {:collectors ::invalid}})
:key := :blaze.metrics/registry
:reason := ::ig/build-failed-spec
[:explain ::s/problems 0 :pred] := `coll?
- [:explain ::s/problems 0 :val] := ::invalid))
+ [:explain ::s/problems 0 :val] := ::invalid)))
+
+
+(def collector
+ (metrics/collector [(metrics/counter-metric "foo_total" "" [] [])]))
+
+
+(def system
+ {:blaze.metrics/registry {:collectors [collector]}})
+
+
+(defn- samples [registry]
+ (mapv datafy/datafy (iterator-seq (.asIterator (.metricFamilySamples registry)))))
+
+(deftest registry-test
(testing "with one collector"
- (with-system [{:blaze.metrics/keys [registry]} {:blaze.metrics/registry
- {:collectors [collector]}}]
+ (with-system [{:blaze.metrics/keys [registry]} system]
(is (= 1 (count (filter (comp #{"foo"} :name) (samples registry))))))))
diff --git a/modules/rest-api/src/blaze/rest_api/capabilities.clj b/modules/rest-api/src/blaze/rest_api/capabilities.clj
index 96743d14a..bc8e55ee7 100644
--- a/modules/rest-api/src/blaze/rest_api/capabilities.clj
+++ b/modules/rest-api/src/blaze/rest_api/capabilities.clj
@@ -108,7 +108,7 @@
:copyright
#fhir/markdown"Copyright 2019 - 2021 The Samply Community\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."
:kind #fhir/code"instance"
- :date #fhir/dateTime"2021-11-22"
+ :date #fhir/dateTime"2021-11-26"
:software
{:name "Blaze"
:version version}
diff --git a/modules/rest-api/src/blaze/rest_api/spec.clj b/modules/rest-api/src/blaze/rest_api/spec.clj
index 83678aeb1..b0094f53d 100644
--- a/modules/rest-api/src/blaze/rest_api/spec.clj
+++ b/modules/rest-api/src/blaze/rest_api/spec.clj
@@ -11,6 +11,10 @@
(set! *warn-on-reflection* true)
+(s/def :blaze/rest-api
+ fn?)
+
+
(s/def :blaze.rest-api/auth-backends
(s/coll-of #(satisfies? p/IAuthentication %)))
diff --git a/modules/rocksdb/deps.edn b/modules/rocksdb/deps.edn
index 329332e23..3b59f98bd 100644
--- a/modules/rocksdb/deps.edn
+++ b/modules/rocksdb/deps.edn
@@ -9,7 +9,7 @@
{:local/root "../module-base"}
org.rocksdb/rocksdbjni
- {:mvn/version "6.25.3"}}
+ {:mvn/version "6.26.1"}}
:aliases
{:test
diff --git a/modules/server/.clj-kondo/config.edn b/modules/server/.clj-kondo/config.edn
new file mode 100644
index 000000000..c1c1f504a
--- /dev/null
+++ b/modules/server/.clj-kondo/config.edn
@@ -0,0 +1,11 @@
+{:lint-as
+ {blaze.async.comp/do-sync clojure.core/let
+ blaze.test-util/with-system clojure.core/with-open}
+
+ :linters
+ {:unsorted-required-namespaces
+ {:level :error}
+ :single-key-in
+ {:level :warning}}
+
+ :skip-comments true}
diff --git a/modules/server/Makefile b/modules/server/Makefile
new file mode 100644
index 000000000..5c978eb5d
--- /dev/null
+++ b/modules/server/Makefile
@@ -0,0 +1,13 @@
+lint:
+ clj-kondo --lint src test deps.edn
+
+test:
+ clojure -M:test --profile :ci
+
+test-coverage:
+ clojure -M:test-coverage
+
+clean:
+ rm -rf .clj-kondo/.cache .cpcache target
+
+.PHONY: lint test test-coverage clean
diff --git a/modules/server/deps.edn b/modules/server/deps.edn
new file mode 100644
index 000000000..345d95a11
--- /dev/null
+++ b/modules/server/deps.edn
@@ -0,0 +1,55 @@
+{:deps
+ {blaze/async
+ {:local/root "../async"}
+
+ blaze/module-base
+ {:local/root "../module-base"}
+
+ org.eclipse.jetty/jetty-server
+ {:mvn/version "9.4.44.v20210927"}
+
+ ring/ring-jetty-adapter
+ {:mvn/version "1.9.4"
+ :exclusions
+ [clj-time/clj-time
+ commons-fileupload/commons-fileupload
+ crypto-equality/crypto-equality
+ crypto-random/crypto-random]}}
+
+ :aliases
+ {:test
+ {:extra-paths ["test"]
+
+ :extra-deps
+ {blaze/test-util
+ {:local/root "../test-util"}
+
+ hato/hato
+ {:mvn/version "0.8.2"}
+
+ lambdaisland/kaocha
+ {:mvn/version "1.60.945"}
+
+ org.slf4j/slf4j-nop
+ {:mvn/version "1.7.32"}}
+
+ :main-opts ["-m" "kaocha.runner"]}
+
+ :test-coverage
+ {:extra-paths ["test"]
+
+ :extra-deps
+ {blaze/test-util
+ {:local/root "../test-util"}
+
+ cloverage/cloverage
+ {:mvn/version "1.2.2"}
+
+ hato/hato
+ {:mvn/version "0.8.2"}
+
+ org.slf4j/slf4j-nop
+ {:mvn/version "1.7.32"}}
+
+ :main-opts ["-m" "cloverage.coverage" "--codecov" "-p" "src" "-s" "test"
+ "-e" ".*spec$"]}}}
diff --git a/modules/server/src/blaze/server.clj b/modules/server/src/blaze/server.clj
new file mode 100644
index 000000000..c07593d4e
--- /dev/null
+++ b/modules/server/src/blaze/server.clj
@@ -0,0 +1,44 @@
+(ns blaze.server
+ "HTTP Server."
+ (:require
+ [blaze.server.spec]
+ [clojure.spec.alpha :as s]
+ [integrant.core :as ig]
+ [ring.adapter.jetty :as ring-jetty]
+ [ring.util.response :as ring]
+ [taoensso.timbre :as log])
+ (:import
+ [org.eclipse.jetty.server Server]))
+
+
+(defn- wrap-server [handler server]
+ (fn
+ ([request]
+ (ring/header (handler request) "Server" server))
+ ([request respond raise]
+ (handler request #(respond (ring/header % "Server" server)) raise))))
+
+
+(defmethod ig/pre-init-spec :blaze/server [_]
+ (s/keys :req-un [::port ::handler ::version]
+ :opt-un [::async? ::min-threads ::max-threads]))
+
+
+(defmethod ig/init-key :blaze/server
+ [_ {:keys [port handler version async? min-threads max-threads]
+ :or {async? false min-threads 8 max-threads 50}}]
+ (log/info "Start main server on port" port)
+ (ring-jetty/run-jetty
+ (wrap-server handler (str "Blaze/" version))
+ {:port port
+ :async? async?
+ :join? false
+ :send-server-version? false
+ :min-threads min-threads
+ :max-threads max-threads}))
+
+
+(defmethod ig/halt-key! :blaze/server
+ [_ server]
+ (log/info "Shutdown main server")
+ (.stop ^Server server))
diff --git a/modules/server/src/blaze/server/spec.clj b/modules/server/src/blaze/server/spec.clj
new file mode 100644
index 000000000..4d48f2dcd
--- /dev/null
+++ b/modules/server/src/blaze/server/spec.clj
@@ -0,0 +1,27 @@
+(ns blaze.server.spec
+ (:require
+ [clojure.spec.alpha :as s]))
+
+
+(s/def :blaze.server/port
+ (s/and nat-int? #(<= % 65535)))
+
+
+(s/def :blaze.server/handler
+ fn?)
+
+
+(s/def :blaze.server/version
+ string?)
+
+
+(s/def :blaze.server/async?
+ boolean?)
+
+
+(s/def :blaze.server/min-threads
+ (s/and nat-int? #(<= % 100)))
+
+
+(s/def :blaze.server/max-threads
+ (s/and nat-int? #(<= % 100)))
diff --git a/modules/server/test/blaze/server_test.clj b/modules/server/test/blaze/server_test.clj
new file mode 100644
index 000000000..f01270732
--- /dev/null
+++ b/modules/server/test/blaze/server_test.clj
@@ -0,0 +1,144 @@
+(ns blaze.server-test
+ (:refer-clojure :exclude [error-handler])
+ (:require
+ [blaze.anomaly :as ba]
+ [blaze.server]
+ [blaze.test-util :refer [given-thrown with-system]]
+ [clojure.spec.alpha :as s]
+ [clojure.spec.test.alpha :as st]
+ [clojure.test :as test :refer [deftest testing]]
+ [cognitect.anomalies :as anom]
+ [hato.client :as hc]
+ [integrant.core :as ig]
+ [juxt.iota :refer [given]]
+ [ring.util.response :as ring]
+ [taoensso.timbre :as log])
+ (:import
+ [java.net ServerSocket]))
+
+
+(st/instrument)
+(log/set-level! :trace)
+
+
+(defn- fixture [f]
+ (st/instrument)
+ (f)
+ (st/unstrument))
+
+
+(test/use-fixtures :each fixture)
+
+
+(deftest init-test
+ (testing "nil config"
+ (given-thrown (ig/init {:blaze/server nil})
+ :key := :blaze/server
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `map?))
+
+ (testing "missing config"
+ (given-thrown (ig/init {:blaze/server {}})
+ :key := :blaze/server
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :port))
+ [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :handler))
+ [:explain ::s/problems 2 :pred] := `(fn ~'[%] (contains? ~'% :version))))
+
+ (testing "invalid port"
+ (given-thrown (ig/init {:blaze/server {:port ::invalid}})
+ :key := :blaze/server
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :handler))
+ [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :version))
+ [:explain ::s/problems 2 :pred] := `nat-int?
+ [:explain ::s/problems 2 :val] := ::invalid))
+
+ (testing "invalid handler"
+ (given-thrown (ig/init {:blaze/server {:handler ::invalid}})
+ :key := :blaze/server
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :port))
+ [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :version))
+ [:explain ::s/problems 2 :pred] := `fn?
+ [:explain ::s/problems 2 :val] := ::invalid))
+
+ (testing "invalid version"
+ (given-thrown (ig/init {:blaze/server {:version ::invalid}})
+ :key := :blaze/server
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :port))
+ [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :handler))
+ [:explain ::s/problems 2 :pred] := `string?
+ [:explain ::s/problems 2 :val] := ::invalid)))
+
+
+(defn ok-handler [_]
+ (ring/response "OK"))
+
+
+(defn async-ok-handler [_ respond _]
+ (respond (ring/response "OK")))
+
+
+(defn error-handler [_]
+ (throw (Exception. "msg-163147")))
+
+
+(defn async-error-handler [_ _ raise]
+ (raise (Exception. "msg-163147")))
+
+
+(defn system
+ ([port]
+ (system port ok-handler))
+ ([port handler]
+ {:blaze/server {:port port :handler handler :version "1.0"}}))
+
+
+(defn async-system
+ ([port]
+ (async-system port async-ok-handler))
+ ([port handler]
+ {:blaze/server {:port port :handler handler :version "1.0" :async? true}}))
+
+
+(defn- find-free-port []
+ (with-open [s (ServerSocket. 0)]
+ (.getLocalPort s)))
+
+
+(deftest server-test
+ (let [port (find-free-port)]
+ (testing "successful response"
+ (with-system [_ (system port)]
+ (given (hc/get (str "http://localhost:" port))
+ :status := 200
+ :body := "OK"
+ [:headers "server"] := "Blaze/1.0"))
+
+ (testing "async"
+ (with-system [_ (async-system port)]
+ (given (hc/get (str "http://localhost:" port))
+ :status := 200
+ :body := "OK"
+ [:headers "server"] := "Blaze/1.0"))))
+
+ (testing "error"
+ (with-system [_ (system port error-handler)]
+ (given (ba/try-anomaly (hc/get (str "http://localhost:" port)))
+ :status := 500))
+
+ (testing "async"
+ (with-system [_ (async-system port async-error-handler)]
+ (given (ba/try-anomaly (hc/get (str "http://localhost:" port)))
+ :status := 500))))
+
+ (testing "with already bound port"
+ (with-system [_ (system port)]
+ (given (ba/try-anomaly (ig/init (system port)))
+ ::anom/category := ::anom/fault
+ :key := :blaze/server,
+ :reason := :integrant.core/build-threw-exception
+ ::anom/message := "Error on key :blaze/server when building system"
+ [:blaze.anomaly/cause ::anom/message] := (str "Failed to bind to 0.0.0.0/0.0.0.0:" port))))))
diff --git a/modules/server/tests.edn b/modules/server/tests.edn
new file mode 100644
index 000000000..94fe5636c
--- /dev/null
+++ b/modules/server/tests.edn
@@ -0,0 +1,5 @@
+#kaocha/v1
+ #merge
+ [{}
+ #profile {:ci {:reporter kaocha.report/documentation
+ :color? false}}]
diff --git a/perf-test/gatling/pom.xml b/perf-test/gatling/pom.xml
index 5e2f4c259..b29d7a627 100644
--- a/perf-test/gatling/pom.xml
+++ b/perf-test/gatling/pom.xml
@@ -5,7 +5,7 @@
samply.blaze
gatling
- 0.13.2
+ 0.13.3
1.8
diff --git a/pom.xml b/pom.xml
index 9168fa9f6..cfd753f8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
samply
blaze
- 0.13.2
+ 0.13.3
blaze
A FHIR Store with internal, fast CQL Evaluation Engine
diff --git a/src/blaze/handler/app.clj b/src/blaze/handler/app.clj
index ae7e2a14c..7b6b616bc 100644
--- a/src/blaze/handler/app.clj
+++ b/src/blaze/handler/app.clj
@@ -1,5 +1,9 @@
(ns blaze.handler.app
(:require
+ [blaze.async.comp :as ac]
+ [blaze.handler.health.spec]
+ [blaze.rest-api.spec]
+ [clojure.spec.alpha :as s]
[integrant.core :as ig]
[reitit.ring]
[ring.util.response :as ring]
@@ -28,7 +32,21 @@
rest-api))
+(defn- wrap-sync [handler]
+ (fn [request respond raise]
+ (-> (handler request)
+ (ac/when-complete
+ (fn [response e]
+ (if response
+ (respond response)
+ (raise e)))))))
+
+
+(defmethod ig/pre-init-spec :blaze.handler/app [_]
+ (s/keys :req-un [:blaze/rest-api :blaze/health-handler]))
+
+
(defmethod ig/init-key :blaze.handler/app
[_ {:keys [rest-api health-handler]}]
(log/info "Init app handler")
- (handler rest-api health-handler))
+ (wrap-sync (handler rest-api health-handler)))
diff --git a/src/blaze/handler/health/spec.clj b/src/blaze/handler/health/spec.clj
new file mode 100644
index 000000000..4a0772d65
--- /dev/null
+++ b/src/blaze/handler/health/spec.clj
@@ -0,0 +1,7 @@
+(ns blaze.handler.health.spec
+ (:require
+ [clojure.spec.alpha :as s]))
+
+
+(s/def :blaze/health-handler
+ fn?)
diff --git a/src/blaze/server.clj b/src/blaze/server.clj
deleted file mode 100644
index 74a3d1873..000000000
--- a/src/blaze/server.clj
+++ /dev/null
@@ -1,49 +0,0 @@
-(ns blaze.server
- "HTTP Server
-
- Call `init!` to initialize an HTTP server and `shutdown!` to release its port
- again."
- (:require
- [blaze.async.comp :as ac :refer [do-sync]]
- [ring.adapter.jetty :as ring-jetty]
- [ring.util.response :as ring])
- (:import
- [org.eclipse.jetty.server Server]))
-
-
-(defn- wrap-server [handler server]
- (fn [request]
- (do-sync [response (handler request)]
- (ring/header response "Server" server))))
-
-
-(defn- wrap-sync [handler]
- (fn [request respond raise]
- (-> (handler request)
- (ac/when-complete
- (fn [response e]
- (if response
- (respond response)
- (raise e)))))))
-
-
-(defn init!
- "Creates a new HTTP server listening on `port` serving from `handler`.
-
- Call `shutdown!` on the returned server to stop listening and releasing its
- port."
- [port handler version]
- (ring-jetty/run-jetty
- (-> handler
- (wrap-server (str "Blaze/" version))
- (wrap-sync))
- {:port port
- :async? true
- :join? false
- :send-server-version? false}))
-
-
-(defn shutdown!
- "Shuts `server` down, releasing its port."
- [server]
- (.stop ^Server server))
diff --git a/src/blaze/server/spec.clj b/src/blaze/server/spec.clj
deleted file mode 100644
index 51e5eba6f..000000000
--- a/src/blaze/server/spec.clj
+++ /dev/null
@@ -1,12 +0,0 @@
-(ns blaze.server.spec
- (:require
- [blaze.executors :as ex]
- [clojure.spec.alpha :as s]))
-
-
-(s/def :blaze.server/port
- (s/and nat-int? #(<= % 65535)))
-
-
-(s/def :blaze.server/executor
- ex/executor?)
diff --git a/src/blaze/server_spec.clj b/src/blaze/server_spec.clj
deleted file mode 100644
index 8ee69e95e..000000000
--- a/src/blaze/server_spec.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-(ns blaze.server-spec
- (:require
- [blaze.server :as server]
- [blaze.server.spec]
- [clojure.spec.alpha :as s])
- (:import
- [java.lang AutoCloseable]))
-
-
-(s/fdef server/init!
- :args (s/cat :port ::server/port :handler fn? :version string?))
-
-
-(s/fdef server/shutdown!
- :args (s/cat :server #(instance? AutoCloseable %)))
diff --git a/src/blaze/system.clj b/src/blaze/system.clj
index 05a04141c..53a2724ce 100644
--- a/src/blaze/system.clj
+++ b/src/blaze/system.clj
@@ -7,7 +7,7 @@
(:require
[blaze.executors :as ex]
[blaze.log]
- [blaze.server :as server]
+ [blaze.server]
[blaze.server.spec]
[clojure.java.io :as io]
[clojure.string :as str]
@@ -87,7 +87,7 @@
(def ^:private root-config
- {:blaze/version "0.13.2"
+ {:blaze/version "0.13.3"
:blaze/clock {}
@@ -125,7 +125,8 @@
:blaze/server
{:port (->Cfg "SERVER_PORT" nat-int? 8080)
:handler (ig/ref :blaze.handler/app)
- :version (ig/ref :blaze/version)}
+ :version (ig/ref :blaze/version)
+ :async? true}
:blaze/thread-pool-executor-collector
{:executors (->RefMap :blaze.metrics/thread-pool-executor)}
@@ -133,13 +134,15 @@
:blaze.metrics/registry
{:collectors (ig/refset :blaze.metrics/collector)}
- :blaze.handler/metrics
+ :blaze.metrics/handler
{:registry (ig/ref :blaze.metrics/registry)}
- :blaze.metrics/server
+ [:blaze/server :blaze.metrics/server]
{:port (->Cfg "METRICS_SERVER_PORT" nat-int? 8081)
- :handler (ig/ref :blaze.handler/metrics)
- :version (ig/ref :blaze/version)}})
+ :handler (ig/ref :blaze.metrics/handler)
+ :version (ig/ref :blaze/version)
+ :min-threads 1
+ :max-threads 4}})
(defn- feature-enabled?
@@ -229,27 +232,3 @@
(derive :blaze.server/executor :blaze.metrics/thread-pool-executor)
-
-
-(defmethod ig/init-key :blaze/server
- [_ {:keys [port handler version]}]
- (log/info "Start main server on port" port)
- (server/init! port handler version))
-
-
-(defmethod ig/halt-key! :blaze/server
- [_ server]
- (log/info "Shutdown main server")
- (server/shutdown! server))
-
-
-(defmethod ig/init-key :blaze.metrics/server
- [_ {:keys [port handler version]}]
- (log/info "Start metrics server on port" port)
- (server/init! port handler version))
-
-
-(defmethod ig/halt-key! :blaze.metrics/server
- [_ server]
- (log/info "Shutdown metrics server")
- (server/shutdown! server))
diff --git a/src/blaze/system_spec.clj b/src/blaze/system_spec.clj
index b3aa2b05f..f88514eee 100644
--- a/src/blaze/system_spec.clj
+++ b/src/blaze/system_spec.clj
@@ -1,7 +1,6 @@
(ns blaze.system-spec
(:require
[blaze.db.api-spec]
- [blaze.server-spec]
[blaze.system :as system]
[clojure.spec.alpha :as s]))
diff --git a/test/blaze/handler/app_test.clj b/test/blaze/handler/app_test.clj
new file mode 100644
index 000000000..2732521db
--- /dev/null
+++ b/test/blaze/handler/app_test.clj
@@ -0,0 +1,90 @@
+(ns blaze.handler.app-test
+ (:require
+ [blaze.async.comp :as ac]
+ [blaze.handler.app]
+ [blaze.test-util :refer [given-thrown with-system]]
+ [clojure.spec.alpha :as s]
+ [clojure.spec.test.alpha :as st]
+ [clojure.test :as test :refer [deftest testing]]
+ [integrant.core :as ig]
+ [juxt.iota :refer [given]]
+ [ring.util.response :as ring]
+ [taoensso.timbre :as log]))
+
+
+(st/instrument)
+(log/set-level! :trace)
+
+
+(defn- fixture [f]
+ (st/instrument)
+ (f)
+ (st/unstrument))
+
+
+(test/use-fixtures :each fixture)
+
+
+(deftest init-test
+ (testing "nil config"
+ (given-thrown (ig/init {:blaze.handler/app nil})
+ :key := :blaze.handler/app
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `map?))
+
+ (testing "missing config"
+ (given-thrown (ig/init {:blaze.handler/app {}})
+ :key := :blaze.handler/app
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :rest-api))
+ [:explain ::s/problems 1 :pred] := `(fn ~'[%] (contains? ~'% :health-handler))))
+
+ (testing "invalid rest-api"
+ (given-thrown (ig/init {:blaze.handler/app {:rest-api ::invalid}})
+ :key := :blaze.handler/app
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :health-handler))
+ [:explain ::s/problems 1 :pred] := `fn?
+ [:explain ::s/problems 1 :val] := ::invalid))
+
+ (testing "invalid health"
+ (given-thrown (ig/init {:blaze.handler/app {:health-handler ::invalid}})
+ :key := :blaze.handler/app
+ :reason := ::ig/build-failed-spec
+ [:explain ::s/problems 0 :pred] := `(fn ~'[%] (contains? ~'% :rest-api))
+ [:explain ::s/problems 1 :pred] := `fn?
+ [:explain ::s/problems 1 :val] := ::invalid)))
+
+
+(defn- rest-api [_]
+ (ac/completed-future (ring/response ::rest-api)))
+
+
+(defn- health-handler [_]
+ (ac/completed-future (ring/response ::health-handler)))
+
+
+(def system
+ {:blaze.handler/app {:rest-api rest-api :health-handler health-handler}})
+
+
+(deftest handler-test
+ (testing "rest-api"
+ (with-system [{handler :blaze.handler/app} system]
+ @(handler
+ {:uri "/" :request-method :get}
+ (fn [response]
+ (given response
+ :status := 200
+ :body := ::rest-api))
+ identity)))
+
+ (testing "health-handler"
+ (with-system [{handler :blaze.handler/app} system]
+ @(handler
+ {:uri "/health" :request-method :get}
+ (fn [response]
+ (given response
+ :status := 200
+ :body := ::health-handler))
+ identity))))