In my handler I have the following defined:
(def reg (new-registry))
(def app (-> app-routes
(expose-metrics-as-json)
(instrument reg)))
If I go to /metrics I only see an empty json object. What am I missing? Also, the documetation is not clear as to how I can report on the console, it seems report-to-console which is in the documentation is not used any more, instead there are reporters but I cannot find any documentation as to how you use reporters.
EDIT:
I have seen that metrics are being added and recorded by looking at this:
(meters/rates ((all-metrics reg) "ring.responses.rate.2xx"))
And the results are as expected, something like:
{1 0.06325196458537237, 5 0.01824839591203641, 15 0.006466051961211013, :total 6}
So it seems that although expose-metrics-as-json is creating an endpoint, the metrics are not getting added to it.
You are not passing the registry to expose-metrics-as-json. It falls back to the default registry.
In your threading expression, try changing the second line so that it reads:
(def app (-> app-routes
(expose-metrics-as-json "/metrics" reg)
(instrument reg)))
Ha! I found that this is actually a bug in the code. https://github.com/sjl/metrics-clojure/blob/master/metrics-clojure-ring/src/metrics/ring/expose.clj
([handler uri registry]
(expose-metrics-as-json handler uri default-registry {:pretty-print? false}))
This ignores the registry parameter. I got things to work by using
(expose-metrics-as-json "/metrics" reg {:pretty-print? false})
I will fix and create a pull request.
Related
I am new to Closure and trying out Ring and Compojure. I want to make an HTTP request when a user hits a route (to a third party API) and then use that response data in my HTML template
. I know this is likely a very easy thing to pull off - but being new to language and the syntax I am a bit lost.
(defroutes app
(GET "/" request
; try to GET "https://third-party-api" and do something with the response
)
)
What is the best practice and format for this - It's possible I am missing some key concepts in routing / response expectations here. Thanks very much!
I recommend the library clj-http for making http requests. You can find many examples on the linked page for how to use it.
Your usage of clj-http may look something like this:
(ns my-app.core
(:require [clj-http.client :as client]))
...
(defn get-api-data []
(:body (client/get "https://third-party-api" {:as :json})))
Note that clj-http.client/get returns a map that includes things like the response status code and headers.
If you use the {:as :json} option to coerce the response into json, make sure to include cheshire in your project.clj (assuming you're using leiningen)
:dependencies [...
[clj-http "3.9.0"]
[cheshire "5.8.0"]]
Documentation on ring requests and responses can be found here.
A large portion of the power in ring is its concept of middlewares. Most of the "nice" features that you'd want in an http server can be found as middlewares in ring itself or other libraries. For example, if you want all of your responses to be serialized as json by default, you might use ring-json
If you're trying to get something "that just works", up and running quickly with a few examples present, Luminus may be useful. It's a curated collection of libraries that prove useful for most webservers. (Disclaimer: I've only minimally experimented with Luminus, opting to understand more explicitly my dependencies).
I personally use compojure sweet at the start of most of my webservice projects, it includes some nicer routing features (including path params) and a swagger UI for testing your endpoints. Unfortunately, it uses its own form of destructuring and includes a bit more magic and "just needing to know" than I'd like, but I'm yet to find something that works better for me.
I have the following Compojure routes:
(defroutes my-handler
(GET "/:my-model-id" [id] (render-my-model (parse-int id))))
It is unfortunate that, for every route that I define this way, I have to manually add a call to parse the incoming integer.
I have created Ring middleware that crawls through any form-params and request-params, and parses anything that looks like it might be an integer. However, this middleware does not apply to the custom-defined Compojure routes.
Does anybody know how I could get Compojure to automatically handle the integer-parsing? Can I somehow hook it up to my existing middleware?
Unfortunately compojure will directly invoke the function that is generated from your route definition after it has parsed the params.
AFAIC the only way to get in between is either to modify compojures codebase directly or to use Robert Hooke (by technomancy) on assoc-route-params in https://github.com/weavejester/compojure/blob/master/src/compojure/core.clj#L30
NOTE: I resolved my issue. However, it took a number of incremental changes. If you happen upon this page, feel free to checkout my github below to see how I made this application work.
I am using http-kit to post a request to btc-china. I want to use their trading api. I am able to do this just fine with python, but for some reason I keep getting 401s with clojure and http-kit. I've posted a snippit of code below which may show that I am not using http-kit correctly. In addition to that, here is a github for my full code if you wish to look at that: https://github.com/gilmaso/btc-trading
Here are the btc-china api docs: http://btcchina.org/api-trade-documentation-en
(def options {:timeout 2000 ; ms
:query-params (sorted-map :tonce tonce
:accesskey access-key
:requestmethod request-method
:id tonce
:method method
:params "")
:headers {"Authorization" auth-string
"Json-Rpc-Tonce" tonce}})
(client/post (str "https://" base-url) options
(fn [{:keys [status headers body error]}] ;; asynchronous handle response
(if error
(println "Failed, exception is " error)
(println "Async HTTP GET: " status))))
quoting from the example on the bttchina site:
# The order of params is critical for calculating a correct hash
clojure hash maps are unordered, and you cannot use a clojure hash map literal to provide the input if order is significant
I had very similar problem with bitstamp api. The solution was to replace :query-params with :form-params. Then the parameters are sent in the body. I noticed that in your api you are manually sending then in the body. It looks like using :form-params might help in your case as well.
I am using liberator with ring/compojure and would like to handle authorization using the liberator defresource macros. I can easily get handle-ok to output html that is recognized by the browser, but handle-unauthorized will have the html output in pre tags.
I'm suspecting that my not being able to find out how to do this means that there is a good reason not to do it this way. All the examples I see online show handle-unauthorized returning text, but I was hoping to display a custom page.
Here is (one version of) the code I was using. I am using Hiccup:
(defresource deny-all
:authorized? (fn [_] false)
:available-media-types ["text/html"]
:handle-ok (fn [ctx] (html5 [:body [:h1 "Yes!"]])))
:handle-unauthorized (fn [ctx] (html5 [:body [:h1 "Noooo!"]])))
What I get back from the browser is a literal
<!DOCTYPE html>
<html><body><h1>Noooo!</h1></body></html>
</pre>
If I change authorized? to return true, then it outputs the html correctly.
I've tried returning ring-style responses, but those raise errors too. What am I missing?
In REST talk, html is one of many possible representations of a resource. Since the resource is unauthorized, no representation should be returned, html or otherwise. Instead, the client, instructed by the 401 status error, should take a different course of action, such as requesting a login page.
Most apps written in web frameworks will not return a 401, but instead redirect to the authorization page. This is possible in Liberator too, because nothing stops you from handling the authorization in the resource itself (handle-ok with logic).
This might be a breach of practice. I posted an issue on Liberator's github to ask for the opinion of people more knowledgeable in the recipes of RESTful cooking.
At any rate, the reason you see your html wrapped in a pre tag is the result of the following factors:
Content negotiation is not available for unauthorized resources in Liberator. The 401's body is always of type text/plain.
When you specify a html string as the response, it will render as is.
When using Chrome development tools, upon inspecting the source, you will see the html string wrapped in a pre tag.
I hope this helps.
I am converting an older web app I made a few months ago from Noir to Compojure and I am using the Lib-Noir add-on. It appears that session/put! is either changed in some way I don't understand or it is bugging out for whatever reason.
Here, I can see that 4Clojure appears to be using it with no problems: See Line 51. I also found this thread that covers the same question but there doesn't appear to be a satisfactory response.
This should work (Noir):
user=> (require '[noir.session :as sesh])
nil
user=> (sesh/put! :user "me")
ClassCastException clojure.lang.Var$Unbound cannot be cast to clojure.lang.Atom
clojure.core/swap! (core.clj:2162)
The above is the same error that I am looking at on the webpage. Basically I'm stuck.
Edit to add
Appears I created a bit of confusion with the command line part: (put!) is not working in the program either. There's not much to write about it, except that it is (shesh/put! :uname user) and it appears that :uname isn't working. I'm confused as to why it would have worked before and not now when I am using the same tools as before. This is a rewrite of a site I build about 6 months ago. I'm just moving it to Compojure from Noir. The lib-noir session is, as far as I know, essentially the same as what was in Noir.
ANOTHER EDIT
I put the code up on github. This isn't the completed project, but hopefully someone can decipher what is going on here: https://github.com/dt1/SoloResume
If you run it from the REPL, there is no browser session registered in Noir. You can simulate this by using binding:
(binding [sesh/*noir-session* (atom {:somekey "somevalue"})]
(sesh/put! :user "borkdude"))
Use this only for testing/simulating to see what goes on in the session map, not in production code.
A fairly old question, but answering here as it was the first Google result when I had the same problem. I was using compojure:1.1.6, ring:1.2.1 and lib-noir:0.7.6
You need to use noir.session/wrap-noir-session when defining your app - e.g:
(def app
(-> (handler/site (routes app-routes ))
session/wrap-noir-session
wrap-base-url))
References:
https://groups.google.com/d/msg/clojure/XXgSGhF912I/luhN9wmMoi8J