Small interceptor library
Clone or download
Latest commit 34834b7 Sep 4, 2018

README.md

sieppari

Small, fast, and complete interceptor library with built-in support for common async libraries.

Noun Siepata (Intercept)

sieppari, someone or something that intercepts

What it does

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. If you are familiar with interceptors you might want to jump to Differences to Pedestal below.

First example

(ns example.simple
  (:require [sieppari.core :as sieppari]))

;; 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))})

(sieppari/execute 
  [inc-x-interceptor handler] 
  {:x 40})
;=> {:y 42}

Async

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 satisfy the AsyncContext protocol.

Using sieppari.core/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)))})


(sieppari/execute
  [inc-x-interceptor multiply-y-interceptor handler]
  {:x 40})
; ... 1 second later:
;=> {:y 84}

There is also a non-blocking version of execute:

(let [respond (promise)
      raise (promise)]
  (sieppari/execute
    [inc-x-interceptor multiply-y-interceptor handler]
    {:x 40}
    respond
    raise) ; returns nil immediately

  (deref respond 2000 :timeout))
; ... 1 second later:
;=> {:y 84}

External Async Libraries

To add a support for one of the supported external async libraries, just add a dependency to them and you are ready. Currently supported async libraries are:

To extend Sieppari async support to other libraries, just extend AsyncContext protocol.

core.async

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)))})

(sieppari/execute
  [inc-x-interceptor (multiply-x-interceptor 10) handler]
  {:x 40})
;=> {:y 411}

manifold

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)))})

(sieppari/execute
  [inc-x-interceptor (minus-x-interceptor 10) handler]
  {:x 40})
;=> {:y 31}

promesa

Requires dependency to [funcool/promesa "1.9.0"] or higher.

(require '[promesa.core :as p])

(defn divide-x-interceptor [n]
  {:enter (fn [ctx]
            (p/promise (update-in ctx [:request :x] / n)))})

(sieppari/execute
  [inc-x-interceptor (divide-x-interceptor 10) handler]
  {:x 40})
;=> {:y 41/10}

Performance

Sieppari aims for minimal functionality and can therefore be quite fast. Complete example to test performance is included.

The example creates a chain of 100 interceptors that have clojure.core/identity as :enter and :leave functions and then executes the chain. The async tests also have 100 interceptors, but in async case they all return core.async channels on enter and leave.

Executor Execution time lower quantile
Pedestal sync 64 µs
Sieppari sync 9 µs
Pedestal async 410 µs
Sieppari async 396 µs
  • MacBook Pro (Retina, 15-inch, Mid 2015), 2.5 GHz Intel Core i7, 16 MB RAM
  • Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
  • Clojure 1.9.0

Differences to Pedestal

Execution

  • io.pedestal.interceptor.chain/execute executes Contexts
  • sieppari.core/execute executes Requests (which are internally wrapped inside a Context for interceptors)

Errors

  • In Pedestal the error handler takes two arguments, the ctx and the exception.
  • In Sieppari the error handlers takes just one argument, the ctx, and the exception is in the ctx under the key :error.
  • In Pedestal the error handler resolves the exception by returning the ctx, and continues the error stage by re-throwing the exception.
  • In Sieppari the 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.
  • In Pedestal the exception are wrapped in other exceptions.
  • In Sieppari exceptions are not wrapped.
  • Pedestal interception execution catches 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.

Async

  • Pedestal transfers thread local bindings from call-site into async interceptors.
  • Sieppari does not support this.

Thanks

License

Copyright © 2018 Metosin Oy

Distributed under the Eclipse Public License 2.0.