request.elも魅力的なのですが今回はurl-retrieve-synchronouslyを使ってみます。
url-retrieve系は色々おかしな挙動を示すこともありますが基本的な使い方は簡単です。
クエリ文字列をPOSTしてレスポンス全体を文字列で受け取るには次のようにします。(Http Post - EmacsWikiより)
(require 'url)
(let ((url-request-method "POST")
(url-request-extra-headers `(("Content-Type" . "application/x-www-form-urlencoded")))
(url-request-data "field1=Hello&field2=from&field3=Emacs"))
(with-current-buffer (url-retrieve-synchronously url)
(buffer-string)))
また、今回はjsonの処理を多用します。jsonとlispオブジェクトとの変換は、Emacsに標準で入っているjson.elを使用します。
(require 'json)
(json-read-from-string "{\"name\": \"Taro\", \"age\": 18}")
(json-encode '((name . "Taro") (age . 18)))
実際には文字列はutf-8でやりとりする必要があるので、encode-coding-stringやdecode-coding-stringで変換してやる必要があります。環境(標準のcoding system)によるのかもしれませんが私の所では送受信とも変換してやる必要がありました。
(json-read-from-string (decode-coding-string response-body 'utf-8))
(encode-coding-string (json-encode json-obj) 'utf-8)
これらを統合して、本プロジェクトでHTTPにアクセスするために使う関数をこしらえました。
主に使うのは gcal-retrieve-json- で始まる三つの関数です。
- (gcal-retrieve-json-get url params)
- (gcal-retrieve-json-post-www-form url params)
- (gcal-retrieve-json-post-json url params obj)
これらは url にリクエストを出して返ってきたレスポンスをJSONとして解釈してその結果をlispオブジェクトで返します。
メソッドをGETにするかPOSTにするか、また、POSTにするなら何をPOSTするかで三つのバリエーションを用意しました。
関数の実装は次の通りです。
(defun gcal-http (method url params headers data)
(let ((url-request-method (or method "GET"))
(url-request-extra-headers headers)
(url-request-data data))
(gcal-parse-http-response
(url-retrieve-synchronously (gcal-http-make-query-url url params)))))
(defun gcal-parse-http-response (buffer)
"バッファ内のHTTPレスポンスを解析して、ステータス、ヘッダー、ボディ等に分割します。"
(with-current-buffer buffer
(beginning-of-buffer)
(if (looking-at "^HTTP/[^ ]+ \\([0-9]+\\) ?\\(.*\\)$")
(let ((status (string-to-number (match-string 1)))
(message (match-string 2))
(headers)
(body))
(next-line)
(while (not (eolp))
(if (looking-at "^\\([^:]+\\): \\(.*\\)$")
(push (cons (match-string 1) (match-string 2)) headers))
(next-line))
(next-line)
(setq body (buffer-substring (point) (point-max)))
(list status message headers body)
))))
(defun gcal-http-get (url params)
"指定されたurlへparamsをGETします。"
(gcal-http "GET" url params nil nil))
(defun gcal-http-post-www-form (url params)
"指定されたurlへparamsをPOSTします。"
(gcal-http "POST" url nil
'(("Content-Type" . "application/x-www-form-urlencoded"))
(gcal-http-make-query params)))
(defun gcal-http-post-json (url params json-obj)
"指定されたurlへjsonをPOSTします。"
(gcal-http "POST" url params
'(("Content-Type" . "application/json"))
(encode-coding-string (json-encode json-obj) 'utf-8)))
(defun gcal-retrieve-json-get (url params)
"指定されたurlへparamsをGETして得られるjsonを解析したリストを返します。"
(gcal-http-response-to-json (gcal-http-get url params)))
(defun gcal-retrieve-json-post-www-form (url params)
"指定されたurlへparamsをPOSTして得られるjsonを解析したリストを返します。"
(gcal-http-response-to-json (gcal-http-post-www-form url params)))
(defun gcal-retrieve-json-post-json (url params json-obj)
"指定されたurlへparamsとjsonをPOSTして得られるjsonを解析したリストを返します。"
(gcal-http-response-to-json (gcal-http-post-json url params json-obj)))
(defun gcal-http-response-to-json (response)
"レスポンス(gcal-http, gcal-parse-http-responseの戻り値)のボディをjson-read-from-stringします。"
(let* ((status (nth 0 response))
(body (nth 3 response)))
(json-read-from-string (decode-coding-string body 'utf-8))))
(defun gcal-http-make-query (params)
"クエリ文字列を作成します。(ex: a=1&b=2&c=3)"
(url-build-query-string params))
(defun gcal-http-make-query-url (url params)
"クエリ付きのURLを作成します。(ex:http://example.com/?a=1&b=2&c=3)"
(let* ((query (gcal-http-make-query params)))
(if (> (length query) 0) (concat url "?" query) url)))