Clojure APIクライアント開発
主にサービスのAPIを叩くクライアントプログラムのノウハウまとめ.
Clojure HTTP Client
主なクライアントライブラリにhttp-kitとclj-httpがある. httpリクエストに関してはどちらも同じことができる. 非同期発行も.
http-kitは2022年現在メンテナンス状況が怪しく見えた. そしてclj-httpは後発のalephやjava-http-cljと比べるともう古いように思える.
いずれにしろ, どれもClojureのWeb抽象であるClojure: Ringの仕様に従っている.
🔧dakrone/clj-http
https://github.com/dakrone/clj-http
Ring の影響を受けているため Request Mapを入力としてResponse Mapを返す.
以下小技. ライブラリのチューニングにはclj-httpというよりもHTTPプロトコルの一般的な理解や経験が生きてくる.
tips: debug 出力
{:debug true} をRequest Mapに含めると, 標準出力にRequest Mapの内容が表示される.
tips: clj-httpにおけるjsonの扱い
ClojureでJSONを扱うライブラリである cheshire をインストールすると, jsonとの連携が{:as :json} でできる. この指定がされると clj-httpはrepsonse dataをcheshire でjsonにパースする.
なお 単純に {:headers {:accept “application/json”}}を設定したいだけなら{:accept :json}を指定する.
ref: 💡Accept と Content-Typeの違い
howto: HTTPエラーの扱いと例外ハンドリング
clj-httpはHTTP Responseが正常終了以外の場合(200 201 202 203 204 205 206 207 300 301 302 303 304 307)はslingshotのインスタンスを返すため, slingshotに合わせたエラーハンドリングが期待される.
すなわち(try… (catch Exception ex))で補足するか, slingshotのtry+ blockを使うか. slingshotはclj-httpをインストールすると一緒についてくる.
- Exceptions - GitHub - dakrone/clj-http
- clj-http READMEの説明.
- Simple error handling using slingshot and clj-http - Oskar Thorén
- シンプルなerror handling wrapper実装例.
howto: clj-httpのリクエストをproxy経由にするには?
Request Mapに proxy-host, prox-port, proxy-user, proxy-passを含める.
ref: clj-http: Proxies
howto: clj-httpで WARN log: Invalid ‘expires’ attribute
request mapのオプションに :cookie-policy :standard を追加.
ref: Invalid ‘expires’ attribute? - GitHub
howto: タイムアウト値を設定するには?
Request Mapに 値をmillsecondで設定.
{:socket-timeout 1000
:connection-timeout 1000
:connection-request-timeout 1000}
clj-httpは JavaのApache HttpClentのWrapperであり仕様はこれに従う.
SocketTimeout/Connection Timeout/Connection Request Timeout
:throw-exceptions falseによる例外抑止
Effective Java “Use Excetions only for exceptional conditions”に従うと, {:thrwo-exceptions false}にすることで例外抑止しつつ自分で例外処理をかくのが吉.
ページが存在するかチェックするには?
スクレイピング用途で利用するときに404がかかるとわかっているならばgetを投げないほうがいい. こんなときはhead requestで事前に存在確認したい.
ただしclj-httpではエラーコードは自動で例外を上げるため, それを防止して自分で中身のコードをチェックするときはRequest Mapに {:throw-exceptions false} を設定する.
(defn page-exists? [url]
(if-let [resp (client/head url {:headers headers
:throw-exceptions false})]
(= (:status resp) 200)
false))
🔗References
- Production Considerations for clj-http · rymndhng
- 実践http request的な. ガチるなら必読.
🔧schmee/java-http-clj
clj-httpをベースにJava11と📝HTTP/2をサポート. clj-httpは2015に登場したHTTP/2をサポートしていない.
- https://github.com/schmee/java-http-clj
- https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/package-summary.html
- https://github.com/jjttjj/lmalob: websocket example.
⚠クエリ文字列(query string)は指定できない
HttpClient APIも対応していないのでこれはライブラリの対応外という方針から. 自力でuriにつける.
✅非同期requestとManifoldを連結するには?
async APIはJavaの CompletableFutures を返すのでこれと📝Clojure: Manifoldの連携を考えるらしい. どうも現在進行系でPRが進んでいるようだ.
ただし, 同期Requestを投げてmanifoldのfutureをつかって待ち合わせもできる. d/let-flow内で非同期処理をして最後の戻り値でderefをすることによってrealizedする.
(defn fetch-ticker [this market]
(let [query-params {"symbol" market}]
@(d/let-flow [ticker (d/future (get-public-query-24-ticker this query-params))
orderbook (d/future (get-public-query-orderbook this query-params))
trades (d/future (get-public-query-trade this query-params))]
(let [ask (-> orderbook :orderbook-p :asks first first)
bid (-> orderbook :orderbook-p :bids first first)
ltp (-> trades :trades-p first)]
{:ask ask
:bid bid
:ltp nth ltp 2}))))
tip: SSL証明書チェックをスキップする
オプションとして自前のclientを作成してそれを渡すことでいろんな通信設定をする.
(defn accept-all-certificates []
(let [trust-manager (proxy [X509TrustManager] []
(getAcceptedIssuers [] (into-array java.security.cert.X509Certificate []))
(checkClientTrusted [chain auth-type] nil)
(checkServerTrusted [chain auth-type] nil))
trust-managers (into-array X509TrustManager [trust-manager])
ssl-context (SSLContext/getInstance "TLSv1.2")]
(.init ssl-context nil trust-managers nil)
ssl-context))
(def opts
{:client
(http/build-client
{:connect-timeout 10000 ;; 10s
:ssl-context (accept-all-certificates)})})
🎨Clojure API Client ベストプラクティス
💡let or throw pattern on http request
(let [resp (client/get xxx)]
(-> resp
:body
xxx
(or (throw (ex-info "Exception occured"
{:response resp})))))
💡Clojureでクエリ文字列を生成するには?
クエリ文字列をClojureで生成する方法
- ring.util.codec/form-encode, url-encode を使う.
- clj-http.client/generate-query-stringを使う.
- java.net.URLEncoder/encodeを使う.
- https://github.com/lambdaisland/uri
;; https://github.com/ring-clojure/ring-codec
(ring.util.codec/form-encode {:symbol "BTCUSDT"})
;; 記号も置き換えられる.
(ring.util.codec/url-encode {:symbol "BTCUSDT"})
ref. Clojure building of URL from constituent parts - Stack Overflow
💡pcoll pattern in clojure http request
http requestの呼び出しでよく使うと思う. applyがミソ.
(defn request [req-fn & req-args]
(try
(when-let [resp (apply req-fn req-args)]
resp)
(catch Exception e [false e])))
Chunked request pattern
リストを指定するときに一回の数にlimitがある場合によく使うパターン. partition-allを使ってlistをchunkにする.
(defn get-bulk->p [& list]
(p/let [results (p/all
(map (fn [chunked]
(apply get-reqest chunked))
(partition-all chunk-length list)))]
(apply merge results)))
💡HTTP Requestのパラレル実行
スレッドを使って同時にhttp requestを出す.
例えばpmapをつかってマップに格納された処理を並列実行することができる.
ref. 💡Lazy Sequence + pmap + doallの並列スクレイピングが強い
httpkitやclj-httpにも並列実行をするoptionがある.
core.asyncは調査中…
Clojure WebSocket Client
- tags: 🔖websocket
aleph
Netty & Manifoldをbaseにしたclient lib.
clj-socketio-client
https://github.com/ejschoen/clj-socketio-client
Socket.IOのClojureライブラリ. socket.ioの公式DocにはClojureクライアントライブラリは乗っていない. JavaはあるのでこのライブラリはJava版のWrapper.
https://github.com/socketio/socket.io-client-java
コードを覗くと短いコードなのでそれを参考にJava版のwrapperを自前実装してもいいかも.
このJava版は内部でThreadを生成して制御するようで, ライブラリの外からThreadの制御を出来るようなものではないようだ. メソッドコールのたびに, たとえばemitを呼ぶたびにRunnable()オブジェクトを作成してThreadPoolのThread上で処理をループさせている.
Clojure: sente
Clojureのためのリアルタイム通信ライブラリ.
https://github.com/ptaoussanis/sente
senteは日本語の囲碁の先手が由来とかwww先手を打つくらい爆速を目指す設計思想, 先手必勝!!! timbreと同じ作者. この人tempuraみたな名前のライブラリとかもつくってるので日本好きだな.
Or: We don’t need no Socket.IO
Or: core.async + Ajax + WebSockets = The Shiznizzle
Socket.IOがWebsocketの欠点を改良したものならば, senteも独自改良したらしい(auto-failback).
そしてもう一つの特徴は, 内部制御で📝Clojure core.async(go-loop)を使っていること.
https://github.com/ptaoussanis/sente/issues/227
他のライブラリはJavaの肩に乗っている影響でJavaのThread/ThreadPoolの仕組みに依存しているがcore.asyncには依存していない. Clojureのcore.asyncは普通のThreadとは違い軽量スレッドなため, そこがsente(先手)の意味だというのがわたしの自己解釈.
sente: Usage
Client Sideのコードを参考にする.
sente: References
- senteとintegrant連携は Clojure: Rollのコードが参考になる(GitHub).
- ClojureでWebアプリ続き(WebSocketとチャット) - ぞぬこBLOG
- 📚Web Development with Clojure - Dmitri Sotnikovにはwebsocketのサンプルある.
Clojure Websocket Client Topics
✅ClojureでProducter-Consumer Patternをするにはcore.async
Producer-Consumer Patternはスレッド同士で情報をやり取りするための仕組み.
Clojureだと, 📝Clojure core.asyncを使える. core.asyncのチャネルの概念はキューの拡張.
別スレッドでwebsocket経由で情報を受信して, core.asyncのchannel経由でスレッド間通信によって情報を非同期で受け取る.
✅websocketのevent-handlerでmultimehodを利用する
Clojure: senteのexampleからのインサイト. websocket経由で受信したmessageをdefmultiで条件分岐する. これはmultimethodをつかったよい例.
https://github.com/ptaoussanis/sente/blob/master/example-project/src/example/client.cljs
ref. 📝Clojure マルチメソッド(multimethod)
✅handlerで受け取ったmessageを変換して保存するには?
サンプルコードはprintlnで標準出力するものばかり. そうではなく受信したデータを保存したい.
References
勉強の参考に目を通したもののブックマーク.
- ClojureでQiita APIをたたく - Qiita
- https://github.com/akthrms/qiita-client-sample
- Qiitaを叩くだけだけど余計なものがないのでよい.
- Clojure tutorial - boot, basic functions and how to do REST requests
- とくにClojureのREST requestsに着目したチュートリアルWeb記事.
- example of clj-http
- https://github.com/joninvski/clojure-tutorial