LoginSignup
2
0

More than 1 year has passed since last update.

文字コードを指定してURLエンコードを行う(Node.js)

Last updated at Posted at 2021-08-09

文字コードを指定してURLエンコードしたい。日本語(が含まれるん)だもの。
そういう話です。

encodeURI()はUTF-8を表すエスケープシーケンスで置換される

Node.jsを用いて、あるAPIをGETメソッドでリクエストする処理を実装していました。

そのAPIはクエリストリングを
APIの名前?key1=value1&key2=value2&signature=認証用の値
にしてリクエスト送信してくださいね、ということで下記のようなソースでクエリストリングを作成。

makeQueryString.js
// 例えばユーザーの情報を取得する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リクエストを送信しても、該当ユーザーがいない、というレスポンスばかりが返ってきていました:fearful:

認証エラーが返ってきていないので、認証は通っている...。
何がダメなんだ?とその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の文字列として表す必要があるので、標準の関数は使えないですね:pensive:

文字コードを指定して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エンコードして返すようにします。

makeQueryString.js
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エンコードするようにします。

makeQueryString.js
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

ひとまず、(今回は)ヨシ!

最終的に、クエリストリング作成関数は以下のようになりました。

makeQueryString.js
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パッケージを使うと可能
    • 数字の0falseはURLエンコード時に消えてしまうので、それらはエンコードしないようにする

というわけで、文字コードを指定して、URLエンコードを行う方法でした。

(今回は本筋から逸れるので記載していませんが、実際は入力値のバリデーションチェックもしています)


  1. 利用するAPIの仕様から「=」と「&」をエンコードさせないためにencodeURI()を使用しています。encodeURIComponent()との違いはMDN Web Docsに例として記載があります 

  2. 翻訳が若干分かりにくかったので、英語版から引用。ちなみにencodeURIComponent()も同様にUTF-8の文字列として表される。 

  3. iconv-liteのgithub内のwikiにサポートしている文字コードの一覧があります。Windows-31jが!指定!できる! 

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0