request 20170131.1747(in MELPA)
Compatible layer for URL request in Emacs

概要

この偉大なライブラリを、なぜみんな知らないのでしょうか。

Emacsにはビルトインで url-retrieve というネットアクセス関数があり、
EmacsでHTTPアクセスをする場合はそれを使うのが普通です。

これは HTTPクライアント としてはお粗末な実装で、
しばしば意味不明な問題に遭遇するし、DNS解決の際には固まります。

それならば curlwget を外部プロセスとして呼び出した方が、
固まらない上に、より信頼でき、より高機能です。

「餅は餅屋」ということで専用の外部プログラムに頼るのは
本来のEmacsの精神、Unixの精神に沿っています。

しかしWindowsにはそれらがインストールされていないので
それがわかっていてもurl-retrieveに固執してしまいます。

せっかくcurlを持っているUnix系OSのユーザからすれば
有難迷惑なもので、url-retrieveの不自由さにイライラします。

なぜ強力な武器があるのに使わないのですか!
もったいないじゃないですか!

request.el は、url-retrieveとcurlのバックエンドに対応した
ネットワークアクセスを行うライブラリです。

curlがインストールされていればそれを使い、
されていなければurl-retrieveにfallbackします。

しかもparserも定義でき、より宣言的で強力な表現ができます。

elispプログラマはより強力な表現力を亨受でき、
curlを持っているユーザはそれを使って快適なネットアクセスができ、
持っていないならurl-retrieveを使ってくれるようになります。

おまけにEmacs23.1という化石にも対応しているので
古いバージョンのEmacsでも問題なく動きます。

request.elを使えばみんな幸せになれるわけです。

結論は
url-retrieveなんか捨てて、今すぐrequestに書き換えろ!
ということです。

インストール

パッケージシステムを初めて使う人は
以下の設定を ~/.emacs.d/init.el の
先頭に加えてください。

(package-initialize)
(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("melpa" . "http://melpa.org/packages/")
        ("org" . "http://orgmode.org/elpa/")))

初めてrequestを使う方は
以下のコマンドを実行します。

M-x package-install request

アップグレードする方は、
以下のコマンドでアップグレードしてください。
そのためにはpackage-utilsパッケージが必要です。

M-x package-install package-utils (初めてアップグレードする場合のみ)
M-x package-utils-upgrade-by-name request

使用例

代表的な使用例を見れば、使い方が見えてきます。

仕様は<f1> f requestで見られます。

コールバック関数はたくさんのキーワード引数を持つので、
function*、(cl-function)と&allow-other-keysを使います。

サイトの内容をそのままバッファに表示する例

(request "http://rubikitch.com/"
         :parser 'buffer-string
         :complete (function*
                    (lambda (&key data &allow-other-keys)
                      (switch-to-buffer "*request-result*")
                      (erase-buffer)
                      (insert data))))

parserにbuffer-stringを指定することで、
HTMLをそのままcomplete(コールバック)のdataに渡せます。

completeはどんな状況でも実行されますが、
successで正常なとき、errorでエラーのときに実行されます。

サイトの内容をw3mで整形してバッファに表示する例

(request "http://rubikitch.com/"
         :parser (lambda ()
                   (shell-command-on-region
                    (point-min) (point-max)
                    "w3m -dump -T text/html"
                    (current-buffer) t)
                   (buffer-string))
         :complete (function*
                    (lambda (&key data &allow-other-keys)
                      (switch-to-buffer "*request-result*")
                      (erase-buffer)
                      (insert data))))

parserはネットワークアクセスの結果をカレントバッファにして
実行するので、 shell-command-on-region でw3mを通して、
(buffer-string)でその内容を文字列にします。

GET でデータを送る

http.el(レビュー) で示したようにHTTPクライアント開発支援サイトの
http://httpbin.org/ を使ってみましょう。

ここからはREADMEの例です。

(request
 "http://httpbin.org/get"
 :params '(("key" . "value") ("key2" . "value2"))
 :parser 'json-read
 :success (function*
           (lambda (&key data &allow-other-keys)
             (message "I sent: %S" (assoc-default 'args data)))))

評価すると

I sent: ((key2 . "value2") (key . "value"))

と出ます。

POST

(request
 "http://httpbin.org/post"
 :type "POST"
 :data '(("key" . "value") ("key2" . "value2"))
 ;; :data "key=value&key2=value2"  ; this is equivalent
 :parser 'json-read
 :success (function*
           (lambda (&key data &allow-other-keys)
             (message "I sent: %S" (assoc-default 'form data)))))

評価すると

I sent: ((key2 . "value2") (key . "value"))

と出ます。

POST file (カレントバッファの全内容が送信されます!!)

(request
 "http://httpbin.org/post"
 :type "POST"
 :files `(("current buffer" . ,(current-buffer))
          ("data" . ("data.csv" :data "1,2,3\n4,5,6\n")))
 :parser 'json-read
 :success (function*
           (lambda (&key data &allow-other-keys)
             (message "I sent: %S" (assoc-default 'files data)))))

このコードのみを書いたバッファでの実行結果

I sent: ((data . "1,2,3
4,5,6
") (current\ buffer . "(request
 \"http://httpbin.org/post\"
 :type \"POST\"
 :files `((\"current buffer\" . ,(current-buffer))
          (\"data\" . (\"data.csv\" :data \"1,2,3\\n4,5,6\\n\")))
 :parser 'json-read
 :success (function*
           (lambda (&key data &allow-other-keys)
             (message \"I sent: %S\" (assoc-default 'files data)))))
"))

HTTP 418を返す場合のコールバック

(request
 "http://httpbin.org/status/418"
 :parser 'buffer-string
 :success
 (function* (lambda (&key data &allow-other-keys)
              (when data
                (with-current-buffer (get-buffer-create "*request demo*")
                  (erase-buffer)
                  (insert data)
                  (pop-to-buffer (current-buffer))))))
 :error
 (function* (lambda (&key error-thrown &allow-other-keys&rest _)
              (message "Got error: %S" error-thrown)))
 :complete (lambda (&rest _) (message "Finished!"))
 :status-code '((400 . (lambda (&rest _) (message "Got 400.")))
                (418 . (lambda (&rest _) (message "Got 418.")))))

結果を*Message*バッファで見てみると

Got 418.
Finished!

が表示されたことがわかります。

HTTP 200(正常)を返す場合のコールバック

(request
 "http://httpbin.org/status/200"
 :parser 'buffer-string
 :success
 (function* (lambda (&key data &allow-other-keys)
              (when data
                (with-current-buffer (get-buffer-create "*request demo*")
                  (erase-buffer)
                  (insert data)
                  (pop-to-buffer (current-buffer))))))
 :error
 (function* (lambda (&key error-thrown &allow-other-keys&rest _)
              (message "Got error: %S" error-thrown)))
 :complete (lambda (&rest _) (message "Finished!"))
 :status-code '((400 . (lambda (&rest _) (message "Got 400.")))
                (418 . (lambda (&rest _) (message "Got 418.")))))
Finished!

とエコーエリアに表示され、 *request demo*バッファが出てきます。

http://httpbin.org/status/200 自体が空の内容なので
バッファの内容も空です。

HTTP 400を返す場合のコールバック

(request
 "http://httpbin.org/status/400"
 :parser 'buffer-string
 :success
 (function* (lambda (&key data &allow-other-keys)
              (when data
                (with-current-buffer (get-buffer-create "*request demo*")
                  (erase-buffer)
                  (insert data)
                  (pop-to-buffer (current-buffer))))))
 :error
 (function* (lambda (&key error-thrown &allow-other-keys&rest _)
              (message "Got error: %S" error-thrown)))
 :complete (lambda (&rest _) (message "Finished!"))
 :status-code '((400 . (lambda (&rest _) (message "Got 400.")))
                (418 . (lambda (&rest _) (message "Got 418.")))))

結果を*Message*バッファで見てみると

Got 400.
Finished!

が表示されたことがわかります。

Atomにxml parserを通し、最初の要素を得る例

(request
 "https://github.com/tkf/emacs-request/commits/master.atom"
 ;; Parse XML in response body:
 :parser (lambda () (libxml-parse-xml-region (point) (point-max)))
 :success (function*
           (lambda (&key data &allow-other-keys)
             ;; Just don't look at this function....
             (let ((get (lambda (node &rest names)
                          (if names
                              (apply get
                                     (first (xml-get-children
                                             node (car names)))
                                     (cdr names))
                            (first (xml-node-children node))))))
               (message "Latest commit: %s (by %s)"
                        (funcall get data 'entry 'title)
                        (funcall get data 'entry 'author 'name))))))

結果

Latest commit: 
        Merge pull request #14 from myuhe/request--curl-write-out-template
     (by tkf)

JSONをPUTする

(request
 "http://httpbin.org/put"
 :type "PUT"
 :data (json-encode '(("key" . "value") ("key2" . "value2")))
 :headers '(("Content-Type" . "application/json"))
 :parser 'json-read
 :success (function*
           (lambda (&key data &allow-other-keys)
             (message "I sent: %S" (assoc-default 'json data)))))

結果

I sent: ((key2 . "value2") (key . "value"))

本サイト内の関連パッケージ


本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。