Small, fast, and complete interceptor library for Clojure/Script with built-in support for common async libraries.
Noun Siepata (Intercept)
sieppari, someone or something that intercepts
Interceptors, like in Pedestal, but with minimal implementation and optimal performance.
The core Sieppari depends on Clojure and nothing else.
If you are new to interceptors, check the
Pedestal Interceptors documentation.
Sieppari's sieppari.core/execute
follows a :request
/ :response
pattern. For
Pedestal-like behavior, use sieppari.core/execute-context
.
(ns example.simple
(:require [sieppari.core :as s]))
;; interceptor, in enter update value in `[:request :x]` with `inc`
(def inc-x-interceptor
{:enter (fn [ctx] (update-in ctx [:request :x] inc))})
;; handler, take `:x` from request, apply `inc`, and return an map with `:y`
(defn handler [request]
{:y (inc (:x request))})
(s/execute
[inc-x-interceptor handler]
{:x 40})
;=> {:y 42}
Any step in the execution pipeline (:enter
, :leave
, :error
) can return either a context map (synchronous execution) or an instance of AsyncContext
- indicating asynchronous execution.
By default, clojure deferrables, java.util.concurrent.CompletionStage
and js/promise
satisfy the AsyncContext
protocol.
Using s/execute
with async steps will block:
;; async interceptor, in enter double value of `[:response :y]`:
(def multiply-y-interceptor
{:leave (fn [ctx]
(future
(Thread/sleep 1000)
(update-in ctx [:response :y] * 2)))})
(s/execute
[inc-x-interceptor multiply-y-interceptor handler]
{:x 40})
; ... 1 second later:
;=> {:y 84}
Using non-blocking version of s/execute
:
(s/execute
[inc-x-interceptor multiply-y-interceptor handler]
{:x 40}
(partial println "SUCCESS:")
(partial println "FAILURE:"))
; => nil
; prints "SUCCESS: {:y 84}" 1sec later
Blocking on async computation:
(let [respond (promise)
raise (promise)]
(s/execute
[inc-x-interceptor multiply-y-interceptor handler]
{:x 40}
respond
raise) ; returns nil immediately
(deref respond 2000 :timeout))
; ... 1 second later:
;=> {:y 84}
Any step can return a java.util.concurrent.CompletionStage
or js/promise
, Sieppari works oob with libraries like Promesa:
;; [funcool/promesa "5.1.0"]`
(require '[promesa.core :as p])
(def chain
[{:enter #(update-in % [:request :x] inc)} ;; 1
{:leave #(p/promise (update-in % [:response :x] / 10))} ;; 4
{:enter #(p/delay 1000 %)} ;; 2
identity]) ;; 3
;; blocking
(s/execute chain {:x 40})
; => {:x 41/10} after after 1sec
;; non-blocking
(s/execute
chain
{:x 40}
(partial println "SUCCESS:")
(partial println "SUCCESS:"))
; => nil
;; prints "SUCCESS: {:x 41/10}" after 1sec
To add a support for one of the supported external async libraries, just add a dependency to them and require
the
respective Sieppari namespace. Currently supported async libraries are:
sieppari.async.core-async
, clj & cljssieppari.async.manifold
cljTo extend Sieppari async support to other libraries, just extend the AsyncContext
protocol.
Requires dependency to [org.clojure/core.async "0.4.474"]
or higher.
(require '[clojure.core.async :as a])
(defn multiply-x-interceptor [n]
{:enter (fn [ctx]
(a/go (update-in ctx [:request :x] * n)))})
(s/execute
[inc-x-interceptor (multiply-x-interceptor 10) handler]
{:x 40})
;=> {:y 411}
Requires dependency to [manifold "0.1.8"]
or higher.
(require '[manifold.deferred :as d])
(defn minus-x-interceptor [n]
{:enter (fn [ctx]
(d/success-deferred (update-in ctx [:request :x] - n)))})
(s/execute
[inc-x-interceptor (minus-x-interceptor 10) handler]
{:x 40})
;=> {:y 31}
Sieppari aims for minimal functionality and can therefore be quite fast. Complete example to test performance is included.
Executing a chain of 10 interceptors, which have :enter
of clojure.core/identity
.
promesa.core/promise
core.async
channelmanifold.deferred.Deferred
All numbers are execution time lower quantile (not testing the goodness of the async libraries , just the execution overhead sippari interceptors adds)
Executor | sync | promesa | core.async | manifold |
---|---|---|---|---|
Pedestal | 8.2µs | - | 92µs | - |
Sieppari | 1.2µs | 4.0µs | 70µs | 110µs |
Middleware (comp) | 0.1µs | - | - | - |
NOTE: running async flows without interceptors is still much faster,
e.g. synchronous manifold
chain is much faster than via interceptors.
NOTE: Goal is to have a Java-backed and optimized chain compiler into Sieppari,
initial tests show it will be near the perf of middleware chain / comp
.
io.pedestal.interceptor.chain/execute
executes Contextssieppari.core/execute
executes Requests (which are internally wrapped inside a Context for interceptors)error
handler takes two arguments, the ctx
and the exception.error
handlers takes just one argument, the ctx
, and the exception is in the ctx
under the key :error
.error
handler resolves the exception by returning the ctx
, and continues the error stage by re-throwing the exception.error
handler resolves the exception by returning the ctx
with the :error
removed. To continue in the error stage, just return the ctx
with the exception still at :error
.java.lang.Throwable
for error processing. Sieppari catches java.lang.Exception
. This means that things like out of memory or class loader failures are not captured by Sieppari.Copyright © 2018-2020 Metosin Oy
Distributed under the Eclipse Public License 2.0.
Can you improve this documentation? These fine people already did:
Jarppe, Tommi Reiman, Miikka Koskinen, den1k, Dosbol, Avi Flax, Pauli Jaakkola, Aleksandr Zhuravlev & Andrea RichiardiEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close