reitit

Lusia 2025-08-22T06:23:44.442259Z

Hi, I'm looking at this doc https://github.com/metosin/reitit/blob/master/doc/ring/RESTful_form_methods.md Does this only work with middleware? I tried to do this with interceptor but doesn't work, the handler that it goes to is still the POST handler instead of DELETE. The reason i need this is because I'm working with hiccup and unpoly, and I wanted to use form to delete a resource.

Lusia 2025-08-28T03:14:18.382269Z

it does run, as you see in the one with hidden delete, the :request-method is :delete

juhoteperi 2025-08-28T06:51:16.123439Z

Reitit ring-handler only finds the match once using the initial path and request-method, it doesn't try try to check if an interceptor makes changes to those

juhoteperi 2025-08-28T06:52:36.532929Z

Or well, the question is at which point the interceptors run. Route-data middleware obviously run only after the match is available. ring-handler middlware run before the match is chosen.

juhoteperi 2025-08-28T06:54:31.191209Z

If you check reitit.http, you see the reitit.http/ring-handler interceptors option is implemented by adding those interceptors to route-data. Those only run after the match is found

Lusia 2025-08-22T06:44:45.950319Z

Here is the interceptor:

(defn- get-hidden-method
  "Extract the override method from form or query parameters."
  [request]
  (some-> (or (get-in request [:form-params "_method"]) ; Look in form params
              (get-in request [:multipart-params "_method"]) ; Or multipart params
              (get-in request [:query-params "_method"]) ; Or query params
              (get-in request [:params "_method"])) ; Or merged params
          str/lower-case
          keyword))

(defn method-override
  "Interceptor that overrides the HTTP method based on _method parameter.

  Only overrides POST requests to prevent security issues.

  Usage:
  Add this interceptor to your route configuration:

  {:interceptors [(method-override)]}

  In your HTML form:
  <form method=\"POST\" action=\"/resource\">
    <input type=\"hidden\" name=\"_method\" value=\"DELETE\">
    <button type=\"submit\">Delete</button>
  </form>

  Docs:
    - "
  []
  {:name ::method-override
   :enter
   (fn [ctx]
     (-> ctx
         (update
           :request
           (fn [req]
             (-> req
                 (medley/assoc-some
                   :request-method
                   (when-let [override-method
                              (and (= :post (-> req :request-method))
                                   (get-hidden-method req))]
                     override-method)))))))})

Lusia 2025-09-03T04:03:45.756429Z

so i suppose its not possible to do it currently unless i use pedestal or other library?

Brandon Olivier 2025-08-26T14:03:48.500739Z

Is the interceptor running? When these things aren't working as I expect, I tend to put a line at the top of the interceptor (def ctx ctx) and start evaluating. Try that and make sure everything looks like you expect it.

Lusia 2025-08-25T07:23:00.491639Z

I read pedestal's code about the method-param interceptor and how it was used. It seems the router matching is done in an interceptor instead. I see there is this reitit-http/routing-interceptor but no example or test written for it, can anyone give me an example on how to use it?

Brandon Olivier 2025-08-25T21:30:01.126929Z

I can't think of a reason it wouldn't work in an interceptor. What exactly is going wrong and how are you using it?

Lusia 2025-08-26T03:14:05.915019Z

Here is an example of what i did

(let [post-handler (fn [req]
                       (println "POST handler")
                       (pp req)
                       (http-resp/ok))
        delete-handler (fn [req]
                         (println "DELETE handler")
                         (pp req)
                         (http-resp/ok))

        routes
        ["/"
         {:post   {:handler post-handler}
          :delete {:handler delete-handler}}]

        ring-handler
        (reitit.http/ring-handler
          (reitit.http/router routes {})
          {:executor       reitit.interceptor.sieppari/executor
           :inject-match?  false
           :inject-router? false
           :data           {}

           :interceptors
           [(reitit.http.interceptors.parameters/parameters-interceptor)
            (muuntaja.interceptor/format-interceptor
              (-> muuntaja/default-options
                  (assoc-in [:formats "application/json"] ginoco-ring/json-muuntaja-format)
                  muuntaja/create))
            (reitit.http.coercion/coerce-exceptions-interceptor)
            (reitit.http.coercion/coerce-response-interceptor)
            (reitit.http.coercion/coerce-request-interceptor)
            (reitit.http.interceptors.multipart/multipart-interceptor)
            (gini.interceptor/method-override)]})]
    
    (println "===== POST request =====")
    (ring-handler (http-mock/request :post "/"))
    (println "===== POST request with hidden DELETE =====")
    (ring-handler (http-mock/request :post "/?_method=delete")))
===== POST request =====
POST handler
{:form-params {}
 :headers {"host" "localhost"}
 :params {}
 :path-params {}
 :protocol "HTTP/1.1"
 :query-params {}
 :remote-addr "127.0.0.1"
 :request-method :post
 :scheme :http
 :server-name "localhost"
 :server-port 80
 :uri "/"
 :muuntaja/request nil
 :muuntaja/response #muuntaja.core.FormatAndCharset
 {:charset "utf-8" :format "application/json" :raw-format nil}}
===== POST request with hidden DELETE =====
POST handler
{:form-params {}
 :headers {"host" "localhost"}
 :params {"_method" "delete"}
 :path-params {}
 :protocol "HTTP/1.1"
 :query-params {"_method" "delete"}
 :query-string "_method=delete"
 :remote-addr "127.0.0.1"
 :request-method :delete
 :scheme :http
 :server-name "localhost"
 :server-port 80
 :uri "/"
 :muuntaja/request nil
 :muuntaja/response #muuntaja.core.FormatAndCharset
 {:charset "utf-8" :format "application/json" :raw-format nil}}

Lusia 2025-08-26T03:14:54.063389Z

I was expecting the second request which is the POST with hidden DELETE is going to hit the delete-handler, but it didn't