Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.jsでFetch APIで取得したShift_JISのテキストをTextDecoderではなくnpmのパッケージを使ってデコードする

$
0
0

Overview

Node.jsでFetchでHTMLのデータを取得してresponse.text()でテキストを取得したら文字化けが:scream:
どうしてだろうと思ったらHTMLがShift-JIS、かつtext()はまさかのUTF-8専用とのこと。
https://developer.mozilla.org/ja/docs/Web/API/Body/text

レスポンスは常に UTF-8 としてデコードします。

さすがモダンなAPIだな、この割り切り嫌いじゃない:wink:

それならばとブラウザ同様TextDecoderを使ってShift-JISに変換しようとしたらエラー[ERR_ENCODING_NOT_SUPPORTED]が:fearful:
Node.jsのドキュメントみてもShift-JISサポートあるやん!
https://nodejs.org/docs/latest/api/util.html#util_class_util_textdecoder

ググってみたらICUとかいうのが必要なようで…
マイナーなデータは別途提供するから自分で取り込んでねとごもっともな意見。
http://var.blog.jp/archives/80396639.html

私はCloud Build(CI/CD)やCloud Functions(FaaS)上でも動作するようなお手軽なのを求めているため、ICUは使わない方向で検討しました。

ちなみにブラウザの場合は、ブラウザ自体が多数の文字コードを扱っているため工夫いらずで変換できる模様。
@kerupani129さんが記事を書かれています。
https://qiita.com/kerupani129/items/6646eb920c23658bc525

Target reader

  • Shift_JISのテキストをnpmのパッケージだけでデコードしたい方

Prerequisite

  • バックエンドはCloud Build(CI/CD)やCloud Functions(FaaS)を利用する、つまりNodeの起動オプションを指定するようなことはできない。
  • Node.jsのバージョンはCloud Functionに依存しているため、現時点ではV10系とする。
  • ソースコードはimport/exportを使ってブラウザと記述を統一できるよう、esmというパッケージを利用している。

Body

2つの選択肢

npmだけで行こうとすると選択肢は二つある。

  • full-icuをインストールしてTextDecoderを利用する。
  • iconv-liteの類の独自の文字コード変換で処理する。

今回何より大事にしたいのはどこでもインストールエラー等なく動くことを最優先とする。
前者のfull-icuはICUをダウンロードするようで、サイズの大きさの懸念と、何よりダウンロードがCloud Buildで利用できるかの懸念がある。
ローカルマシンと比較してフルマネージドサービスではいろいろと制約があるため、インストール時に権限等により失敗することが少なくない。
また、Cloud FunctionsではNode.jsの起動オプションは指定できず、環境変数についても動作するか不明。

ということで、後者のパッケージで文字コード変換を完結するものを利用する。

iconv-lite

iconv-liteの週間ダウンロード数は2千万でversionが1.00に到達していないが十分すぎる人気。
https://www.npmjs.com/package/iconv-lite

Node.jsなのでパッケージサイズは気にならないが、念のため調べるとMINIFIED + GZIPPEDで150KBなので、フロントで利用するわけじゃないので合格。
package.json覗いてみたところ、依存関係も1つと素晴らしい。
https://bundlephobia.com/result?p=iconv-lite@latest

そして、私が作ったFetchAPIのソースコード。
Shift-JIS対策だけではなく、タイムアウトや認証エラーが含まれるため少し複雑になっている。
Shift-JISの変換は、fetchText()のoption.sjisの部分で、iconv-liteのdecode()を利用しているだけ。
decode()にはBufferを渡す必要があるため、Buffer.from()でresponseを変換している。

responseをみて文字コード指定も考えたが、ヘッダーになかったり指定が間違っていることもありえるので、割り切ってShift-JISのURLの場合にはsjisオプションを付与してコールしている。

utils/fetch.js
importiconvfrom'iconv-lite';importfetchfrom'node-fetch';importAbortControllerfrom'abort-controller';// for "reason: unable to verify the first certificate"// see: https://github.com/node-fetch/node-fetch/issues/15#issuecomment-533869809importhttpsfrom"https";constagent=newhttps.Agent({rejectUnauthorized:false});constfetchCore=async(url,option={})=>{// set timeout after 15sconstcontroller=newAbortController();consttimeout=setTimeout(()=>{controller.abort();},option.timeout||15000);try{constresponse=awaitfetch(url,{method:'GET',mode:'cors',cache:'default',agent:option.resolveUnauthorized?agent:undefined,signal:controller.signal,// for timeout...option,});if(!response.ok){constdescription=`status code:${response.status} , text:${response.statusText}`;thrownewError(description);}returnresponse;}finally{clearTimeout(timeout);}}constfetchJson=async(url,option)=>{constresponse=awaitfetchCore(url,option);returnawaitresponse.json();}constfetchText=async(url,option)=>{constresponse=awaitfetchCore(url,option);if(option.sjis){returniconv.decode(Buffer.from(awaitresponse.arrayBuffer()),"shift_jis")}returnawaitresponse.text();}export{fetchJson,fetchText};

Conclusion

ブラウザを使っているともはや文字コードなんて気にすることもなかったが、改めブラウザ大変だなと実感。
Shift-JISはあと十年くらいは生存していそうなので、CI/CD環境下ではiconv-lite様様といったところ。

Have a great day!


Viewing all articles
Browse latest Browse all 8691

Trending Articles