diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e174570..618e38b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,12 @@ [ring/ring-core "1.7.0"] is available but we use "1.6.3" ``` +## `reitit-http` + +* `:options` requests are served for all routes by default with 200 OK to better support things like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) + * the default handler is not documented in Swagger + * new router option `:reitit.http/default-options-handler` to change this behavior. Setting `nil` disables this. + ## `reitit-middleware` * fix `reitit.ring.middleware.parameters/parameters-middleware` diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index c7a8f852..fff47f52 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -14,7 +14,7 @@ (update acc method expand opts) acc)) data ring/http-methods)]) -(defn compile-result [[path data] opts] +(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}] (let [[top childs] (ring/group-keys data) compile (fn [[path data] opts scope] (interceptor/compile-result [path data] opts scope)) @@ -29,7 +29,12 @@ (fn [acc method] (cond-> acc any? (assoc method (->endpoint path data method nil)))) - (ring/map->Methods {}) + (ring/map->Methods + {:options + (if default-options-handler + (->endpoint path (assoc data + :handler default-options-handler + :no-doc true) :options nil))}) ring/http-methods))] (if-not (seq childs) (->methods true top) @@ -45,6 +50,14 @@ support for http-methods and Interceptors. See [docs](https://metosin.github.io/reitit/) for details. + Options: + + | key | description | + | ---------------------------------------|-------------| + | `:reitit.interceptor/transform` | Function of `[Interceptor] => [Interceptor]` to transform the expanded Interceptors (default: identity). + | `:reitit.interceptor/registry` | Map of `keyword => IntoInterceptor` to replace keyword references into Interceptors + | `:reitit.http/default-options-handler` | Default handler for `:options` method in endpoints (default: reitit.ring/default-options-handler) + Example: (router @@ -58,7 +71,9 @@ ([data] (router data nil)) ([data opts] - (let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)] + (let [opts (merge {:coerce coerce-handler + :compile compile-result + ::default-options-handler ring/default-options-handler} opts)] (r/router data opts)))) (defn routing-interceptor diff --git a/test/clj/reitit/http_test.clj b/test/clj/reitit/http_test.clj index 9cfa35bf..f964b049 100644 --- a/test/clj/reitit/http_test.clj +++ b/test/clj/reitit/http_test.clj @@ -169,6 +169,50 @@ (testing "handler rejects" (is (= -406 (:status (app {:request-method :get, :uri "/pong"})))))))))) +(deftest default-options-handler-test + (let [response {:status 200, :body "ok"}] + + (testing "with defaults" + (let [app (http/ring-handler + (http/router + [["/get" {:get (constantly response) + :post (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]]) + {:executor sieppari/executor})] + + (testing "endpoint with a non-options handler" + (is (= response (app {:request-method :get, :uri "/get"}))) + (is (= {:status 200, :body "", :headers {"Allow" "GET,POST,OPTIONS"}} + (app {:request-method :options, :uri "/get"})))) + + (testing "endpoint with a options handler" + (is (= response (app {:request-method :options, :uri "/options"})))) + + (testing "endpoint with top-level handler" + (is (= response (app {:request-method :get, :uri "/any"}))) + (is (= response (app {:request-method :options, :uri "/any"})))))) + + (testing "disabled via options" + (let [app (http/ring-handler + (http/router + [["/get" {:get (constantly response)}] + ["/options" {:options (constantly response)}] + ["/any" (constantly response)]] + {::http/default-options-handler nil}) + {:executor sieppari/executor})] + + (testing "endpoint with a non-options handler" + (is (= response (app {:request-method :get, :uri "/get"}))) + (is (= nil (app {:request-method :options, :uri "/get"})))) + + (testing "endpoint with a options handler" + (is (= response (app {:request-method :options, :uri "/options"})))) + + (testing "endpoint with top-level handler" + (is (= response (app {:request-method :get, :uri "/any"}))) + (is (= response (app {:request-method :options, :uri "/any"})))))))) + (deftest async-http-test (let [promise #(let [value (atom ::nil)] (fn