Skip to content

Instantly share code, notes, and snippets.

@arifian
Last active July 9, 2018 10:18
Show Gist options
  • Save arifian/288fda0820813da5b3c90122925c2d2f to your computer and use it in GitHub Desktop.
Save arifian/288fda0820813da5b3c90122925c2d2f to your computer and use it in GitHub Desktop.
pedestal - component basic building block
(ns app.pedestal.component
"Connection between the Component framework and the Pedestal web application server.
inspired by: https://github.com/stuartsierra/component.pedestal/commit/9ee90960b86f84c02ec59848c6f34bd977c5e8d0"
(:require [com.stuartsierra.component :as component]
[io.pedestal.http :as http]
[io.pedestal.http.cors :as cors]
[io.pedestal.http.impl.servlet-interceptor :as servlet-interceptor]
[io.pedestal.interceptor :refer [interceptor interceptor-name]]))
(defn insert-context-interceptor
"Returns an interceptor which associates key with value in the
Pedestal context map."
[& kv]
(interceptor
{:name ::insert-context
:enter (fn [context]
(apply assoc context kv))}))
(defn add-component-interceptor
"Adds an interceptor to the pedestal-config map which associates the
pedestal-component into the Pedestal context map. Must be called
before io.pedestal.http/create-server."
[pedestal-config pedestal-component]
(update pedestal-config
::http/interceptors
conj
(apply
insert-context-interceptor
(-> pedestal-component
seq flatten))))
(defrecord Pedestal [pedestal-config-fn pedestal-server config]
component/Lifecycle
(start [this]
(if pedestal-server
this
(assoc this :pedestal-server
(-> (pedestal-config-fn config)
(add-component-interceptor this)
(http/create-server (constantly nil)) ; no-op init-fn
http/start))))
(stop [this]
(when pedestal-server
(http/stop pedestal-server))
(assoc this :pedestal-server nil)))
;; (defn- get-pedestal
;; [context]
;; (let [pedestal (get context ::pedestal-component ::not-found)]
;; (when (nil? pedestal)
;; (throw (ex-info (str "Pedestal component was nil in context map; "
;; "component.pedestal is not configured correctly")
;; {:reason ::nil-pedestal
;; :context context})))
;; (when (= ::not-found pedestal)
;; (throw (ex-info (str "Pedestal component was missing from context map; "
;; "component.pedestal is not configured correctly")
;; {:reason ::missing-pedestal
;; :context context})))
;; pedestal))
;; (defn context-component
;; "Returns the component at key from the Pedestal context map. key
;; must have been a declared dependency of the Pedestal server
;; component."
;; [context key]
;; (let [component (get (get-pedestal context) key ::not-found)]
;; (when (nil? component)
;; (throw (ex-info (str "Component " key " was nil in Pedestal dependencies; "
;; "maybe it returned nil from start or stop")
;; {:reason ::nil-component
;; :dependency-key key
;; :context context})))
;; (when (= ::not-found component)
;; (throw (ex-info (str "Missing component " key " from Pedestal dependencies")
;; {:reason ::missing-dependency
;; :dependency-key key
;; :context context})))
;; component))
;; (defn component-handler
;; "Returns a Pedestal interceptor which extracts the component named
;; key from the context map. The key must have been declared a
;; dependency of the Pedestal server component.
;; Invokes f with two arguments, the component and the Ring-style
;; request map. f should return a Ring-style response map.
;; You can use this to replace Ring-style handler functions with
;; functions that take both a component and a request."
;; ([key f] (component-handler nil key f))
;; ([name key f]
;; (interceptor
;; {:name (interceptor-name name)
;; :enter (fn [context]
;; (let [c (context-component context key)]
;; (assoc context :response (f c (:request context)))))})))
;; (defn using-component
;; "Returns an interceptor which associates the component named key
;; into the Ring-style request map as :component. The key must have
;; been declared a dependency of the Pedestal server component.
;; You can add this interceptor to your Pedestal routes to make the
;; component available to your Ring-style handler functions, which can
;; get :component from the request map."
;; [key]
;; (interceptor
;; {:name ::using-component
;; :enter (fn [context]
;; (assoc-in context [:request ::component]
;; (context-component context key)))}))
;; (defn use-component
;; "Returns the component added to the request map by
;; 'using-component'."
;; [request]
;; (::component request))
(defn pedestal
"Returns a new instance of the Pedestal server component.
pedestal-config-fn is a no-argument function which returns the
Pedestal server configuration map, which will be passed to
io.pedestal.http/create-server. If you want the default
interceptors, you must call io.pedestal.http/default-interceptors
in pedestal-config-fn.
The Pedestal component should have dependencies (as by
com.stuartsierra.component/using or system-using) on all components
needed by your web application. These dependencies will be available
in the Pedestal context map via 'context-component'.
You can make components available to your handler functions with
'using-component' or 'component-handler'."
([pedestal-config-fn config]
(->Pedestal pedestal-config-fn nil config)))
;; (defrecord DevPedestal [pedestal-config-fn pedestal-server config]
;; component/Lifecycle
;; (start [this]
;; (if pedestal-server
;; this
;; (assoc this :pedestal-server
;; (-> (pedestal-config-fn config)
;; (add-component-interceptor this)
;; ;; for development mode
;; (update-in [::http/interceptors]
;; (fn [xs]
;; (vec (->> xs
;; (cons cors/dev-allow-origin)
;; (cons servlet-interceptor/exception-debug)))))
;; (http/create-server (constantly nil)) ; no-op init-fn
;; http/start))))
;; (stop [this]
;; (when pedestal-server
;; (http/stop pedestal-server))
;; (assoc this :pedestal-server nil)))
;; (defn dev-pedestal
;; "Returns a new instance of the Pedestal server component.
;; pedestal-config-fn is a no-argument function which returns the
;; Pedestal server configuration map, which will be passed to
;; io.pedestal.http/create-server. If you want the default
;; interceptors, you must call io.pedestal.http/default-interceptors
;; in pedestal-config-fn.
;; The Pedestal component should have dependencies (as by
;; com.stuartsierra.component/using or system-using) on all components
;; needed by your web application. These dependencies will be available
;; in the Pedestal context map via 'context-component'.
;; You can make components available to your handler functions with
;; 'using-component' or 'component-handler'."
;; ([pedestal-config-fn config]
;; (->DevPedestal pedestal-config-fn nil config)))
(ns app.pedestal.config
"Configuration integration, injecting routes into the system"
(:require
[app.web.routes :as webrout]
[app.utils :as utils]
[app.pedestal.interceptors :as ceptors]
[io.pedestal.http :as http]
[io.pedestal.http.route.definition.table :as table]
[io.pedestal.http.secure-headers :as http.sh]
[ring.middleware.session.cookie :as cookie]))
(defn pedestal-config
"Config specs"
[config]
{::http/host (-> config :pedestal :host)
::http/port (-> config :pedestal :port)
::http/type (-> config :pedestal :http-type)
::http/join? (-> config :pedestal :join?)
::http/resource-path (-> config :pedestal :resource-path)
::http/enable-session {:store
(cookie/cookie-store
{:key (->> config :cookie :key)})
:cookie-name (->> config :cookie :name)}
::http/routes (app-routes config)
::http/secure-headers {:content-security-policy-settings
(http.sh/content-security-policy-header
"object-src 'none';")}})
(ns app.system
(:require
[com.stuartsierra.component :as component]
[schema.core :as s]
[app.utils :refer :all]
[app.schema :refer [ConfigSchema]]
[app.db.datomic :as datomic]
[app.pedestal.component :as pcm]
[app.pedestal.config :as pcf]))
(defn init-production-system
"Initialize production system"
[config]
(component/system-map
:config (s/validate ConfigSchema config)
;; :mycomponent {:my :component}
:datomic (component/using
(datomic/make (-> config :datomic-prod))
[:config])
:pedestal (component/using
(pcm/pedestal pcf/pedestal-config-fn config)
[:datomic])))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment