How to a json-rpc call from Clojure? - clojure

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.

Related

avoid circular dependency when I access reitit route info from handler

Assuming I have some kind of router set up that maps some routes to handlers something like this...
(ns myapp.user.api
(:require [reitit.core :as r]))
; define handlers here...
(def router
(r/router
[["/user" {:get {:name ::user-get-all
:handler get-all-users}}]
["/user/:id"
{:post {:name ::user-post
:handler user-post}}
{:get {:name ::user-get
:handler user-get}}]]))
And those handlers then call services that want access to the routing information...
(ns myapp.user-service
(:require [myapp.user.api :as api]))
; how can I get access to the route properties inside here..?
(defn get-all-users [])
(println (r/route-names api/router)))
When I try to import the router from the api file, into the service, I get a problem with circular dependencies, because the api requires handler, which requires service, so service can not then require api.
What's the best way to avoid this circular dependency? Can I look up values and properties of the router from within services?
I use six general approaches to avoid circular dependencies in clojure. They all have different tradeoffs and some situations one will fit better than another. I list them in order from what I prefer most to what I prefer least.
I show one example for each below. There may be more ways I haven't thought of, but hopefully this gives you some ways of thinking about the issue.
Refactor the code to remove the commonly referenced vars into a new namespace and require that namespace from both original namespaces. Often this is the best and simplest way. But can't be done here because the root handler var is a literal containing a var from the other namespace.
Pass in the dependent value into the function at runtime so as to avoid having to require the namespace literally.
(ns circular.a)
(defn make-handler [routes]
(fn []
(println routes)))
(ns circular.b
(:require [circular.a :as a]))
(def routes
{:handler (a/make-handler routes)})
;; 'run' route to test
((:handler routes))
Use multimethods to provide the dispatch mechanism, and then defmethod your binding from the other namespace.
(ns circular.a
(:require [circular.b :as b]))
(defmethod b/handler :my-handler [_]
(println b/routes))
(ns circular.b)
(defmulti handler identity)
(def routes
{:handler #(handler :my-handler)})
(ns circular.core
(:require [circular.b :as b]
;; now we bring in our handlers so as to define our method implementations
[circular.a :as a]))
;; 'run' route to test
((:handler b/routes))
Use a var literal that is resolved at runtime
(ns circular.a)
(defn handler []
(println (var-get #'circular.b/routes)))
(ns circular.b
(:require [circular.a :as a]))
(def routes
{:handler a/handler})
;; 'run' route to test
((:handler routes))
Move the code into the same namespace.
(ns circular.a)
(declare routes)
(defn handler []
(println routes))
(def routes
{:handler handler})
;; 'run' route to test
((:handler routes))
Use state. Store one of the values in an atom at runtime.
(ns circular.a
(:require [circular.c :as c]))
(defn handler []
(println #c/routes))
(ns circular.b
(:require [circular.a :as a]
[circular.c :as c]))
(def routes
{:handler a/handler})
(reset! c/routes routes)
((:handler routes))
(ns circular.c)
(defonce routes (atom nil))
You are making a simple mistake somewhere. My example:
(ns demo.core
(:use tupelo.core)
(:require
[reitit.core :as r]
[schema.core :as s]
))
(defn get-all-users [& args] (println :get-all-users))
(defn user-post [& args] (println :user-post))
(defn user-get [& args] (println :user-get))
; define handlers here...
(def router
(r/router
[
["/dummy" :dummy]
["/user" {:get {:name ::user-get-all
:handler get-all-users}}]
["/user/:id"
{:post {:name ::user-post
:handler user-post}}
{:get {:name ::user-get
:handler user-get}}]
]))
and use here:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.string :as str]
[reitit.core :as r]
))
(dotest
(spyx-pretty (r/router-name router))
(spyx-pretty (r/route-names router))
(spyx-pretty (r/routes router))
)
with result:
*************** Running tests ***************
:reloading (demo.core tst.demo.core)
Testing _bootstrap
-----------------------------------
Clojure 1.10.3 Java 15.0.2
-----------------------------------
Testing tst.demo.core
(r/router-name router) =>
:lookup-router
(r/route-names router) =>
[:dummy]
(r/routes router) =>
[["/dummy" {:name :dummy}]
["/user"
{:get
{:name :demo.core/user-get-all,
:handler
#object[demo.core$get_all_users 0x235a3fc "demo.core$get_all_users#235a3fc"]}}]]
Ran 2 tests containing 0 assertions.
0 failures, 0 errors.
based on my favorite template project

Endpoint unit testing with Lacinia-Pedestal in Clojure

I have been using Pedestal for RESTful API servers and its endpoint unit testing. This approach is to set the server up and test it on endpoint level. The so-called "endpoint unit testing" is well documented in below page.
http://pedestal.io/reference/unit-testing#_testing_your_service_with_response_for
This time, however, I am using Lacinia-Pedestal for GraphQL (not RESTful, in other words) and am wondering if I can apply the same endpoint testing logic. In Lacinia-Pedestal repository (https://github.com/walmartlabs/lacinia-pedestal), I could not find a relevant instruction. In fact it doesn't mention about unit testing at all.
If anyone has experience with this approach, can you please share?
Thanks!
-- edit
I am adding my testing code here.
resources/main-schema.edn:
{:queries
{:hello
{:type String}}}
core.clj
(ns lacinia-pedestal-lein-clj.core
(:require [clojure.edn :as edn]
[clojure.java.io :as io]
[com.walmartlabs.lacinia.pedestal2 :as p2]
[com.walmartlabs.lacinia.schema :as schema]
[com.walmartlabs.lacinia.util :as util]
[io.pedestal.http :as http]))
(defn resolve-hello
[_ _ _]
"hello, darren")
(defn hello-schema
[]
(-> (io/resource "main-schema.edn")
slurp
edn/read-string
(util/inject-resolvers {:queries/hello resolve-hello})
schema/compile))
(def service
(-> (hello-schema)
(p2/default-service nil)
http/create-server
http/start))
and Pedestal (not Lacinia-Pedestal) says that the server instance can be set up and tested by the following code snippet:
(ns lacinia-pedestal-lein-clj.core-test
(:require [lacinia-pedestal-lein-clj.core :as core]
[clojure.test :as t]
[io.pedestal.http :as http]
[io.pedestal.test :as ptest]
[com.walmartlabs.lacinia.pedestal2 :as p2]
[com.walmartlabs.lacinia.util :as util]))
(def service
(:io.pedestal.http/service-fn
(io.pedestal.http/create-servlet service-map)))
(is (= "Hello!" (:body (response-for service :get "/hello"))))
But, I believe this way works for RESTful but not for GraphQL because GraphQL needs to set schema (.edn file) and resolvers for a server.
So, I tried to tweak this.
(ns lacinia-pedestal-lein-clj.core-test
(:require [lacinia-pedestal-lein-clj.core :as core]
[clojure.test :as t]
[io.pedestal.http :as http]
[io.pedestal.test :as ptest]
[com.walmartlabs.lacinia.pedestal2 :as p2]
[com.walmartlabs.lacinia.util :as util]))
(defonce service
(-> (core/hello-schema)
(p2/default-service nil)
http/create-server
http/start))
(t/is (= "Hello!"
(:body
(util/response-for service
:get "/hello")))) ;; NOT WORKING
But it does not work this way because response-for expects interceptor-service-fn type.
So, as far as I know, the real question is how to use response-for function with GraphQL server instance.
in fact, i found a solution from Lacinia documentation.
https://lacinia.readthedocs.io/en/latest/tutorial/testing-1.html

How to apply ring-anti-forgery on specific reitit routes?

I keep getting "Invalid anti-forgery token" when wrapping specific routes created with metosin/reitit reitit.ring/ring-router. I've also tried reitit's middleware registry, but it didn't work too. Although I could just wrap the entire handler with wrap-session and wrap-anti-forgery, that defeats the reitit's advantage on allowing route-specific middleware.
(ns t.core
(:require [immutant.web :as web]
[reitit.ring :as ring]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[ring.middleware.content-type :refer [wrap-content-type]]
[ring.middleware.params :refer [wrap-params]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.session :refer [wrap-session]]
[ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.util.response :as res]))
(defn render-index [_req]
(res/response (str "<form action='/sign-in' method='post'>"
(anti-forgery-field)
"<button>Sign In</button></form>")))
(defn sign-in [{:keys [params session]}]
(println "params: " params
"session:" session)
(res/redirect "/index.html"))
(defn wrap-af [handler]
(-> handler
wrap-anti-forgery
wrap-session
wrap-keyword-params
wrap-params))
(def app
(ring/ring-handler
(ring/router [["/index.html" {:get render-index
:middleware [[wrap-content-type]
[wrap-af]]}]
["/sign-in" {:post sign-in
:middleware [wrap-af]}]])))
(defn -main [& args]
(web/run app {:host "localhost" :port 7777}))
It turns out that metosin/reitit creates one session store for each route (refer to issue 205 for more information); in other words, ring-anti-forgery is not working because reitit does not use the same session store for each route.
As of the time of this answer, the maintainer suggests the following (copied from the issue for ease of reference within Stack Overflow):
mount the wrap-session outside of the router so there is only one instance of the mw for the whole app. There is :middleware option in ring-handler for this:
(require '[reitit.ring :as ring])
(require '[ring.middleware.session :as session])
(defn handler [{session :session}]
(let [counter (inc (:counter session 0))]
{:status 200
:body {:counter counter}
:session {:counter counter}}))
(def app
(ring/ring-handler
(ring/router
["/api"
["/ping" handler]
["/pong" handler]])
(ring/create-default-handler)
;; the middleware on ring-handler runs before routing
{:middleware [session/wrap-session]}))
create a single session store and use it within the routing table (all instances of the session middleware will share the single store).
(require '[ring.middleware.session.memory :as memory])
;; single instance
(def store (memory/memory-store))
;; inside, with shared store
(def app
(ring/ring-handler
(ring/router
["/api"
{:middleware [[session/wrap-session {:store store}]]}
["/ping" handler]
["/pong" handler]])))
Not shown in this answer is the third option that the maintainer calls for PR.

Missing *out* in Clojure with Lein and Ring

I am running Lein 2 and cider 0.7.0. I made a sample ring app that uses ring/run-jetty to start.
(ns nimbus-admin.handler
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[clojure.tools.nrepl.server :as nrepl-server]
[cider.nrepl :refer (cider-nrepl-handler)]
[ring.adapter.jetty :as ring]
[clojure.tools.trace :refer [trace]]
[ring.util.response :refer [resource-response response redirect content-type]]
[compojure.route :as route])
(:gen-class))
(defroutes app-routes
(GET "/blah" req "blah")
(route/resources "/")
(route/not-found (trace "not-found" "Not Found")))
(def app (handler/site app-routes))
(defn start-nrepl-server []
(nrepl-server/start-server :port 7888 :handler cider-nrepl-handler))
(defn start-jetty [ip port]
(ring/run-jetty app {:port port :ip ip}))
(defn -main
([] (-main 8080 "0.0.0.0"))
([port ip & args]
(let [port (Integer. port)]
(start-nrepl-server)
(start-jetty ip port))))
then connect to it with cider like:
cider-connect 127.0.0.1 7888
I can navigate to my site and eval forms in emacs and it will update what is running live in my nrepl session, so that is great.
I cannot see output, either with (print "test") (println "test") (trace "out" 1)
Finally, my project file:
(defproject nimbus-admin "0.1.0"
:description ""
:url ""
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.6.0"]
[com.climate/clj-newrelic "0.1.1"]
[com.ashafa/clutch "0.4.0-RC1"]
[ring "1.3.1"]
[clj-time "0.8.0"]
[midje "1.6.3"]
[org.clojure/tools.nrepl "0.2.6"]
[ring/ring-json "0.3.1"]
[org.clojure/tools.trace "0.7.8"]
[compojure "1.1.9"]
[org.clojure/data.json "0.2.5"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
]
:plugins [[lein-environ "1.0.0"]
[cider/cider-nrepl "0.7.0"]]
:main nimbus-admin.handler)
I start the site with lein run
Edit
I CAN see output, ONLY when using (.println System/out msg)
Have you tried (.println System/out msg)? I had the same problem and this worked for me.
It's possible to just put print statements in your code manually.
If you want to print information about each request, you can add middleware.
The handler you pass to jetty is a function from Ring requests to Ring responses.
Ring request and responses are just maps, see the Ring spec for more which keys they should contain.
Middleware is just a function that takes a handler as its first argument and returns a handler.
Example of a middleware function to print basic info about requests and responses:
(defn log-middleware [handler]
(fn [request]
(let [response (handler request)]
(println "=>" (name (:request-method request)) ":" (:uri request))
(println "<=" (:status request))
response)))
This middleware should print to the cider repl buffer, but cider behaves strangely
sometimes and send output to *Messages* or the nrepl server buffer.
You use this middleware by applying it to your handlers:
(def application (log-middleware (handler/site routes)))
Headers could be printed this way to: just get the :headers field form the request map and print it.
The Prone library may help you out. It is has a ring middleware for better exception reporting that also has the ability for debugging. When debugging, you can inspect any local bindings as well as the Ring request.
Here is a video that demonstrates how it works
Use (flush) after your print expressions to force output.

Consuming WSDL in Clojure

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)))