MessagePackでエンコードしたバイト列をaxiosでクエリパラメータに含めて送りたかったのですが、ちょっとハマったので書き留めておきます。
環境
バージョン | |
---|---|
node | 10.12.0 |
axios | 0.18.0 |
msgpack-lite | 0.1.26 |
目標
const data = [{
bar: 'bar',
foo: 'foo',
}]
このようなObjectを送ることを考える。
https://msgpack.org/ で確認してみるとこのObjectはMessagePackによって以下のようなバイト列になる。
91 82 a3 62 61 72 a3 62 61 72 a3 66 6f 6f a3 66 6f 6f
ということはクエリパラメータはこんな感じになってくれると嬉しい。
...?data=%91%82%A3bar%A3bar%A3foo%A3foo
これはUTF-8でエンコードできるバイトはエンコードし、できないバイトは%を頭につけて16進数として出力するパーセント・エンコーディングです。
課題
まず書いたコードはこんな感じ。
const data = {
bar: 'bar',
foo: 'foo',
}
const buf = msgpack.encode(data)
axios.get(
endpoint,
{ params: { data: buf } },
)
送られたクエリパラメータ
...?data=%7B%22type%22:%22Buffer%22,%22data%22:[130,163,98,97,114,163,98,97,114,163,102,111,111,163,102,111,111]%7D
BufferのObjectがそのままエンコードされてるっぽい…。
ということで以下のようにBufferをStringに修正してみる。
const buf = msgpack.encode(data).toString()
axios.get(
endpoint,
{ params: { data: buf } },
)
送られたクエリパラメータ
...?data=%EF%BF%BD%EF%BF%BDbar%EF%BF%BDbar%EF%BF%BDfoo%EF%BF%BDfoo
ん?UTF-8でエンコードできない部分がなんかすごいことになってる。
調べてみるとエンコード失敗した時の文字列をさらにエンコードしようとしてこうなってしまうらしい…。
(参照)
解決策
これではリクエストを受け取った側でmsgpackとしてデコードできないのでどうしたものかと調べてみると、axiosではクエリパラメータのシリアライザをカスタマイズできるらしい。
実装したコードはこんな感じ。
const buf = msgpack.encode(data);
axios
.get(
endpoint,
{
params: { data: buf },
paramsSerializer: (params) => { // シリアライザをカスタマイズ
let result = '';
for(let k of Object.keys(params)) {
if (result !== "") {
result += "&";
}
if (params[k] instanceof Buffer) { // Bufferだけエンコードが失敗しないようにする
let encodedData = '';
for (let value of params[k].values()) {
encodedData += '%' + value.toString(16);
}
result += k + "=" + encodedData;
} else {
result += k + "=" + encodeURI(params[k]); // Buffer以外は普通にエンコード
}
}
return result;
},
},
);
paramsSerializerを指定することによってシリアライザをカスタマイズできました。
内容としてはBufferに対してのみ単純なパーセント・エンコーディングを適用するようにしたものです。
まとめ
axiosは簡単なことは簡単にできながら、カスタマイズも細かくできるのがいいなと思いました。
他にいい方法があればコメントください!