by shigemk2

当面は技術的なことしか書かない

request.elでマルチバイト文字の入ったJSONをPOSTできない

結論

パッチを投げました。

github.com

概要

EmacsのHTTPクライアント request.el というものがあります。これはcurlのラッパーみたいなもので、GETしたりPOSTしたりするのに大変便利なのですが、以下のようにマルチバイト文字の入ったJSONデータをPOSTしたりPUTしようとしたりすると死にます。POSTできませんが、elisp側でエラーは検出されません。

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

直接の原因はここです。curlでデータを送信するときにテンポラリのバッファにデータを入れているのですが、このときバッファのエンコーディングが 'binary になっているので、マルチバイト文字はユニコードに変換されたうえでHTTPリクエストされます。そのためにリクエストがエラーになってしまいます。

   (when data
     (let ((tempfile (request--make-temp-file)))
       (push tempfile (request-response--tempfiles response))
       (let ((file-coding-system-alist nil)
             (coding-system-for-write 'binary))
         (with-temp-file tempfile
           (setq buffer-file-coding-system 'binary)
           (set-buffer-multibyte nil)
           (insert data)))
       (list "--data-binary" (concat  "@" (request-untrampify-filename tempfile)))))

https://github.com/tkf/emacs-request/blob/master/request.el#L917-L926

んじゃあここの buffer-file-coding-system'utf-8 にして、 set-buffer-multibyte の設定をやめたらいいのでは?と思うのですが、これWindowsなど別の文字コードをベースにしてrequestするとまた別の問題が発生するかもしれないので、ココの部分を変数でもってユーザー側で変更できるようにしました。デフォルトは 'binary なので、既存のものはそのまま使えます。

ほんとはココと結構被るのですが、テストコードやドキュメントを追加した感じになります。

github.com

と言った感じの備忘録です。

問題点

最後のメンテが2017/1で1年近くメンテされておらず、誰がメンテナーなのかわかりません。ので、取り込まれるどころかレビューされるかどうかも怪しいです。気長にまとうと思います。

defcustom

decustom

defcustom Macro: Declare SYMBOL as a customizable variable. SYMBOL is the variable name; it should not be quoted. STANDARD is an expression specifying the variable’s standard value. It should not be quoted. It is evaluated once by ‘defcustom’, and the value is assigned to SYMBOL if the variable is unbound. The expression itself is also stored, so that Customize can re-evaluate it later to get the standard value. DOC is the variable documentation.

  • defvarは初期化で、setqは代入であること
  • ユーザカスタマイズを目的とする変数を宣言するにはdefcustomを使うこと
  • defcustomで定義された変数の変更には常にcustom-set-variablesを用いるべきであること。

http://d.hatena.ne.jp/rubikitch/20100201/elispsyntax

http://memo.sugyan.com/entry/20120104/1325604433

http://kawamuray.hatenablog.com/entry/2013/11/03/180543

JSON_AS_ASCII

JSON_AS_ASCII

JSONの中の文字(non ASCII)をASCIIにエンコードしない。

By default Flask serialize object to ascii-encoded JSON. If this is set to False Flask will not encode to ASCII and output strings as-is and return unicode strings. jsonify will automatically encode it in utf-8 then for transport for instance.

http://flask.pocoo.org/docs/0.12/config/

なのだけど、例のUnicodeDecodeErrorでコケた。

  • Flask 0.12.2
  • Python 2.7.13

memo: GET website including non-ASCII in request.el

概要

request.elで、日本語とかのASCIIじゃない文字列が混ざったページに対してGETすると、curlの結果が文字化けする。

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

理由

このプルリク。elispについて、サブプロセスでcurlを実行しているけど、実行するバッファのエンコードが binary になっている。 Win/Mac/Linux全部に対応しないといけないライブラリなので、binaryにすることで動かなくなるのを回避しているのだと思う。

このプルリクの修正が直接の原因。 github.com

でも、binaryにするとASCIIじゃない、具体的には日本語のサイトでGETすると文字化けする。 github.com

対策

こう書いて、dataのエンコードをutf-8とかにする。

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

ってあるけど、正直知らないとわからないから、ドキュメントやテストには明記したいね。

set-process-coding-system

set-process-coding-system

サブプロセスに対して送るencodeの指定

(set-process-coding-system PROCESS &optional DECODING ENCODING)

Set coding systems of PROCESS to DECODING and ENCODING.
DECODING will be used to decode subprocess output and ENCODING to
encode subprocess input.

M-x describe-function で見られる説明はこれだけで、どんなエンコードを設定できるかは書いていない。

request.el parser

request.el parser

https://tkf.github.io/emacs-request/manual.html

request.elのparserについて。 レスポンスボディをどうやってパースするかを決める。 json-readを使う場合は、JSONのデータ構造を決めることができる。この例の場合だと、json-readしたデータをplistにすることが出来る。

(request
 "http://..."
 :parser (lambda ()
           (let ((json-object-type 'plist))
             (json-read)))
 ...)

すべてのレスポンスボディをstringにしたいなら、 buffer-string にする。

(request
 "http://..."
 :parser '(buffer-string)
 ...)