EmacsにはHTTPリクエストを送信する方法はいくつかある。今回はその中でも得にHTTPの形式に近い構文でリクエストを記述し、それを送信するhttp.elとrestclient.elを取り上げて考える。
http.elとrestclient.el
この2つは似たような機能を提供している。それらが定義する構文に従ってHTTPリクエストを記述でき、そのリクエストを送信しレスポンスを確認するといったことができる。通常は request.http
といったようなファイルを作成し、そのファイル内にリクエストの内容を記述する。検証時やドキュメントとして使用することが多い。競合するEmacs Lisp製ではない製品としてはcURLやPostmanが挙げられる。
Babelと文芸的プログラミング
EmacsにはOrg-modeという強力なドキュメンテーションシステムがあり、Org-modeにはコードブロックに記述された処理を実行するためのBabelという機構がある。この機構を用いるとOrg-modeの文書で実行が可能なコードを記述し、その結果を即座にドキュメントに挿入できる。それは実行可能なドキュメントとなるため、文書の保守がとてもやりやすくなる。いわゆる文芸的プログラミングのようなことができる1。コードブロックに記述する内容は、Babel用のプラグインが用意されているかどうかで、実行が可能かどうかが決まる。もしプラグインが無ければ自分で実装することもできる。
http.elやrestclient.elとBabelを組み合わせる
http.elのためのBabelプラグインとしてはob-httpがある。またrestclient.elのBabelプラグインとしてはob-restclient.elがある。これらは別途インストールし org-babel-load-languages
に登録することで利用可能となる。
(org-babel-do-load-languages
'org-babel-load-languages
'(
(restclient . t)
(http . t)))
この設定を行うとOrg-modeのコードブロックでhttp.elやrestclient.elの構文のリクエストを記述しそれを送信できるようになる。
#+begin_src http GET http://example.com #+end_src
#+begin_src restclient GET http://example.com #+end_src
multipart/form-dataでのキャリッジリターン文字扱いが微妙に異るため注意する必要がある
これまでEmacsでのHTTPリクエストの送信のための機構のいくつかを説明した。HTTPリクエストにはJSON形式であったりx-www-form-urlencoded形式であったりといったいろいろな形式のBODYを記述できる。このBODYの形式の中でファイルのアップロードなどに用いられるmultipart/form-dataというものがある。multipart/form-dataはHTMLのFORMタグを指定し、そのenctype属性をmultipart/form-dataとすることでブラウザから送信できるようになる。実際のデータは以下のような形式だ。
POST / HTTP/1.1 Host: localhost:8001 User-Agent: curl/7.64.1 Accept: */* Content-Length: 189 Content-Type: multipart/form-data; boundary=------------------------44644daa7f63ea32 --------------------------44644daa7f63ea32 Content-Disposition: form-data; name="file"; filename="foo.txt" Content-Type: text/plain aaa --------------------------44644daa7f63ea32--
これと同様のリクエストをhttp.elやrestclient.elの構文では以下のように記述できる。
POST http://localhost:8001
Content-Type: multipart/form-data; boundary=BOUNDARY
--BOUNDARY
Content-Disposition: form-data; name="file"; filename="foo.txt"
Content-Type: text/plain
aaa
--BOUNDARY--
今回は制御文字であるキャリッジリターンが問題になった。HTTPの仕様上この文字は必須であり、各ヘッダーの終端やヘッダーとボディの境界にはキャリッジリターンとラインフィードを用いて指定する。http.elやrestclient.elの構文では、これらの文字を適切に使用しなくても各ヘッダーの終端やヘッダーとボディの境界については、それらのライブラリがそれらを判断して制御文字を適切に設定した状態でリクエストを送信する。
しかしmultipart/form-dataの場合、HTTPのボディ部の中にさらなるヘッダーのような値を設定する。http.elやrestclient.elはそれらを解釈しないため、適宜リクエスト内部に制御文字を明示的に指定する必要がある。http.elやrestclient.elはファイル単体で独立できるため、これらの改行をCRLF(キャリッジリターンとラインフィード)にすれば特に気にする必要はない。
では文書はどうだろうか。文書の改行はLinuxのスタイルに従いLF(ラインフィード)のみにしている人が多いのではないだろうか。Org-modeの文書の改行をLFで記述しており、更にその文書でBabelの機構を用いてhttp.elやrestclient.elを利用する場合に問題が発生する。文書の改行はLFだが、http.elやrestclient.elスタイルのブロックの改行はCRLFにしなければならない。ob-httpやob-restclientはこれらの要求に上手く対応できず、期待するリクエストを送信できない。今のところ上手くこの問題を回避する方法がわからない。
restclientで変数を展開した後のデータを取得する
外部APIを使用する場合で、HTTPリクエストのBODYの署名をHTTPヘッダーに付与しなければならない場合がある。そういった場合、署名を計算するために送信するBODY全体のデータが必要なのだが、http.elやrestclient.elでBODYに変数を埋め込んでいると、変数展開前のBODYを元に署名を計算することになってしまい、正しい署名が計算できない。正しい署名を計算するには変数展開後のBODY全体が必要になる。このデータを取得できないかを試すことにした。
restclientの内部を読んでみると、実装済みの使えそうな関数がいくつかあった。
- restclient-narrow-to-current
- 現在のポイントのrestclientの範囲をナローイングする。
- restclient-parse-body
- restclient形式の文字列を変数展開して返す。
- restclient-find-vars-before-point
- restclient形式で宣言された変数の連想リストを返す。
これらを組み合せ、変数展開後の文字列を専用のバッファに書き出せば良い。完成形はこのよになった。これは本当に小さなEmacs Lispのため、init.elにでも直接書いておこうと思う。
(defun restclient-show-request-on-new-buffer ()
(interactive)
(save-excursion
(restclient-narrow-to-current)
(let ((req-txt (restclient-parse-body
(buffer-substring-no-properties (point-min) (point-max))
(restclient-find-vars-before-point))))
(widen)
(with-current-buffer (get-buffer-create "*Restclient Request*")
(erase-buffer)
(insert req-txt)
(switch-to-buffer (current-buffer))))))
まとめ
EmacsのHTTPクライアントであるhttp.elとrestclient.elについて考えた。競合するツールはあれど、Org-modeの強みを生かすことで、文芸的プログラミングのような方法でクライアントを記述できるが、改行文字の問題などでハマる箇所もあった。またボディから署名を計算するための小さなEmacs Lispを実装した。
特にOrg-mode, Babel, org-tangleを組み合せることで、本当に文芸的プログラミングを実践できそうに思えることがある。これはかなり魅力的にも感じるが、実際に複数人のチームで開発を行う場合には現実的ではない。スタイルが大きく異なる事、皆がEmacsやOrg-modeに好意的ではない事、デバッグのやりにくさ等がその理由になる。一方で、システムのテスト用ドキュメント兼ツールとしては結構使えるのではないかとも思う。本来http.elやrestclient.elはEmacs Lispでは実装せず、他のエディタや環境からも使用できるようにすると、もっと広がりがあるのではないかと思う。ここについて今回は踏み込まないけれど、時間ができたらもう少し掘り下げてみたい。