(rontolisp) docs

HTTPリクエスト(fetch)

rontolisp パッケージはJavaScriptの fetch APIをモデルにした外向きHTTPと、 それと自然に組み合わせられるJSON関数を提供します。いずれも Common Lisp の 一部ではないため、rontolisp: 修飾子で参照します (パッケージを参照)。rontolisp:fetch は リクエストを開始して即座に プロミス を返します。汎用のプロミス操作が それを解決・変換し、rontolisp:json-parse / rontolisp:json-stringify が JSONドキュメントとLispの値を相互変換します。

関数用途
rontolisp:fetchHTTPリクエストを開始する: (rontolisp:fetch url &optional options)
rontolisp:awaitプロミスが確定するまでブロックして値を返す
rontolisp:then確定値にコールバックを適用する新しいプロミスを導出する
rontolisp:promisep値がプロミスなら t
rontolisp:json-parseJSON文字列をLispの値にパースする
rontolisp:json-stringifyLispの値をJSON文字列にシリアライズする

バックエンドのサポート。 インタプリタとJVMコンパイル済みクラスはJDKの java.net.http.HttpClient を使い、fetch が返った瞬間からリクエストは バックグラウンドスレッドで走ります。WASMバックエンドは componentモード 専用 です (--componentwasi:http@0.2 をimportするハイブリッド): fetch はPreview 1 (コアモジュール) モードではコンパイルエラーになり、 fetchを使うcomponentは非同期フラグに加えて -S http=y を付けて実行する 必要があります。ブラウザプレイグラウンド では本物のブラウザの fetch() が実行され (CORSの制約を受けます)、その間プログラムは続行 します。プロミス操作 (await / then / promisep) とJSON関数は すべての バックエンド・すべてのWASMモードで動作します — 制限が あるのは fetch 自体だけです。

最初のリクエスト

fetch はリクエストが飛び始めたらすぐに返ります。プロミスを rontolisp:await に渡すとレスポンスの到着までブロックし、結果の プロパティリスト (:status <integer> :body <string> :headers <alist>) が 得られます:

個々のフィールドの読み取り:

(let ((res (rontolisp:await (rontolisp:fetch "http://example.com/"))))
  (print (getf res :status))    ; => 200
  (print (getf res :body))      ; => "<html>...</html>"
  (print (getf res :headers)))  ; => (("content-type" . "text/html") ...)

リクエストのオプション

省略可能な第2引数はオプションのプロパティリストで、:method (文字列、デフォルト "GET")、:headers ((name . value) の文字列ペアの alist)、:body (文字列) を指定できます:

;; GET with request headers (an alist of (name . value) string pairs)
(rontolisp:fetch "http://example.com/api"
                 (list :headers (list (cons "Accept" "application/json"))))

;; POST with a request body
(rontolisp:fetch "http://example.com/api"
                 (list :method "POST"
                       :headers (list (cons "Content-Type" "application/json"))
                       :body "{\"name\":\"rontolisp\"}"))

サポートされるメソッドは GETHEADPOSTPUTDELETEOPTIONSPATCH です。バックエンドごとのバリデーションのタイミングとエラー時の 挙動 (リクエストの失敗は fetch 時ではなく await 時に表面化します — インタプリタとJVMはエラーをシグナルし、WASMは nil を返します) は fetch のリファレンスページを参照してください。

プロミス

fetch が返った時点でリクエストは既に走っているので、複数のリクエストは オーバーラップします — 全部開始してからそれぞれをawaitします:

(let ((p1 (rontolisp:fetch "http://example.com/a"))
      (p2 (rontolisp:fetch "http://example.com/b")))  ; both requests running
  (list (rontolisp:await p1) (rontolisp:await p2)))

rontolisp:then は確定値を変換する新しいプロミスを導出します。JavaScriptの Promise.prototype.then と同様に呼び出しはチェーンでき、プロミスを返す コールバックは平坦化されます:

どちらの操作も汎用です: await はプロミス以外の値をそのまま通し、then も プロミス以外を受け付けるので、プロミスかもしれない値を一様に扱えます。 rontolisp:promisep で両者を見分けられます:

then のコールバックは最初の await 時に遅延実行され、結果はメモ化され ます (確定したプロミスは何度でもawaitできます)。正確なタイミングは then のリファレンスページを参照してください。

JSONの扱い

rontolisp:json-parse はJSONドキュメントをLispの値に変換します。デフォルト ではJSONオブジェクトはキーワードをキーとするプロパティリストになるので、 結果は getf で読めます。配列はリストに、true/false/nullt/nil になります:

:hash-table を渡すと、代わりに文字列キーのハッシュテーブルが返ります — キーが任意の文字列のときや、空オブジェクトを nil と区別し続けたいときに 使ってください:

rontolisp:json-stringify はその逆です: キーワードのプロパティリストと ハッシュテーブルはオブジェクトに、その他のリストは配列にシリアライズ されます:

どちらの関数もrontolisp自身で書かれていて、使われたときにプログラムに コンパイルされ、すべてのバックエンドで動作します。値の対応表と エッジケース (整数の桁数、nil の曖昧さ、キーの順序) の全体は json-parsejson-stringify のリファレンスページにあります。

完全なプログラム

これらの部品を組み合わせると、JSON APIの典型的な往復になります: json-stringify でリクエストボディを作り、POSTし、レスポンスをawaitして ボディを json-parse でパースします。以下を fetch-post.lisp として保存 してください:

(let* ((payload (rontolisp:json-stringify (list :name "rontolisp" :stars 1)))
       (res (rontolisp:await
             (rontolisp:fetch "https://httpbin.org/post"
                              (list :method "POST"
                                    :headers (list (cons "Content-Type" "application/json"))
                                    :body payload))))
       (json (rontolisp:json-parse (getf res :body))))
  (print (getf res :status))
  (write-line (getf json :data)))
200
{"name":"rontolisp","stars":1}

実行方法

インタプリタで:

rontolisp fetch-post.lisp

JVMクラスにコンパイルして (クラス名は出力ファイル名から付きます):

rontolisp fetch-post.lisp -o FetchPost.class
java FetchPost

WASM componentにコンパイルして (wasmtime 46+。外向きHTTPを許可する -S http=y に注意 — これがないと wasi:http のimportが提供されず、 インスタンス化に失敗します):

rontolisp fetch-post.lisp -o fetch-post.wasm --component
wasmtime run -W gc=y -W component-model-more-async-builtins=y -S http=y fetch-post.wasm

HTTPではなく素のTCPを使う場合 — あるいは サーバー 側を実装する場合 — は TCPソケットガイドを参照してください。