Overview
Node.jsでFetchでHTMLのデータを取得してresponse.text()
でテキストを取得したら文字化けが
どうしてだろうと思ったらHTMLがShift-JIS、かつtext()
はまさかのUTF-8専用とのこと。
https://developer.mozilla.org/ja/docs/Web/API/Body/text
レスポンスは常に UTF-8 としてデコードします。
さすがモダンなAPIだな、この割り切り嫌いじゃない
それならばとブラウザ同様TextDecoderを使ってShift-JISに変換しようとしたらエラー[ERR_ENCODING_NOT_SUPPORTED]が
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オプションを付与してコールしている。
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!