I am using clojure liberator to expose my services as REST service,I have a POST request, Below is the code, I could do the process on calling the service as POST, but I want to send back the event id as response of the POST, Can anyone help
(defresource send-event-resource
:method-allowed? (request-method-in :post)
:available-media-types ["text/plain"]
:post! (fn [context]
(workers/send-event context)))
Raised an issue in liberator, got the response from there
https://github.com/clojure-liberator/liberator/issues/61
Put the event id in to context map and look it up from handle-ok:
(defresource send-event-resource
:method-allowed? (request-method-in :post)
:available-media-types ["text/plain"]
:post! (fn [context] {::event-id (workers/send-event context)})
:handle-ok ::event-id)
The above code makes use of the fact that a clojure keyword is a function that when applied to the context map will look up "itself".
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 have POSTed data to a Pedestal endpoint "/my-post. I have routed that end point as such:
[[["/" {:get landing} ^:interceptors [(body-params/body-params) ...]
["/my-post {:post mypost-handler}
....
So to my mind this means that the body-params interceptor will fire for /my-post too.
In mypost-handler I have:
(defn mypost-handler
[request]
****HOW TO ACCESS THEN FORM DATA HERE ****
)
How do I now access the form data here? I can see from printing the request that I have a #object[org.eclipse.jetty.sever.HttpInputOverHTTP..] which will clearly need further processing before it is useful to me.
(I must say, the documentation for Pedestal is pretty sketchy at best...)
Something like this should work. Note the body-params interceptor on the mypost-handler route
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; json-params is the posted json, so
;; (:name json-params) will be the value (i.e. John) of name property of the posted json {"name": "John"}
;; handle request
{:status 200
:body "ok"})
(defroutes routes
[[["/mypost-handler" {:post mypost-handler}
^:interceptors [(body-params/body-params)]
]
]])
The mypost-handler is acting as a Ring handler, i. e. it should accept a Ring request map and return a Ring response map. Thus, you can expect a typical Ring request structure:
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; handle request
{:status 200
:body "ok"})
Here's more relevant info on defining such handlers in your route tables.
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.
I'm struggling with understanding how to properly use sessions in Compojure/Ring.
Some of the examples I have come across:
https://github.com/brentonashworth/sandbar-examples/blob/master/sessions/src/sandbar/examples/session_demo.clj
http://rjevans.net/post/2628238502/session-support-in-compojure-ring
https://github.com/ring-clojure/ring/wiki/Sessions
These examples do not help me understand how to integrate sessions into something like a login mechanism.
(defroutes main-routes
(POST "/login" request (views/login request)))
;; views.clj
(defn login
[request]
(let [{params :params} request
{username :username} params
{password :password} params
{session :session} request]
(if (db/valid-user? username password)
(-> (logged-in request)
(assoc-in [:session :username] username))
(not-logged-in))))
I realize that this isn't correct as logged-in returns hiccup/html and I believe that the ring response map isn't added on until after the route is fully evaluated. This seems to be why all of the above examples show sessions being added to a complete response map. But, one of the features of Compojure to begin with was abstracting away the requirement of the development having to work with the response map. Therefore I feel like I must me missing something.
What would the correct way be to do the above?
If (logged-in request) returns the contents that should be rendered, then instead of associating :session :username onto the results of logged-in, you can return a proper response map:
{:body (logged-in request)
:session (assoc session :username username)}
:status, :headers, etc. have decent defaults if you do not provide them.
I have a clojure / compojure webapp with the following routes
(defroutes my-routes
(GET "/app/preview" request (my-preview-function request))
(ANY "*" request (str "ANY page <br>" (request :params))))
The preview GET request is made with a couple of parameters. I find this works most of the time but sometimes the /ebook/preview is not found and processing drops to the ANY route, in which case the output is similar to this,
ANY page
{:* "/app/preview", :section "50", :id "48"}
Can anyone suggest what might cause the /ebook/preview request to be skipped? It is definitely a GET request being made; the HTML does not have a POST for the /app/preview URL and to be doubly sure I added a POST route for /app/preview and that was not being hit.
JAR versions:
Clojure 1.2
compojure-0.6.2
ring-core-0.3.7
jetty-6.1.14
ring-jetty-adapter-0.3.1
ring-servlet-0.3.1jar
servlet-api-2.5-6.1.14
Routes are wrapped as follows
(require '[compojure.handler :as handler])
(defn wrap-charset [handler charset]
(fn [request]
(if-let [response (handler request)]
(if-let [content-type (get-in response [:headers "Content-Type"])]
(if (.contains content-type "charset")
response
(assoc-in response
[:headers "Content-Type"]
(str content-type "; charset=" charset)))
response))))
(def app (-> my-routes
handler/site
wrap-stateful-session
(wrap-charset "utf-8")
(wrap-file "public")))
(defn run []
(run-jetty (var app) {:join? false :port 8080}))
If you're trying to figure out what request is causing the problems, stop throwing away the request map with (request :params) and just have a look at request. That will give you a map with all the information Compojure has; you can inspect it, and pass it back into your routes later to observe what happens (after you make some changes, say).
If
(my-preview-function request)
returns nil, then the routing will try the next route. Take a look at (source GET) and see how it matches (or doesn't) your route.