开发者

How to parse URL parameters in Clojure?

开发者 https://www.devze.com 2023-03-18 11:39 出处:网络
If I have the request \"size=3&mean=1&sd=3&type=pdf&distr=normal\" what\'s the idiomatic way of writing the function (defn request->map [request] ...) that takes this request and

If I have the request "size=3&mean=1&sd=3&type=pdf&distr=normal" what's the idiomatic way of writing the function (defn request->map [request] ...) that takes this request and returns a map {:size 3, :mean 1, :sd 3, :type pdf, :distr normal}

Here is my attempt (using clojure.walk and clojure.string):

(defn request-to-map
   [request]
   (keywordize-keys
      (apply hash-ma开发者_高级运维p
             (split request #"(&|=)"))))

I am interested in how others would solve this problem.


Using form-decode and keywordize-keys:

(use 'ring.util.codec)
(use 'clojure.walk)

(keywordize-keys (form-decode "hello=world&foo=bar"))

{:foo "bar", :hello "world"}


Assuming you want to parse HTTP request query parameters, why not use ring? ring.middleware.params contains what you want.

The function for parameter extraction goes like this:

(defn- parse-params
  "Parse parameters from a string into a map."
  [^String param-string encoding]
  (reduce
    (fn [param-map encoded-param]
      (if-let [[_ key val] (re-matches #"([^=]+)=(.*)" encoded-param)]
        (assoc-param param-map
          (codec/url-decode key encoding)
          (codec/url-decode (or val "") encoding))
         param-map))
    {}
    (string/split param-string #"&")))


You can do this easily with a number of Java libraries. I'd be hesitant to try to roll my own parser unless I read the URI specs carefully and made sure I wasn't missing any edge cases (e.g. params appearing in the query twice with different values). This uses jetty-util:

(import '[org.eclipse.jetty.util UrlEncoded MultiMap])

(defn parse-query-string [query]
  (let [params (MultiMap.)]
    (UrlEncoded/decodeTo query params "UTF-8")
    (into {} params)))

user> (parse-query-string "size=3&mean=1&sd=3&type=pdf&distr=normal")
{"sd" "3", "mean" "1", "distr" "normal", "type" "pdf", "size" "3"}


Can also use this library for both clojure and clojurescript: https://github.com/cemerick/url

user=> (-> "a=1&b=2&c=3" cemerick.url/query->map clojure.walk/keywordize-keys)
{:a "1", :b "2", :c "3"}


Yours looks fine. I tend to overuse regexes, so I would have solved it as

(defn request-to-keywords [req]
  (into {} (for [[_ k v] (re-seq #"([^&=]+)=([^&]+)" req)]
    [(keyword k) v])))

(request-to-keywords "size=1&test=3NA=G")

{:size "1", :test "3NA=G"}

Edit: try to stay away from clojure.walk though. I don't think it's officially deprecated, but it's not very well maintained. (I use it plenty too, though, so don't feel too bad).


I came across this question when constructing my own site and the answer can be a bit different, and easier, if you are passing parameters internally.

Using Secretary to handle routing: https://github.com/gf3/secretary

Parameters are automatically extracted to a map in :query-params when a route match is found. The example given in the documentation:

(defroute "/users/:id" [id query-params]
  (js/console.log (str "User: " id))
  (js/console.log (pr-str query-params)))

(defroute #"/users/(\d+)" [id {:keys [query-params]}]
  (js/console.log (str "User: " id))
  (js/console.log (pr-str query-params)))

;; In both instances...
(secretary/dispach! "/users/10?action=delete")
;; ... will log
;; User: 10
;; "{:action \"delete\"}"


You can use ring.middleware.params. Here's an example with aleph:

user=> (require '[aleph.http :as http])
user=> (defn my-handler [req] (println "params:" (:params req)))
user=> (def server (http/start-server (wrap-params my-handler)))

wrap-params creates an entry in the request object called :params. If you want the query parameters as keywords, you can use ring.middleware.keyword-params. Be sure to wrap with wrap-params first:

user=> (require '[ring.middleware.params :refer [wrap-params]])
user=> (require '[ring.middleware.keyword-params :refer [wrap-keyword-params])
user=> (def server 
         (http/start-server (wrap-keyword-params (wrap-params my-handler))))

However, be mindful that this includes a dependency on ring.

0

精彩评论

暂无评论...
验证码 换一张
取 消