Skip to content

Commit

Permalink
0.7.3 default Ring middleware
Browse files Browse the repository at this point in the history
Technically a breaking change from 0.7.2 since any apps with custom
middleware will now break but no one is likely to be using 0.7.0/1/2 and
0.7.0 is already signaled as breaking.

Also cleans up the rendering, routing, and handling code (the latter
because the middleware logic is a lot simpler now).
  • Loading branch information
seancorfield committed Oct 3, 2016
1 parent 488ea0a commit 9f2a394
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 74 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Any controller function also has access to the the FW/1 API (after `require`ing

* `(cookie rc name)` - returns the value of `name` from the cookie scope.
* `(cookie rc name value)` - sets `name` to `value` in the cookie scope, and returns the updated `rc`.
* `(event rc name)` - returns the value of `name` from the event scope (`:action`, `:section`, `:item, or `:config`).
* `(event rc name)` - returns the value of `name` from the event scope (`:action`, `:section`, `:item`, or `:config`).
* `(event rc name value)` - sets `name` to `value` in the event scope, and returns the updated `rc`.
* `(flash rc name)` - returns the value of `name` from the flash scope.
* `(flash rc name value)` - sets `name` to `value` in the flash scope, and returns the updated `rc`.
Expand Down Expand Up @@ -153,18 +153,16 @@ as a map (preferred) or as an arbitrary number of inline key / value pairs (lega
* `:error` - the action - _"section.item"_ - to execute if an exception is thrown from the initial request, defaults to `:default-section` value and `"error"` _[untested]_.
* `:home` - the _"section.item"_ pair used for the / URL, defaults to `:default-section` and `:default-item` values.
* `:json-config` - specify formatting information for Cheshire's JSON `generate-string`, used in `render-json` (`:date-format`, `:ex`, `:key-fn`, etc).
* `:middleware` - specify additional Ring-compatible middleware to use. By default, this is prepended to the default list (params, flash, session, resource). The behavior can be changed by specifying a keyword at the start of the list of additional middleware: `:append`, `:prepend`, `:replace`.
* `:middleware-default-fn` - an optional function that will be applied to Ring's site defaults; note that by default we do **not** enable the XSRF Anti-Forgery middleware that is normally part of the site defaults since that requires session scope and client knowledge which is not appropriate for many uses of FW/1. Specify `#(assoc-in [:security :anti-forgery] true)` here to opt into XSRF Anti-Forgery (you'll probably also want to change the :session :store from the in-memory default unless you have just a single server instance).
* `:options-access-control` - specify what an `OPTIONS` request should return (`:origin`, `:headers`, `:credentials`, `:max-age`).
* `:password` - specify a password for the application reload URL flag, default `"secret"` - see also `:reload`.
* `:reload` - specify an `rc` key for the application reload URL flag, default `:reload` - see also `:password`.
* `:reload-application-on-every-request` - boolean, whether to reload controller, view and layout components on every request (intended for development of applications).
* `:routes` - a vector of hash maps, specifying route patterns and what to map them to (full documentation coming in due course).
* `:selmer-tags` - you can specify a map that is passed to the Selmer parser to override what characters are used to identify tags, filters
* `:session-store` - specify storage used for Ring session storage. Legal values are `:memory` and `:cookie`. Default is whatever is Ring's default (which is memory storage as of this writing).
* `:suffix` - the file extension used for views and layouts. Default is `"html"`.

For example: `(fw1/configure-router :default-section "hello" :default-item "world")` will tell FW/1 to use `hello.world` as the default action.
You could also say: `(fw1/configure-router {:default-section "hello" :default-item "world"})`.
For example: `(fw1/configure-router {:default-section "hello" :default-item "world"})` will tell FW/1 to use `hello.world` as the default action.

License & Copyright
===================
Expand Down
3 changes: 2 additions & 1 deletion build.boot
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(def project 'framework-one)
(def version "0.7.2")
(def version "0.7.3")

(task-options!
pom {:project project
Expand All @@ -18,6 +18,7 @@
[cheshire "5.6.3"]
; core web request handling
[ring "1.6.0-beta6"]
[ring/ring-defaults "0.2.1"]
; view/layout templates
[selmer "1.0.9"]
; standardized application start/stop
Expand Down
117 changes: 49 additions & 68 deletions src/framework/one.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,12 @@
[clojure.data.xml :as xml]
[clojure.stacktrace :as stacktrace]
[clojure.string :as str]
[clojure.walk :as walk]
[com.stuartsierra.component :as component]
[compojure.coercions :refer :all]
[compojure.core :refer :all]
[compojure.route :as route]
[ring.adapter.jetty :as jetty]
[ring.middleware.flash :as ring-f]
[ring.middleware.params :as ring-p]
[ring.middleware.resource :as ring-r]
[ring.middleware.session :as ring-s]
[ring.middleware.session.cookie :refer [cookie-store]]
[ring.middleware.session.memory :refer [memory-store]]
[ring.middleware.defaults :as ring-md]
[selmer.filters]
[selmer.parser]
[selmer.util :refer [resource-path]]))
Expand Down Expand Up @@ -59,7 +53,7 @@

;; FW/1 base functionality - this is essentially the public API of the
;; framework with the entry point to create Ring middleware being:
;; (fw1/start) - returns Ring middleware for your application
;; (fw1/default-handler) - returns Ring middleware for your application
;; See the bottom of this file for more details

(def cookie
Expand Down Expand Up @@ -149,7 +143,7 @@
(def session
"Get / set items in session scope:
(session rc name) - returns the named session variable
(session rc value) - sets the named session variable
(session rc name value) - sets the named session variable
Session variables persist across requests and use Ring's session
middleware (and can be memory or cookie-based at the moment)."
(scope-access :session))
Expand Down Expand Up @@ -253,8 +247,8 @@
desired layout path, attempt to render that layout."
[config rc exceptional? html layout-path]
(let [render-layout (fn [] (selmer.parser/render-file layout-path
(assoc rc :body [:safe html])
(:selmer-tags config)))]
(assoc rc :body [:safe html])
(:selmer-tags config)))]
(if exceptional?
;; if we fail to render a layout while processing an exception
;; just treat the layout as not found so the original view will
Expand Down Expand Up @@ -362,7 +356,7 @@
render views and layouts."
[config section item req]
(let [exceptional? (::handling-exception req)
rc (-> (walk/keywordize-keys (:params req))
rc (-> (:params req)
(pack-request req)
(event :action (str section "." item))
(event :section section)
Expand Down Expand Up @@ -395,34 +389,15 @@
{"Access-Control-Max-Age" (str (:max-age access-control))}]}))

(defn- default-middleware
"The default set of Ring middleware we apply in FW/1"
[config]
[ring-p/wrap-params
ring-f/wrap-flash
(fn [h]
(condp = (:session-store config)
:memory (ring-s/wrap-session h {:store (memory-store)})
:cookie (ring-s/wrap-session h {:store (cookie-store)})
(ring-s/wrap-session h)))
(fn [req] (ring-r/wrap-resource req (stem config "/")))])

(defn- merge-middleware
"Return a function that, given any user-supplied middleware (as a vector),
will combine that will our default Ring middleware (see above). The user
supplied middleware vector is prepended before the default middleware by
default, but can have a placement as its first element:
- :append - append supplied middleware after defaults
- :replace - use supplied middleware instead of defaults
- :prepend - prepend supplied middleware before defaults"
[config]
(fn [middleware]
(if middleware
(condp = (first middleware)
:append (concat (default-middleware config) (rest middleware))
:replace (rest middleware)
:prepend (concat (rest middleware) (default-middleware config))
(concat middleware (default-middleware config)))
(default-middleware config))))
"The default Ring middleware we apply in FW/1. Returns a single
composed piece of middleware. We start with Ring's site defaults
and the fn passed in may modify those defaults."
[modifier-fn]
(fn [handler]
(ring-md/wrap-defaults handler (-> ring-md/site-defaults
; you have to explicitly opt in to this:
(assoc-in [:security :anti-forgery] false)
modifier-fn))))

(def ^:private default-options-access-control
{:origin "*"
Expand All @@ -440,6 +415,9 @@
:home (if (:home options)
(clojure.string/split (:home options) #"\.")
[(:default-section options) (:default-item options)])
; can modify site-defaults
:middleware (default-middleware (or (:middleware-default-fn options)
identity))
:options-access-control (merge default-options-access-control
(:options-access-control options))))

Expand All @@ -452,46 +430,49 @@
:reload :reload
:reload-application-on-every-request false
:suffix "html" ; views / layouts would be .html
:version "0.6.0"})
:version "0.7.3"})

(defn- build-config
"Given a 'public' application configuration, return the fully built
FW/1 configuration for it."
[app-config]
(let [config (framework-defaults (merge default-options app-config))]
(update config :middleware (merge-middleware config))))
(framework-defaults (merge default-options app-config)))

(defn- handler
"The underlying Ring request handler for FW/1."
[config section item req fw1-router]
(try
(if (= :options (:request-method req))
(render-options config req)
(render-request config section item req))
(catch Exception e
(if (::handling-exception req)
(do
(stacktrace/print-stack-trace e)
(html-response 500 (str e)))
(let [section (first (:error config))
item (second (:error config))
fw1 (fw1-router (keyword section item))]
(fw1 (-> req
(assoc ::handling-exception true)
(assoc :uri (str "/" section "/" item))
(assoc-in [:params :exception] e))))))))

(defn configure-router
"Given the application configuration, return a function that takes a
"Given the application configuration, return a router function that takes a
:section/item keyword and produces a function that handles a (Ring) request."
[app-config]
(let [config (build-config app-config)]
(fn configured-fw1
([] (configured-fw1 (keyword (first (:home config))
(second (:home config)))))
(let [config (build-config app-config)]
(fn fw1-router
([] (fw1-router (keyword (first (:home config))
(second (:home config)))))
([section-item] ; :section or :section/item
(let [[section item] (if (namespace section-item)
[(namespace section-item) (name section-item)]
[(name section-item) (second (:home config))])]
(reduce (fn [handler middleware] (middleware handler))
(fn [req]
(try
(if (= :options (:request-method req))
(render-options config req)
(render-request config section item req))
(catch Exception e
(if (::handling-exception req)
(do
(stacktrace/print-stack-trace e)
(html-response 500 (str e)))
(let [section (first (:error config))
item (second (:error config))]
((configured-fw1 (keyword section item))
(-> req
(assoc ::handling-exception true)
(assoc :uri (str "/" section "/" item))
(assoc-in [:params :exception] e))))))))
(:middleware config)))))))
[(name section-item) (second (:home config))])
middleware-fn (:middleware config)]
(middleware-fn (fn [req]
(handler config section item req fw1-router))))))))

(defn default-handler
"Build a default FW/1 handler from the application and configuration.
Expand Down

0 comments on commit 9f2a394

Please sign in to comment.