Get data from api using clojure and log result - clojure

I'm very new to clojure and I familiar with java and PHP. I have assign new clojure job to get data from outside API and do some process.
This my API request and i tried it using curl command
curl -i -X POST -H "Content-Type: application/json" -d "{\"uid\":\"uId\", \"param\":\"1\"}" http://localhost:8081/api/getData
result is - {"comp":true ,"yUi":"78.2555","xUi":"4.5233","id":"10"}
I have apply above process using clojure and i need log those return values.Could you please help me to do this using clojure
This is how i tried this
(defn- get-data
"api call"
[uId]
(try
(log/info (trs "start process: {0}" (str "uid" uId )))
(when-let [ui (client/post "http://localhost:8081/api/getData" {:form-params {:uid uId,:param "1"} :content-type :json })]
(log/info (trs "get data: {0} {1} {2} {3}" (:comp ui) (:yUi ui) (:xUi ui) (:id ui))))
(catch Throwable e
(log/error "api call failed:" (.getMessage e)))))

Related

Clojure liberator library doesn't send the JSON response

I'm pretty new in the Clojure webdev ecosystem, I want to send a JSON response with the POST method using the liberator API, I tried this:
(POST "/post/savecomment"
request
(resource
:allowed-methods [:post]
:available-media-types ["application/json"]
:handle-ok (fn [ctx]
(format (str "{body: %s a: 1 b: 4}"), "the body part"))))
All looks fine, there is no error message, I get a "201 Created" response from ring, but the JSON data is not send, in Chrome "response" tab is just empty. Need I to add something? BTW, I'm using compojure, not compojure-api.
I also tried:
(POST "/post/savecomment" request (resource
:allowed-methods [:post]
:available-media-types ["application/json"]
:available-charsets ["utf-8"]
:handle-ok (fn [_] (rep/ring-response {:status 200 :body "\"this is json\""}))
:post! (fn [ctx] (rep/ring-response {:status 666 :body "\"this is json\""}))
))
But no luck.
For 201 Created responses you need to define the handler :handle-created, e.g.
(POST "/post/savecomment"
request
(resource
:allowed-methods [:post]
:available-media-types ["application/json"]
:handle-created (fn [ctx]
(format (str "{body: %s a: 1 b: 4}"), "the body part"))))
The tutorial covers the fundamental concepts of liberator: https://clojure-liberator.github.io/liberator/tutorial/

response map is nil compojure

I just don't understand anything.
When I do curl -X GET --header 'Accept: application/json' 'http://localhost:3000/api/hello' from my swagger I get a nice answer {"result":1}.
However, when I try something like: http://localhost:3000/api/hello?criteria=drf, the server gives me java.lang.NullPointerException: Response map is nil. I am pretty sure it's not nil, since I still get my {"result":1} back on the screen.
But after that first error, every GET /hello request, without any query parameters, gives an error on server side, while still giving the right answer to the client?
And I need to restart server in order to make the error stop. Which works, until the first request with query params....
(ns swag.handler
(:require [compojure.api.sweet :refer :all]
[ring.util.http-response :refer :all]
[clojure.java.jdbc :as j]
[clojure.core.match :as match]
[clojure.spec.alpha :s s]
)
(def app
(api
{:swagger
{:ui "/"
:spec "/swagger.json"
:data {:info {:title "Trash"
:description "Compojure Api example"}
:tags [{:name "api", :description "some apis"}]}}}
(context "/api" []
:tags ["api"]
(GET "/plus" []
:return {:result Long}
:query-params [x :- Long, y :- Long]
:summary "adds two numbers together"
(ok {:result (+ x y)}))
(GET "/hello" []
:query-params [& z]
(let [criteria (:criteria z) values (:date z)]
(println z)
(ok {:result 1})))
))
)
If you're getting this message, check for favicon.ico request in the browser.
This may trigger a response map is null because no route is configured to handle it.
Issue for us was we needed trailing /s on the endpoint that was giving us this error (was looking at your q while we were debugging, but just solved).

Clojure, Compojure-api: Access Request headers

I am trying to implement request end point authentication. For that I want to access accessToken value from request headers.
My GET request end Point is
CURL Command
curl -X GET \
'http://localhost:3000/hello?id=10' \
-H 'accesskey: 23423sfsdfsdfsfg' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-H 'postman-token: f69b34e6-4888-ec31-5fbc-b734e176571b' \
-d '{
"artwork": {id" : 1}
}'
HTTP Command
GET /hello?id=10 HTTP/1.1
Host: localhost:3000
Content-Type: application/json
accessKey: 23423sfsdfsdfsfg
Cache-Control: no-cache
Postman-Token: b974719d-5e1d-4d68-e910-e9ca50562b2f
My Code for GET Method Implementation
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id]
(log/info "Function begins from here")
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201}))
(ok data)))
I want to fetch accessKey: 23423sfsdfsdfsfg from request header.
Is there any way to get the value and use in my GET Method?
I am using POSTMAN to test all API end points.
Compojure has custom destructuring syntax (i.e., different from Clojure proper) for the parameters. You can bind the whole request map using keyword :as
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id :as request]
If you want only request headers, the following should work
(defapi app
(GET ["/hello/:id", :id #"[0-9]+" ] [id :as {:headers headers}]
Note that this still allows you to bind path parameter id.
The Compojure Sweet API functions like [compojure.api.sweet :refer [defroutes GET PUT context]] let us bind the whole request or bind select headers. In the snippet below [:as request] makes the whole request available to me.
(GET
"/download/:id"
[:as request]
:header-params [{x-http-request-id :- X-Http-Request-Id nil}]
:path-params [id :- (describe String "The encoded id of the image")]
:summary "Download the image bytes"
:description "This endpoint responds 307 - Temporary Redirect to a cacheable presigned S3 URL for the actual bytes."
(let [http-response (->> request
walk/keywordize-keys
util/extract-base-url
(transform/generate-resource-url (util/decode-key id))
status/temporary-redirect)
expire-time (-> 3 hours from-now coerce/to-date ring-time/format-date)]
(log/infof "x-http-request-id is %s" x-http-request-id)
(response/header http-response "Expires" expire-time)))
The vector beginning :header-params [{x-http-request-id :- X-Http-Request-Id nil}] makes the value of the "X-HTTP-REQUEST-ID" header in the request available to my function directly as x-http-request-id.
The squiglies thing {...} makes the presence of x-http-request-id header optional in the request.
The :- X-Http-Request-Id nil stuff gives it a Schema which is defined somewhere else like (s/defschema X-Http-Request-Id (rss/describe String "Request ID for tracing calls")).
Once you've got those kids bound to names you just work with the names. The compojure folks don't do a great job at documenting everything you can do there. Poke around their examples and you'll find stuff like this.
I have figured out solution to the issue. Please check solution here.
(ns clojure-dauble-business-api.core
(:require [compojure.api.sweet :refer :all]
[ring.util.http-response :refer :all]
[clojure-dauble-business-api.logic :as logic]
[clojure.tools.logging :as log]
[clojure-dauble-business-api.domain.artwork]
[cheshire.core :as json])
(:import [clojure_dauble_business_api.domain.artwork Artwork]))
(defapi app
(GET ["/hello/:id", :id #"[0-9]+"] [id :as request]
(log/info "Function begins from here" request)
(def jsonString (json/generate-string (get-in request [:headers])))
(log/info "Create - Access Key is " (get-in (json/parse-string jsonString true) [:accesskey]))
(def artworkData (logic/artwork-id (->> id (re-find #"\d+") Long/parseLong)))
(def data (if (not-empty artworkData)
{:data artworkData :status 200}
{:data [] :status 201})))
I don't think it is smart way.
Can you anybody look into my solution and tell me Is there another way to get accesskey?

No 'Access-Control-Allow-Origin' header is present when origin is allowed in Pedestal

When I try and request a resource from a cljs app (running on http://localhost:3000) to my Pedestal server (running on http://localhost:8080) I get the below error. I would like to allow CORS from http://localhost:3000:
XMLHttpRequest cannot load http://localhost:8080/db/query. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
I am using cljs-http to send the request from the client. The request looks something like this:
(defn load-server-data
[]
(go
(let [q (<! (http/post "http://localhost:8080/db/query"
{:edn-params {:query '[:find ?rep ?last
:where
[?rep :sales-rep/first-name ?last]]}}))]
(println "q" q))))
The route for /db/query looks like this:
(defroutes routes
[[["/db"
{:post handlers/db-post}
["/query" {:post handlers/db-query}
^:interceptors [interceptors/edn-interceptor]]]]])
This is the handler for /db/query:
(defn db-query
[req]
(let [edn-params (:edn-params req)
q (:query edn-params)
args (:args edn-params)
q-result (apply d/q q (d/db conn) args)]
{:status 200
:body (pr-str q-result)}))
To run the server I execute this function in the REPL.
(defn run-dev
"The entry-point for 'lein run-dev'"
[& args]
(println "\nCreating your [DEV] server...")
(-> service/service
(merge {:env :dev
::server/join? false
::server/routes #(deref #'service/routes)
::server/allowed-origins {:creds true :allowed-origins (constantly true)}})
server/default-interceptors
server/dev-interceptors
server/create-server
server/start))
There does not seem to be much information around CORS for Pedestal. I have looked at the cors example but it seems to just work while mine does not. Is there another interceptor I need to add to my routes or some sort of configuration setting that I am missing here?
I have figured out the problem. It turns out that an error was being thrown, however, it was getting swallowed and hidden from my debugger. Simply adding a try catch around my handler function fixes the problem.
(defn db-query
[req]
(try
(let [edn-params (:edn-params req)
q (:query edn-params)
args (:args edn-params)
q-result (apply d/q q (d/db conn) args)]
{:status 200
:body (pr-str q-result)})
(catch Exception ex
{:status 400
:body "Not authorized"})))
My original response:
The purpose of CORS is to limit the origin of the requests. You have
to purposely tell it where requests can come from. This will fix it.
(def service {;other config stuff
io.pedestal.http/allowed-origins ["http://localhost:3000"]}
It appears this is a duplicate question. Apparently javascript ajax requests are by definition limited to single origin. That code would work in production only if the GET request is made by clj-http or http-kit on the ring server that spawn http://localhost:3000 and then a cljs-http ajax request is made to that same ring server on port 3000. I still don't know why your run-dev doesn't work, but if you're calling lein with run, this is definitely what's happening.

NullPointerException when lein ring server on Aleph+Ring

I am trying to run Aleph on top of Ring and use lein ring server for shorter feedback loop.
When I'm invoking lein ring server everything seems to be fine but when I point my browser to an url I get a nasty NullPointerException with the stack trace below.
However, when I run (al.app/start 3006) then no NLP shows up.
The whole project is available on GitHub.
What am I doing wrong?
core.clj:107 lamina.core/enqueue
app.clj:39 al.app/hello-handler
http.clj:79 aleph.http/wrap-aleph-handler[fn]
response.clj:27 compojure.response/eval1328[fn]
response.clj:10 compojure.response/eval1289[fn]
core.clj:93 compojure.core/make-route[fn]
core.clj:39 compojure.core/if-route[fn]
...
I am using aleph 0.3.2 and that's my code:
(ns al.app
(:use
aleph.http
lamina.core
compojure.core
ring.middleware.keyword-params)
(:require [compojure.route :as route]))
(defn hello-handler
"Our handler for the /hello path"
[ch request]
(let [params (:route-params request)
name (params :name)]
(enqueue ch
{:status 200
:headers {}
:body (str "Hello " name)})))
(defroutes my-routes
(GET ["/hello/:name", :name #"[a-zA-Z]+"] {} (wrap-aleph-handler hello-handler))
(route/not-found "Page not found"))
(defn start
"Start our server in the specified port"
[port]
(start-http-server (wrap-ring-handler my-routes) {:port port}))
The Lein-Ring plugin uses an embedded Jetty web server, while Aleph uses the asynchronous Netty web server. The aleph.http/wrap-aleph-handler middleware is designed only to work with a Netty server started with aleph.http/start-http-server function.