文字コードを指定してURLエンコードしたい。日本語(が含まれるん)だもの。
そういう話です。
encodeURI()はUTF-8を表すエスケープシーケンスで置換される
Node.jsを用いて、あるAPIをGETメソッドでリクエストする処理を実装していました。
そのAPIはクエリストリングを
APIの名前?key1=value1&key2=value2&signature=認証用の値
にしてリクエスト送信してくださいね、ということで下記のようなソースでクエリストリングを作成。
// 例えばユーザーの情報を取得するAPI
// API名称
const apiName = 'getUser';
// リクエスト内容
const params = {
name : '則巻アラレ',
age : 13
}
/**
* クエリストリングを作成する
* @param {String} apiName API名称
* @param {Object} params リクエスト内容
* @returns クエリストリング
*/
function makeQueryString(apiName, params) {
let queryString = `${apiName}?`;
// リクエスト内容分ループ
for (const key in params) {
const value = params[key]; // 値
queryString += `${key}=${value}&`;
}
// 末尾に認証用の値を付与
queryString += `signature=xxx`;
// URLエンコード
return encodeURI(queryString);
}
// クエリストリングの作成
const queryString = makeQueryString(apiName, params);
しかし、encodeURI()1を用いてURLエンコードを行ったクエリストリングを使って、GETリクエストを送信しても、該当ユーザーがいない、というレスポンスばかりが返ってきていました。ぴえん
(該当するnameとageを持つユーザーは作成したのに!)
認証エラーが返ってきていないので、認証は通っている...。
何がダメなんだ?とそのAPIの開発者向けドキュメントを再読すると、以下の記述。
ドキュメントさん「Windows-31JでURLエンコードを行ってください」
私「OK Google、Windows-31Jとは」
文字コード CP932 / Windows-31J
CP932とは、日本語の文字などを収録した文字コード規格の一つで、Shift JIS規格を元にマイクロソフト(Microsoft)社が独自に拡張したもの。微妙に異なる複数の仕様がある。
〔......〕同社(注: Microsoft社)はCP932のインターネット上での識別名としてIANAに「Windows-31J」を登録し〔......〕
CP932とは - IT用語辞典
ぶっちゃけこの文字コード初めて聞いたな...と思いつつ、
encodeURI()について確認すると、確かにrepresenting the UTF-8 encoding of the character2と記載があるので、UTF-8の文字を表すエスケープシーケンスで置換されるようです。
先ほどのコードだと、以下クエリストリングは
getUser?name=則巻アラレ&age=13&signature=xxx
encodeURI()によって、以下に変換されます。
name部分は、UTF-8の文字を表す(らしい)エスケープシーケンスで置換されています。
getUser?name=%E5%89%87%E5%B7%BB%E3%82%A2%E3%83%A9%E3%83%AC&age=13&signature=xxx
今回は、URLエンコードが行われた文字列を、Windows-31Jの文字列として表す必要があるので、標準の関数は使えないですね
文字コードを指定してURLエンコードができるiconv-urlencode
やっと本題。
UTF-8以外の文字コードを指定してURLエンコードを行わなければならない!!!という方が世界にもいたのでしょう。
iconv-urlencodeというパッケージを用いることで、それが可能です。
上記サイトの説明によると、iconv-liteパッケージで指定可能な文字コード3であれば、URLエンコード/デコード可能なようです。Shift_JIS, Windows-31j等が使用可能です。
以下の実行環境で実施していきます。
$ node --version
v14.17.4
$ npm --version
6.14.14
インストール。
npm install iconv-urlencode
あとは先ほどのソースコードに、モジュールの読み込みを追加し、
関数内で作成したクエリストリングをURLエンコードして返すようにします。
const conv = require('iconv-urlencode');
const encoding = 'Windows-31j'; // 文字コード
// ......
// 略
// ......
function makeQueryString(apiName, params) {
let queryString = `${apiName}?`;
// リクエスト内容分ループ
for (const key in params) {
const value = params[key]; // 値
queryString += `${key}=${value}&`;
}
// 末尾に認証用の値を付与
queryString += `signature=xxx`;
// 文字コードを指定し、URLエンコード
return conv.encode(queryString, encoding); // iconv-urlencodeを利用
}
...と上記ソースコードだと実行結果がこうなります。
# getUser?name=則巻アラレ&age=13&signature=xxx のURLエンコード結果
getUser%3Fname%3D%91%A5%8A%AA%83A%83%89%83%8C%26age%3D13%26signature%3Dxxx
今回は?, =, &は変換されてほしくないので、修正。
値のみURLエンコードするようにします。
function makeQueryString(apiName, params) {
let queryString = `${apiName}?`;
// リクエスト内容分ループ
for (const key in params) {
// 値のみURLエンコードを行う(文字コードを指定)
const value = conv.encode(params[key], encoding);
queryString += `${key}=${value}&`;
}
// 末尾に認証用の値を付与
queryString += `signature=xxx`;
return queryString;
}
結果は以下のようになりました。
# getUser?name=則巻アラレ&age=13&signature=xxx のURLエンコード結果(Windows-31J)
getUser?name=%91%A5%8A%AA%83A%83%89%83%8C&age=13&signature=xxx
これで無事に文字コードWindows-31Jの文字列として、サーバ側でデコードができて、
指定ユーザーの情報がAPIのレスポンスで返ってくるようになりましたとさ、めでたしめでたs
......残念ですが、このままではある条件下でエラーになる場合があります。
値だけURLエンコードをしていると、数字の0が消える
それは、以下のユーザーで検索を行ったときに起きました...。
// リクエスト内容
const params = {
name : '則巻ターボ',
age : 0
}
# 作成されたクエリストリング
getUser?name=%91%A5%8A%AA%83%5E%81%5B%83%7B&age=&signature=xxx
おわかりいただけただろうか...。
そう、ageの値が欠けているのだ...\キャー/
ageの値を文字列の'0'とした場合はクエリストリングにage=0&...となりますが、
数字の0であった場合は、age=&...として値が欠けてしまいます。
今回は値が数字の0であった場合は、URLエンコードしないようにしました。
const value = params[key] === 0 ? params[key] : conv.encode(params[key], encoding);
# getUser?name=則巻ターボ&age=0&signature=xxx のURLエンコード結果(Windows-31J)
getUser?name=%91%A5%8A%AA%83%5E%81%5B%83%7B&age=0&signature=xxx
ひとまず、(今回は)ヨシ!
というわけで、文字コードを指定して、URLエンコードを行う方法でした。
(今回は本筋から逸れるので記載していませんが、実際は入力値のバリデーションチェックもしています)
利用するAPIの仕様から「=」と「&」をエンコードさせないためにencodeURI()を使用しています。encodeURIComponent()との違いはMDN Web Docsに例として記載があります ↩
翻訳が若干分かりにくかったので、英語版から引用。ちなみにencodeURIComponent()も同様にUTF-8の文字列として表される。 ↩
iconv-liteのgithub内のwikiにサポートしている文字コードの一覧があります。Windows-31jが!指定!できる! ↩
↧