Serve static favicon file in Compojure - clojure

According to Compojure:
Compojure does not serve static files by default, nor does it
automatically deal out 404s when no route matches.
So to deal with that, we have to set these Public Vars
files
not-found
resources
And this is how we currently set it:
(defroutes app-routes
(route/files "/" {:root "path/to/public"})
(route/resources "/")
(route/not-found "Not Found"))
It worked as expected when most of the static files are accessed through the web browser.
e.g.
http://localhost:3000/img/icon.png
But the problem is, it doesn't work on favicon files.
e.g.
http://localhost:3000/img/favicon.ico
It treats this as a different call which it should be serve as a static file.
Response to to the CURL I run:
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /img/favicon.ico HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 28 Jul 2015 19:05:24 GMT
< Last-Modified: Thu, 28 May 2015 14:51:16 +0000
< Content-Length: 1106
< Content-Type: image/x-icon
* Server Jetty(7.6.13.v20130916) is not blacklisted
< Server: Jetty(7.6.13.v20130916)

files, resources, and not-found are functions, not variables that you set. When you call (route/files "/" {:root "path/to/public"}), a route for resolving URLs under "/" as static files under "path/to/public" is returned.
defroutes defines a collection of routes. These routes are tried in the order they are listed, until the first one that returns a response.
If you add a route (GET "/:slug" [* :as req slug] (search req slug)) before the others, then any URL other than "/" will be handled by this new route — including the favicon request. On the other hand, if you add it just before the not-found route, then it should work.
Also, if there isn't a static file that matches the request, then the files route will fail and the next one will be tried. So you should also check that favicon.ico actually exists and is in the img sub-directory.

umm...
This works for me, (wherever you define/declare your routes):
In the namespace section:
[ring.util.response :refer [resource-response]]
Down below (at least above (def app ):
(defn wrap-return-favicon [handler]
(fn [req]
(if (= [:get "/favicon.ico"] [(:request-method req) (:uri req)])
(resource-response "favicon.ico" {:root "public/img"})
(handler req))))
then in:
(def app
(-> routes
...
wrap-return-favicon
wrap-stacktrace))

Related

wrap-cors middleware not working with system.components

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 use optional query parameters in Yada?

I'm making a toy API using the Yada library in Clojure. It searches a database for city names starting with the given characters and returns some info about it.
I want a URI of the form: /cities/:name?count=:count so for example /cities/ber?count=4 will return the top 4 matches. But I also want /cities/ber without the ?count= parameter to return a default number of results (say just the first).
I've defined my route and yada handler like this:
(defn city-search-fn
[ctx]
(let [name (get-in ctx [:parameters :path :name])
count (get-in ctx [:parameters :query :count] 1)]
(city->geoposition name count)))
(def cities (yada/handler (yada/resource
{:methods
{:get
{:parameters {:path {:name String}
:query {:count Long}}
:produces ["application/json"
"application/edn"]
:response city-search-fn}}})))
(def routes
[["/cities/" :name] cities])
(def server
(yada/listener routes {:port 30000}))
This works fine if I supply the ?count= query parameter:
$ curl -i 'http://localhost:30000/cities/ber?count=2'
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 259
Content-Type: application/json
Vary: accept
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Mon, 09 Sep 2019 16:01:45 GMT
[{"name":"Berlin","state":"Berlin","countrycode":"DE","timezone":"Europe/Berlin","latitude":52.52437,"longitude":13.41053},{"name":"Berbera","state":"Woqooyi Galbeed","countrycode":"SO","timezone":"Africa/Mogadishu","latitude":10.43959,"longitude":45.01432}]
But I get status 400 ({:status 400, :errors ([:query {:error {:count missing-required-key}}])}) if I don't supply it:
$ curl -i 'http://localhost:30000/cities/ber'
HTTP/1.1 400 Bad Request
Content-Length: 77
Content-Type: text/plain;charset=utf-8
Server: Aleph/0.4.4
Connection: Keep-Alive
Date: Mon, 09 Sep 2019 16:06:56 GMT
{:status 400, :errors ([:query {:error {:count missing-required-key}}])}
The documentation of yada says it supports optional query parameters using the "schema" library. So I found in schema's documentation that there exists a schema.core/maybe function. I tried to modify my yada resource as follows:
:parameters {:path.....
:query (schema/maybe {:count Long})}
this doesn't work (same 400 error).
Then I tried:
:parameters {:path.....
:query {:count (schema/maybe Long)}}
this also didn't work.
So my question is: what is the correct way to have an optional query parameter in yada?
To answer my own question, digging more into Schema documentation, here is the correct way:
:parameters {:path.....
:query {(schema/optional-key :count) Long}}
The key itself needs to be marked as optional.

How can I explicitly set content type on compojure response?

I am playing with compojure-api and am blocked at trying to manage Content-Type for my simple webapp. What I want is to emit an HTTP response that is just plain/text, but somehow Compojure-API keeps setting it to "application/json".
(POST "/echo" []
:new-relic-name "/v1/echo"
:summary "info log the input message and echo it back"
:description nil
:return String
:form-params [message :- String]
(log/infof "/v1/echo message: %s" message)
(let [resp (-> (resp/response message)
(resp/status 200)
(resp/header "Content-Type" "text/plain"))]
(log/infof "response is %s" resp)
resp))
but curl shows the server responded Content-Type:application/json.
$ curl -X POST -i --header 'Content-Type: application/x-www-form-urlencoded' -d 'message=frickin compojure-api' 'http://localhost:8080/v1/echo'
HTTP/1.1 200 OK
Date: Fri, 13 Jan 2017 02:04:47 GMT
Content-Type: application/json; charset=utf-8
x-http-request-id: 669dee08-0c92-4fb4-867f-67ff08d7b72f
x-http-caller-id: UNKNOWN_CALLER
Content-Length: 23
Server: Jetty(9.2.10.v20150310)
My logging shows that the function requested "plain/text", but somehow the framework trumped it.
2017-01-12 18:04:47,581 INFO [qtp789647098-46]kthxbye.v1.api [669dee08-0c92-4fb4-867f-67ff08d7b72f] - response is {:status 200, :headers {"Content-Type" "text/plain"}, :body "frickin compojure-api"}
How do I gain control over Content-Type in a Compojure-API Ring application?
compojure-api serves response in format requested by HTTP client which is indicated using HTTP Accept header.
With curl you need to add:
-H "Accept: text/plain"
You can also provide a list of acceptable formats and the server will serve the response in the first supported format from that list:
-H "Accept: text/plain, text/html, application/xml, application/json, */*"
I never tried compojure so here goes nothing:
1.) your local val reps has the same name as the aliased namespace - kind of confusing
2.) to get access to the params - it seems - you have to apply ring.middleware.params/wrap-params to your routes
3.) ah yes the Content-Type: since you required :form-params, which didn't get delivered due to missing wrap-params you ended up in some sort of default route - hence not text/plain. Thats what I think happend, at least.
with
lein try compojure ring-server
demo/paste into repl:
(require '[compojure.core :refer :all])
(require '[ring.util.response :as resp])
(require '[ring.server.standalone :as server])
(require '[ring.middleware.params :refer [wrap-params]])
(def x
(POST "/echo" [message]
:summary "info log the input message and echo it back"
:description nil
:return String
:form-params [message :- String]
(let [resp (-> (resp/response (str "message: " message))
(resp/status 200)
(resp/header "Content-Type" "text/plain"))]
resp)))
(defroutes app (wrap-params x))
(server/serve app {:port 4042})
test:
curl -X POST -i --header 'Content-Type: application/x-www-form-urlencoded' -d 'message=frickin' 'http://localhost:4042/echo'
HTTP/1.1 200 OK
Date: Fri, 13 Jan 2017 17:32:03 GMT
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 14
Server: Jetty(7.6.13.v20130916)
message: frickin

Clojure Pedestal root serving as application/octet-stream

I am trying to host static assets along with services in Pedestal 0.5.1. I am using the ::file-path to point to a directory to host the files. This works fine if I navigate directly to the file http://localhost:8888/index.html but if I go to the root of the site http://localhost:8888 it serves the files as application/octet-stream rather than text/html. I adapted the Hello World Sample and it has the same behavior.
src/hello_world/server.clj
(ns hello-world.server
(:require [io.pedestal.http :as http]
[io.pedestal.http.route :as route])
(:gen-class))
(def routes
(route/expand-routes [[]]))
(def service
{:env :prod
::http/join? false
::http/routes routes
::http/file-path "/tmp/www"
::http/type :jetty
::http/allowed-origins {:creds true :allowed-origins (constantly true)}
::http/port 8888})
(defonce runnable-service (http/create-server service))
(defn -main
"The entry-point for 'lein run'"
[& args]
(println "\nCreating your server...")
(http/start runnable-service))
Start lein run
$ curl -i localhost:8888
HTTP/1.1 200 OK
Date: Fri, 18 Nov 2016 16:02:56 GMT
Last-Modified: Fri, 18 Nov 2016 15:10:22 GMT
Content-Type: application/octet-stream
Content-Length: 12
Server: Jetty(9.3.8.v20160314)
hello world
$ curl -i localhost:8888/index.html
HTTP/1.1 200 OK
Date: Fri, 18 Nov 2016 16:03:02 GMT
Last-Modified: Fri, 18 Nov 2016 15:10:22 GMT
Content-Type: text/html
Content-Length: 12
Server: Jetty(9.3.8.v20160314)
hello world
Is there some way to fix the "/" route to serve the correct content-type?
To get correct content-types for files served as directory indexes,
add the interceptor io.pedestal.http.ring-middlewares/file-info to
your Pedestal configuration.
This requires that you override the default interceptor chain with
your own, so you will have to include all of the default interceptors
that your app needs.
For example, your service might look something like this:
(ns hello-world.service
(:require
[io.pedestal.http :as http]
[io.pedestal.http.ring-middlewares :as middlewares]
[io.pedestal.http.route :as route]
[io.pedestal.http.route.definition :refer [defroutes]]))
(defroutes routes
[[]])
(def service
{::http/type :jetty
::http/port 8080
::http/interceptors [http/log-request
http/not-found
middlewares/session
route/query-params
(middlewares/file-info) ; HERE
(middlewares/file "/tmp/www")
;; ... insert other interceptors ...
(route/router #(deref #'routes) :map-tree)]})
For examples of other default interceptors you might want to include,
see default-interceptors.
Explanation
This probably does not come up very often in practice because many web
applications use a handler function to generate the home page instead
of returning a static file.
For an alternate solution, you could write a route handler for the /
route which returns the contents of index.html with the appropriate
content-type.
Pedestal's default interceptor stack includes
io.pedestal.http.ring-middlewares/file
and io.pedestal.http.ring-middlewares/content-type.
These interceptors just wrap the Ring middleware functions
file-request
and content-type-response, respectively.
file-request returns a java.io.File object as the HTTP response.
content-type-response examines the request URI to determine the
value of the Content-Type header. Since the URI is just / it
defaults to application/octet-stream.
By contrast ring.middleware.file-info (which is deprecated) examines
path of the actual File object in the response.
See file-info-response.
io.pedestal.http.ring-middlewares/file-info is the interceptor
wrapper around ring.middleware.file-info/file-info-response.

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.