I am trying to server html over a ring based api server path . however everytime i hit the endpoint , i am getting this wierd error
java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap
at clojure.core$_cache_protocol_fn.invokeStatic(core_deftype.clj:583) ~[clojure-1.10.1.jar:?]
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:575) ~[clojure-1.10.1.jar:?]
at ring.core.protocols$eval18503$fn__18504$G__18494__18513.invoke(protocols.clj:8) ~[?:?]
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:106) ~[?:?]
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91) ~[?:?]
at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:95) ~[?:?]
at ring.util.servlet$update_servlet_response.invoke(servlet.clj:91) ~[?:?]
at ring.adapter.jetty$proxy_handler$fn__18623.invoke(jetty.clj:27) ~[?:?]
Here is what my api is returning.
{:status 200
:body (resource-response "index.html" {:root "public"})}
Whereas if i hit the index.html path directly it is accessible at this route
http://localhost:8080/index.html
You get the error No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap because as a body you return a PersistentArrayMap instead of something that can be encoded as the body of a Ring HTTP response.
resource-response already returns a full response map (a PersistentArrayMap):
(resource-response "index.html")
;; => {:status 200,
;; :headers
;; {"Content-Length" "0", "Last-Modified" "Mon, 16 Nov 2020 14:22:48 GMT"},
;; :body
;; #object[java.io.File 0x239d3777 "/index.html"]}
so no need to wrap it in {:status 200, :body ...} since it becomes {:status 200 :body {:status 200, ...}} which lead to that error. To fix it your API can directly return:
(resource-response "index.html" {:root "public"})
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 sending an index-handler on "/this-route":
(defn index-handler [req]
(assoc (resource-response "index.html" {:root "public"})
:headers {"Content-Type" "text/html; charset=UTF-8"}))
(def routes ["" {"/this-route" {:get index-handler}}]) ;; works
Which works fine.
But when I append anything to this-route, I'm unable to send index-handler even though I can still send a basic res/response:
(def routes ["" {"/this-route" {"" {:get index-handler} ;; doesn't work
"/something" {:get index-handler} ;; doesn't work
"/something-else" (res/response "some response") ;; works
}}])
I get the errors in the client side console showing an error in the index.html line, where I start my app:
<script type="text/javascript">myapp.system.go();</script>
and the error is "myapp is not defined."
Why might this be, and what am I doing wrong?
--- EDIT ---
This is the complete error in console:
Uncaught ReferenceError: myapp is not defined
at something-else:15
DevTools failed to load SourceMap: Could not load content for chrome-extension://gighmmpiobklfepjocnamgkkbiglidom/include.preload.js.map: HTTP error: status code 404, net::ERR_UNKNOWN_URL_SCHEME
Your routes work fine:
(let [routes ["" {"/this-route" {:get :some-handler}}]]
(is= (bidi/match-route routes "/this-route" :request-method :get)
{:handler :some-handler, :request-method :get}))
(let [routes ["" {"/this-route" {"" {:get :handler-1}
"/something" {:get :handler-2}
"/something-else" {:get :handler-3}}}]]
(is= (bidi/match-route routes "/this-route" :request-method :get)
{:handler :handler-1, :request-method :get})
(is= (bidi/match-route routes "/this-route/something" :request-method :get)
{:handler :handler-2, :request-method :get})
(is= (bidi/match-route routes "/this-route/something-else" :request-method :get)
{:handler :handler-3, :request-method :get}))
As cfrick said, you have somehow created a problem compiling and/or loading your code. That is where you need to look.
I have the following system.components middleware config, in which I'm using the ring.middleware wrap-cors, to allow for redirects to an external server:
(defn config []
{:http-port (Integer. (or (env :port) 5000))
:middleware [[wrap-defaults api-defaults]
wrap-with-logger
wrap-gzip
ignore-trailing-slash
[wrap-reload {:dir "../../src"}]
[wrap-trace :header :ui]
wrap-params
wrap-keyword-params
wrap-cookies
[wrap-cors :access-control-allow-headers #{"accept"
"accept-encoding"
"accept-language"
"authorization"
"content-type"
"origin"}
:access-control-allow-origin [#"https://some-url"]
:access-control-allow-methods [:delete :get
:patch :post :put]]
]})
And this is supposed to insert headers into every response. But instead, on a request from the client which leads to a redirect to https://some-url, I get the following error in the client browser:
Access to XMLHttpRequest at 'https://someurl' (redirected from 'http://localhost:5000/some-uri') from origin 'http://localhost:5000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Why aren't the correct headers in the response despite adding the middleware?
-- EDIT --
I've also tried the [jumblerg.middleware.cors] wrap-cors middleware like so:
(defn config []
{:http-port (Integer. (or (env :port) 5000))
:middleware [[wrap-defaults api-defaults]
wrap-with-logger
wrap-gzip
ignore-trailing-slash
[wrap-reload {:dir "../../src"}]
[wrap-trace :header :ui]
wrap-params
wrap-keyword-params
wrap-cookies
[wrap-cors #".*"]
]})
And have added the headers using liberator like so:
(defresource some-route [redirect-uri]
:available-media-types ["application/json"]
:allowed-methods [:post]
:post-redirect? true
:as-response (fn [d ctx]
;; added headers
(-> (as-response d ctx)
(assoc-in [:headers "Access-Control-Allow-Origin"] "*")
(assoc-in [:headers "Access-Control-Allow-Headers"] "Content-Type")
)
)
;; redirect uri
:location redirect-uri
)
But still get the ````No 'Access-Control-Allow-Origin' header is present on the requested resource.``` error
Try this library to (wrap-cors):
[jumblerg/ring-cors "2.0.0"]
like this:
(wrap-cors your-routes identity)
Note the third parameter is a function to determine if an origin is allowed (or a list of reg exp)
You might have to add a manual route though:
(OPTIONS "/yourendpoint" req {:headers {"Access-Control-Allow-Headers" "*"}})
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}))
I have a small compojure site, with the routes defined as such:
(defroutes example
(GET "/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (home)})
(GET "/*" (or (serve-file (params :*)) :next))
(GET "/execute/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (execute-changes)})
(GET "/status/" [] {:status 200
:headers {"Content-Type" "text/html"}
:body (status)})
(route/not-found "Page not found"))
When I try to load the project, I get this error:
java.lang.Exception: Unsupported binding form: (or (serve-file (params :*)) :next)
What am I doing wrong? I took most of this from scattered examples on the internet.
After adding the empty vector, I get this error:
java.lang.Exception: Unable to resolve symbol: serve-file in this context
I think you're missing a binding form:
(GET "/*" {params :params} (or (serve-file (params :*)) :next))
; ^- note the binding form