How do I get the URL params into the request map in Pedestal? I am assuming that this needs the use of an interceptor? However the Pedestal documentation (or severe lack thereof) does not make this at all clear. Thanks.
Query parameters are parsed automatically by Pedestal, and the resulting map is placed in the request map under the :query-params key.
As a simple example, start with the pedestal-service template and use the following definitions:
(defn home-page
[request]
(ring-resp/response (format "Hello with params: %s" (:query-params request))))
(defroutes routes
[[["/" {:get home-page}]]])
Now if you browse to http://localhost:8080/?param=true&other=1234, you should see Hello world with paramters: {:param "true", :other "1234"}.
Related
I have a simple route and middleware setup with compojure/swagger that is utilizing a ring middleware.
(POST "/worlds" [request]
:body-params [name :- String]
:header-params [token :- String]
:middleware [wrap-api-auth]
:summary "Creates a new world with 'name'"
(ok (do-something-with-user)
(defn wrap-api-auth [handler]
(fn [request]
(let
[token (get (:headers request) "token")
user (db/get-user-by-token token)]
(if user
(handler request) ; pass to wrapped handler
(unauthorized {:error "unauthorized"})))))
This defines a simple route with some basic auth. The token passed in the header param is used to make a database query, which either returns a user and continues, or returns false and fails.
What I'm trying to accomplish is to pass the returned user back out so that I can use it later. I haven't had any luck, as I don't really know where I would try to add it to that I could access it later. I've tried to assoc it with the request but it doesn't appear that I can access it later. The ideal situation is I'm able to pass it to the do-something-with-user function.
Using assoc to add some data to the request should totally work.
You can find an example with some code that is very close to what I have in production at https://gist.github.com/ska2342/4567b02531ff611db6a1208ebd4316e6#file-gh-validation-clj-L124
In essence, that middleware calls
(handler (assoc request
:validation {:valid true
:validation valid?}))
So for your case, the following should just work:
(handler (assoc request
:user user))
If I understood correctly, the destructuring syntax you use is from compojure-api. According to the example at https://github.com/metosin/compojure-api/wiki/Middleware I'd say that the middleware set via the :middleware key behaves just as expected and you should be able to extract the :user from the request that ultimately ends up in your route.
So, just pass the request on to the do-something-with-user function:
(POST "/worlds" request
:body-params [name :- String]
:header-params [token :- String]
:middleware [wrap-api-auth]
:summary "Creates a new world with 'name'"
(ok (do-something-with-user request))
It should contain the :user you assoced into it in your middleware function. Note the missing brackets around request like mentioned in the comments to this answer.
I just created my RESTful service through Luminus using this doc: http://www.luminusweb.net/docs/services.md
Apparently Compojure-API uses Schema library to map the query parameters. However, I would like to be able to get all the query parameters as a single map instead. For instance:
From this GET /api/myapp?color=green&shape=round&height=100
to this {:color "green", :shape "round", :height "100"}
thanks!
Got it.
I used this as an example (GET "/test" {params :params} (str params)). params is a map with keys and values for the given query params.
I'm using wrap-with-logger (from ring.middleware.logger) and wrap-params (from ring.middleware.params) middlewares in my application. Any simple way to filter sensitive parameters (password, credit card number etc.) from logs?
You could also consider migrating to ring-logger which includes a feature to redact sensitive information:
By default, ring-logger will redact an authorization header or any param named password (at any nesting level). If you want ring-logger to redact other params you can configure the redact-keys option:
(wrap-with-logger app {:redact-keys #{:senha :token})
Ring-logger will walk through the params and headers and redact any key whose name is found in that redact-keys set.
There's also ring-logger-onelog that should make it very easy to migrate from ring.middleware.logger to ring-logger
You may implement custom pre-logger that filters request according to your needs.
See the following:
(use 'ring.adapter.jetty)
(require '[ring.middleware.logger :as logger])
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/html"}
:body "Hello World"})
(run-jetty
(logger/wrap-with-logger
handler
:pre-logger
(fn [options req]
;; Filtering goes here
(let [filtered-req (filter-sensitive-data req)]
((:info options) "Filtered requrest is: " filtered-req))))
{:port 8080})
Note, while documentation claims that pre-logger accepts only one argument, truly it is two-arg function.
I'm using Liberator, and am having a hard time getting my POSTed data into a map using with keywords as the keys. Here is my resource, with a few printlines for testing:
(defresource finish_validation
:allowed-methods [:post]
:available-media-types ["application/json"]
:post! (fn [context]
(let [params (slurp (get-in context [:request :body]))
mapped_params (cheshire/parse-string params)]
(println (type params))
(println (type mapped_params))
(validation/finish mapped_params)))
:handle-created (println ))
For testing, I'm posting the data using curl:
curl -H "Content-Type: application/json" -X POST -d '{"email":"test#foo.com","code":"xyz"}' http://localhost:8080/validate
cheshire converts the params into a map, but the keys are not keywords: I get {email test#foo.com, code xyz} as the output, instead of the hoped-for {:email test#foo.com, :code xyz}.
Should I be doing something differently? Is this even the right approach to getting the data?
You need to leverage ring's wrap-params middleware, coupled with the wrap-keyword-params middleware which converts the params map to a key map.
(ns your.namespace
(:require [ring.middleware.params :refer [wrap-params]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]))
(def app
(-> some-other-middleware
wrap-keyword-params
wrap-params))
Using this middleware with wrap-params converts params to use keys. After adding this middleware, you can access your params from the request map, like so (-> ctx :request :params). No need to convert them per request. This will handle all requests.
I just had to put "true" at the end of the call to the cheshire function, and the keys are returned as keywords:
(cheshire/parse-string params true)
Depending on your requirements, you can simplify the handling of your post data using various ring middleware. This will allow you to process your json data in one place and eliminate the need to have duplicate data processing in each of your handlers/resource definitions. There are a few ways of doing this. You can have the json data added as keywordized parameters in the params map or a json-params map. Have a look at ring.middleware.format and ring.middleware.json.
Suppose I have this handler:
(defroutes routes
(DELETE "/books" [id] (delete-book id)))
What can I do to make this app return HTTP 404 when request does not contain ID?
Firstly, you could make the id a part of the URI, which seems nice and RESTful and would allow you to use the route syntax to impose your condition:
(GET ["/books/:id" :id #"[0-9]+"] [] ...)
If you do prefer to use a parameter, something like
(if-not id
(ring.util.response/not-found body-for-404)
...)
should work in the next Ring version, though this particular function has not been released yet (it simply returns {:status 404 :headers {} :body the-body} though).
Also,
(when id
...)
would result in the equivalent of a route match failure and the remaining routes would be tried; then you could use
(compojure.route/not-found body-for-404)
as the final route which would always match.
Finally, if you wish to apply filtering to a large group of Compojure handlers, you may wish to combine them into a single handler with Compojure's defroutes or routes (the latter is a function) and wrapping them in a piece of middleware:
(defn wrap-404 [handler]
(fn wrap-404 [request]
(when (-> request :params :id)
(handler request))))
You can then include the wrapped handler as an entry in routes / defroutes forms.