How to get the content of an incoming POST http request's :body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x42c3599b "HttpInputOverHTTP#42c3599b"] in a Compojure/Ring project?
I know that this :body is composed of a part named data whose MIME-type is text-plain and another part named excel whose MIME-type is application/excel.
I slurped the content of :body and it shows:
Parsing a binary stream manually would be difficult. Wrap your handler as follows:
(wrap-multipart-params handler options)
This middleware parses the body and populates :params parameters with parsed data as well.
See ring.middleware.multipart-params documentation for more details.
I was seeing it in Reitit, what fixed for me was to change the order of the middlewares so the exception-middleware is after the multipart/multipart-middleware.
:middleware [;; multipart
multipart/multipart-middleware
;; exception handling
exception-middleware]
You can find a basic example in the Clojure Cookbook (O'Reilly), which I highly recommend:
(ns ringtest
(:require
[ring.adapter.jetty :as jetty]
clojure.pprint))
;; Echo (with pretty-print) the request received
(defn handler [request]
{:status 200
:headers {"content-type" "text/clojure"}
:body (with-out-str (clojure.pprint/pprint request))})
(defn -main []
;; Run the server on port 3000
(jetty/run-jetty handler {:port 3000}))
Related
I have a server hosting my API. My API relies on data requested from a third-party API (Spotify). Here are the relevant parts of my API handler:
(ns myapp.api.handler
(:require
[compojure.api.sweet :refer :all]
[ring.util.http-response :refer [ok forbidden no-content not-found bad-request]]
[clj-spotify.core :as spotify]))
(defroutes api-routes
(api
{:middleware [wrap-api]
:swagger {:ui "/api-docs"
:spec "/swagger.json"
:data {:info {:title "My API"
:description "A description for My API"}
:consumes ["application/json"]
:produces ["application/json"]}}}
(context "/api" []
(context "/me" []
(PUT "/player" []
:query-params [device_id :- String]
(handle-player-put device_id))))))
As you'll be able to tell from my route handler, I'd essentially like to forward the response of the third-party API to my API. Here is the handler function, handle-player-put:
(defn handle-player-put [device-id]
(let [available-devices (-> (spotify/get-current-users-available-devices
{}
(lm/oauth-token :spotify))
:devices)]
(doseq [device available-devices]
(when (= (:id device) device-id)
(if (not (:is_restricted device))
(let [response (spotify/transfer-current-users-playback
{:device_ids [device-id]
:play false}
(lm/oauth-token :spotify))]
(case (-> response :error :status)
nil (no-content)
404 (do
(println "Playback response: 404")
(not-found "Spotify could not find the requested resource."))
{:status (-> response :error :status)
:headers {}
:body (-> response :error :message)})))))))
After a successful (spotify/transfer-current-users-playback) request, response binds to {}. An example of a response after an error looks like {:error {:status 502, :message "Bad gateway."}}
No matter whether transfer-current-users-playback is successful or not, I always get a 404 error (with body text Not Found [404]). What am I doing wrong?
doseq always returns nil so your handler returns nil - which is interpreted by compojure as “this handler won’t handle the request; skip to the next handler” and if no other handler handles the request you get a 404 not found.
You should not use (doseq … (when … expr))) if you need to return expr
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?
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.
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.
I need to consume a WSDL web service and the Java client-side code I've seen so far looks bloated and complicated. I was wondering whether a cleaner solution might exist in Clojure so that I may perhaps implement that part in Clojure and expose a simpler API to the Java code.
cd your_project_dir/src
wsimport -p some.import.ns http://.../service?wsdl
It would create ./some.import.ns/*.class. So you can just use them in your clojure project
(ns your.ns ...
(:import [some.import.ns some_WS_Service ...]))
(let [port (-> (some_WS_Service.)
.getSome_WS_ServicePort]
(... (.someMethod port) ...))
Check out paos: https://github.com/xapix-io/paos
Lightweight and easy-to-use library to build SOAP clients from WSDL files.
(require '[clj-http.client :as client])
(require '[paos.service :as service])
(require '[paos.wsdl :as wsdl])
(defn parse-response [{:keys [status body] :as response} body-parser fail-parser]
(assoc response
:body
(case status
200 (body-parser body)
500 (fail-parser body))))
(let [soap-service (wsdl/parse "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl")
srv (get-in soap-service ["BLZServiceSOAP11Binding" :operations "getBank"])
soap-url (get-in soap-service ["BLZServiceSOAP11Binding" :url])
soap-headers (service/soap-headers srv)
content-type (service/content-type srv)
mapping (service/request-mapping srv)
context (assoc-in mapping ["Envelope" "Body" "getBank" "blz" :__value] "28350000")
body (service/wrap-body srv context)
resp-parser (partial service/parse-response srv)
fault-parser (partial service/parse-fault srv)]
(-> soap-url
(client/post {:content-type content-type
:body body
:headers (merge {} soap-headers)
:do-not-throw true})
(parse-response resp-parser fault-parser)))