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).
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?
How do I make this test pass:
(ns imp-rest.parser-test-rest
(:require [clojure.test :refer :all])
(:require [ring.mock.request :as mock] )
(:require [imp-rest.web :as w]))
(deftest test-parser-rest
(testing "put settings"
(w/app
(mock/request :put "/settings/coordinateName" "FOO" ))
(let [response (w/app (mock/request :get "/settings"))]
(println response )
(is (= (get (:body response) :coordinateName) "FOO")))))
it fails with:
FAIL in (test-parser-rest) (parser_test_rest.clj:30)
put settings
expected: (= (get (:body response) :coordinateName) "FOO")
actual: (not (= nil "FOO"))
Here's my handler:
(ns imp-rest.web
(:use compojure.core)
(:use ring.middleware.json-params)
(:require [clj-json.core :as json])
(:require [ring.util.response :as response])
(:require [compojure.route :as route])
(:require [imp-rest.settings :as s]))
(defn json-response [data & [status]]
{:status (or status 200)
:headers {"Content-Type" "application/json"}
:body (json/generate-string data)})
(defroutes handler
(GET "/settings" []
(json-response (s/get-settings)))
(GET "/settings/:id" [id]
(json-response (s/get-setting id)))
(PUT "/settings" [id value]
(json-response (s/put-setting id value)))
(route/not-found "Page not found") )
(def app
(-> handler
wrap-json-params))
which exposes this map (of settings):
(ns imp-rest.settings)
(def settings
(atom
{:coordinateName nil
:burnin nil
:nslices nil
:mrsd nil
}))
(defn get-settings []
#settings)
(defn get-setting [id]
(#settings (keyword id)))
(defn put-setting [id value]
(swap! settings assoc (keyword id) value)
value)
and the entry point:
(ns imp-rest.core
(:use ring.adapter.jetty)
(:require [imp-rest.web :as web]))
(defn -main
"Entry point"
[& args]
(do
(run-jetty #'web/app {:port 8080})
);END;do
);END: main
Now when I 'lein run' I can make a (working) request like this:
curl -X PUT -H "Content-Type: application/json" \
-d '{"id" : "coordinateName", "value" : "FOO"}' \
http://localhost:8080/settings
which is what I try to mock with the test. Any help appreciated.
If you want to have :id in your PUT /settings/:id route accepting body in format {"value": "..."}, you need to change your routes definition:
(defroutes handler
(GET "/settings" []
(json-response (s/get-settings)))
(GET "/settings/:id" [id]
(json-response (s/get-setting id)))
(PUT "/settings/:id" [id value]
(json-response (s/put-setting id value)))
(route/not-found "Page not found"))
And change how you call your PUT endpoint in the test:
(w/app
(-> (mock/request
:put
"/settings/coordinateName"
(json/generate-string {:value "FOO"}))
(mock/content-type "application/json")))
What was changed?
:id in your PUT URL route definition (/settings -> /settings/:id)
Your PUT request didn't send a correct request and content type.
If you want to have a PUT /settings route expecting {"id": "...", "value": "..."} request body, then you need to change how you create a mock request:
(w/app
(-> (mock/request
:put
"/settings"
(json/generate-string {:id "coordinateName" :value "FOO"}))
(mock/content-type "application/json"))
Your curl request specifies the parameters as JSON in the body of the PUT request, but your mock request tries to use URL parameters.
There are two options to resolve this:
compojure can automatically translate parameters, but only when the relevant middleware is present -- you have wrap-json-params added to your handler, but you're missing wrap-params. The answer from Piotrek Bzdyl amounts to making these params explicit in the compojure routes.
Alternatively, you can add the ID/value pair as JSON in the body of the mock request using request.mock.body.
I've got a local server running on port 8545 which listen to JSON-RPC requests. I can call it using curl like this:
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xf54c19d9ef3873bfd1f7a622d02d86249a328f06", "latest"],"id":1}' http://localhost:8545
What would be the equivalent call from Clojure? Do I need to add some external libraries to the project.clj?
I think you should try http-kit.
Also you will need some library for json (data.json or cheshire)
So add to your project.clj following dependencies:
[http-kit "2.1.18"]
[org.clojure/data.json "0.2.6"]
And try this
(ns your-ns
(:require [org.httpkit.client :as http]
[clojure.data.json :as json]))
(let [url "http://localhost:8545"
body (json/write-str
{:jsonrpc "2.0"
:method "eth_getBalance"
:params ["0xf54c19d9ef3873bfd1f7a622d02d86249a328f06" "latest"]
:id 1})
options {:body body}
result #(http/post url options)]
(prn result))
I had a similar use case so I created a small Clojure library for making JSON-RPC calls. With this, you can do,
(ns example.core
(:require [json-rpc.core :as rpc]))
(with-open [channel (rpc/open "http://localhost:8545")]
(rpc/send! channel "eth_blockNumber" ["latest"]))
;; => {:result "0x14eca", :id "6fd9a7a8-c774-4b76-a61e-6802ae64e212"}
, and the boilerplate will be handled for you.
I'm trying to get started with Clojure and Clojurescript by implementing a simple web app. Things are going pretty good so far and reading from different tutorials I've come up with the code below:
core.clj:
(ns myapp.core
(:require [compojure.core :as compojure]
[compojure.handler :as handler]
[compojure.route :as route]
[myapp.controller :as controller]))
(compojure/defroutes app-routes
(compojure/GET "/" [] controller/index)
(route/resources "/public")
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
controller.clj:
(ns myapp.controller
(:use ring.util.response)
(:require [myapp.models :as model]
[myapp.templates :as template]))
(defn index
"Index page handler"
[req]
(->> (template/home-page (model/get-things)) response))
templates.clj:
(ns myapp.templates
(:use net.cgrand.enlive-html)
(:require [myapp.models :as model]))
(deftemplate home-page "index.html" [things]
[:li] (clone-for [thing things] (do->
(set-attr 'data-id (:id thing))
(content (:name thing)))))
The problem is I can't display non-ascii characters on the page and I don't know how to set HTTP headers on a page.
I see solutions like this but I simply can't figure out where place them in my code:
(defn app [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
P.S: Any suggestions about style and/or code organization are welcome.
Use ring.util.response:
(require '[ring.util.response :as r])
Then on your index function:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")))
You can chain other actions on the response such as set-cookie and whatnot:
(defn index
"Index page handler"
[req]
(-> (r/response (->> (template/home-page (model/get-things)) response))
(r/header "Content-Type" "text/html; charset=utf-8")
(r/set-cookie "your-cookie-name"
"" {:max-age 1
:path "/"})))