文字コードを指定して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リクエストを送信しても、該当ユーザーがいない、というレスポンスばかりが返ってきていました
認証エラーが返ってきていないので、認証は通っている...。
何がダメなんだ?とそのAPIの開発者向けドキュメントを再読すると、以下の記述。
「Windows-31JでURLエンコードを行ってください」
文字コード 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
ひとまず、(今回は)ヨシ!
最終的に、クエリストリング作成関数は以下のようになりました。
function makeQueryString(apiName, params) {
let queryString = `${apiName}?`;
// リクエスト内容分ループ
for (const key in params) {
// 値のみURLエンコードを行う(文字コードを指定)
// 値が数字の0の時はURLエンコードを行わない
const value = params[key] === 0 ? params[key] : conv.encode(params[key], encoding);
queryString += `${key}=${value}&`;
}
// 末尾に認証用の値を付与
queryString += `signature=xxx`;
return queryString;
}
URLエンコードのまとめ
- JavaScriptの
encodeURI()
/encodeURIComponent()
関数はUTF-8の文字を表すエスケープシーケンスで置換される - UTF-8以外の文字コードを指定する場合は、
iconv-urlencode
パッケージを使うと可能- 数字の
0
やfalse
はURLエンコード時に消えてしまうので、それらはエンコードしないようにする
- 数字の
というわけで、文字コードを指定して、URLエンコードを行う方法でした。
(今回は本筋から逸れるので記載していませんが、実際は入力値のバリデーションチェックもしています)
-
利用するAPIの仕様から「=」と「&」をエンコードさせないために
encodeURI()
を使用しています。encodeURIComponent()
との違いはMDN Web Docsに例として記載があります ↩ -
翻訳が若干分かりにくかったので、英語版から引用。ちなみに
encodeURIComponent()
も同様にUTF-8の文字列として表される。 ↩ -
iconv-liteのgithub内のwikiにサポートしている文字コードの一覧があります。Windows-31jが!指定!できる! ↩