Slide 1

Slide 1 text

OSSコミットして Zennの課題を解決した話 2024.7.18 新規事業部Zennチーム dyoshikawa

Slide 2

Slide 2 text

2020年10月入社(今年で4年目) 2023年よりZennチーム バックエンドエンジニア ZennではRuby on Rails開発 Webフロントエンド(React|Next)も触ります 自己紹介

Slide 3

Slide 3 text

https://zenn.dev/ エンジニアのための情報共有コミュニティサービス 2023年12月に会員数10万|月間PV数1000万突破を発表 🎉 Zennについて

Slide 4

Slide 4 text

Zennではopen-graph-scraperというnpmパッケージを使っています https://github.com/jshemas/openGraphScraper Open Graph(OG)情報の読み取りを良い感じにやってくれる Open Graph protocol 今回はこのopen-graph-scraperの話 OGの読み取りにopen-graph-scraperを使用

Slide 5

Slide 5 text

リンクカードに使用 記事執筆でURLだけが記述された行があると、その部分がリンクカ ードとして表示される仕様 ➡️ OGの読み取りにopen-graph-scraperを使用

Slide 6

Slide 6 text

使い方の例 import ogs from "open-graph-scraper"; (async () => { const data = await ogs({ url: "https://classmethod.jp/", }); console.log(JSON.stringify(data.result, null, 2)); })(); OGの読み取りにopen-graph-scraperを使用

Slide 7

Slide 7 text

クラスメソッドコーポレートサイトを読み取るとこんな感じ https://classmethod.jp/ { "success": true, "ogLocale": "ja_JP", "ogSiteName": "クラスメソッド株式会社", "ogType": "article", "ogTitle": "クラスメソッド株式会社", "ogDescription": "クラスメソッドはクラウド、デジタル化、データの3つの分野を掛けあわせて、お客様のビジネス成長に向けた技術支援を行います。2022年に "以下省略": "..." } OGの読み取りにopen-graph-scraperを使用

Slide 8

Slide 8 text

v5.2.0より文字コードごとのデコード処理がなくなり、アップデート するとUTF-8でないサイトで問題が発生するようになった Shift_JISやEUC-JPなどのWebページをopen-graph-scraperで読み 取ると結果が文字化けしてしまう 課題: open-graph-scraperをアップデートできない

Slide 9

Slide 9 text

これによりバージョン5.2.0以前に固定する状態が続いていた パッケージの「塩漬け」が課題に 課題: open-graph-scraperをアップデートできない

Slide 10

Slide 10 text

同じ問題がopenGraphScraperリポジトリのIssueに起票されていた 課題: open-graph-scraperをアップデートできない

Slide 11

Slide 11 text

課題: open-graph-scraperをアップデートできない

Slide 12

Slide 12 text

Shift_JIS Webページの例として阿部寛のホームページが取り上げられ ている 本発表でも例として使わせていただきます 🙏 課題: open-graph-scraperをアップデートできない

Slide 13

Slide 13 text

阿部寛のホームページを読み取った場合 import ogs from "open-graph-scraper"; // v6.3.4 (async () => { const { result } = await ogs({ url: 'http://abehiroshi.la.coocan.jp/' }) console.log(JSON.stringify(result, null, 2)) })(); 以下の結果に { "ogTitle": "�������̃z�[���y�[�W", // "阿部寛のホームページ"を期待 "charset": "Shift_JIS", "requestUrl": "http://abehiroshi.la.coocan.jp/", "success": true } 課題: open-graph-scraperをアップデートできない

Slide 14

Slide 14 text

阿部寛のホームページ が �������̃z�[���y�[�W に文字化け なんかこう、うまくゴニョゴニョして �������̃z�[���y�[�W から復元 できたりしないものなの・・・? ここで文字化けがどのように起きるかをみてみる 文字化け部分を修復して使えないの?

Slide 15

Slide 15 text

最初の2文字 阿部 を例に まずはUTF-8の 阿部 をShift_JISとしてデコードするとどう文字化けす るか?をみる UTF-8の 阿部 のバイト列は e998bfe983a8 阿 e998bf 部 e983a8 例1: UTF-8の文字列をShift_JISでデコードする場合

Slide 16

Slide 16 text

e998bfe983a8 をShift_JISでデコードすることを考える Shift_JISの定義表を見ながらバイト列を再解釈して文字を置換する JIS X 0208コード表 - CyberLibrarian 例1: UTF-8の文字列をShift_JISでデコードする場合

Slide 17

Slide 17 text

すると 阿部 は 髦ソ驛ィ になる これが文字化け! ➡️ 例1: UTF-8の文字列をShift_JISでデコードする場合 阿 e998bf 部 e983a8 UTF-8 髦 e998 ソ bf 驛 e983 ィ a8 Shift_JIS

Slide 18

Slide 18 text

e998bfe983a8 というバイト列はそのまま残っていることに注目 そのため、これをUTF-8に再度デコードすると 阿部 を復元できる ➡️ 髦ソ驛ィ は 阿部 に復元できる 髦 e998 ソ bf 驛 e983 ィ a8 Shift_JIS 阿 e998bf 部 e983a8 UTF-8

Slide 19

Slide 19 text

今度は逆にShift_JISからUTF-8にデコード 今回の問題はこっち Shift_JISの 阿部 はバイト列にすると 88a29594 になる 阿 88a2 部 9594 例2: Shift_JISの文字列をUTF-8でデコードする場合

Slide 20

Slide 20 text

88a29594 をUTF-8でデコードすることを考える ところが・・・?このバイト列に対してUTF-8の定義表から対応させ られる文字がない 例2: Shift_JISの文字列をUTF-8でデコードする場合

Slide 21

Slide 21 text

そういう場合はREPLACEMENT CHARACTER( � )の出番になる 実際手元で試すと 阿部 は ���� になった 88a29594 は4バイトなので、1バイト区切りで ���� になったと思 われる(デコーダの実装によりそう) ➡️ 例2: Shift_JISの文字列をUTF-8でデコードする場合 阿 88a2 部 9594 Shift_JIS � efbfbd � efbfbd � efbfbd � efbfbd UTF-8

Slide 22

Slide 22 text

バイト列が efbfbdefbfbdefbfbdefbfbd になってしまい、元の値 88a29594 を保持できていないことに注目 そのためREPLACEMENT CHARACTERに置換されてしまった場合は元 の文字列に戻すことは困難(おそらくできない) REPLACEMENT CHARACTERは復元が困難

Slide 23

Slide 23 text

🤔「 阿部寛のホームページ の文字化け後の �������̃z�[���y�[�W には � じゃない文字も含まれてるけど?」 ちなみに: 文字化けに � でない文字がある件

Slide 24

Slide 24 text

Shift_JISにおいて 阿部寛のホームページ のバイト列は 88a295948ab082cc837a815b83808379815b8357 になる 一部UTF-8としてデコードできる文字がある ちなみに: 文字化けに � でない文字がある件 ������� 88a295948ab082 ~ cc83 z 7a � 81 [ 5b �� 838083 y 79 � 81 [ 5b � 83 W 57 UTF-8

Slide 25

Slide 25 text

UTF-8での cc83 Unicodeでは U+0303 のチルダ ~ はCOMBINED TILDEといって、他の文字と組み合わせて使うものらしい REPLACEMENT CHARACTER � と組み合わさって �̃ になる 発音表記などで使われる ちなみに: 文字化けに � でない文字がある件

Slide 26

Slide 26 text

話を戻して・・・ 解決するため、OSSコントリビューションすることに 解決策: OSSコントリビューションする

Slide 27

Slide 27 text

理由 自前で実装し直すよりライブラリにパッチを当てた方が工数がかか らない open-graph-scraperを使い続けることで、本OSSの今後のエンハン スの利益を受け続けることができる 他の非英語圏ユーザにも役に立つと思った 幸い、活発に開発が続いている状況だったので、プルリクエストが受 け入れられる可能性は高いと思った 解決策: OSSコントリビューションする

Slide 28

Slide 28 text

https://github.com/jshemas/openGraphScraper/pull/206 プルリクエストを作成

Slide 29

Slide 29 text

fetch() + text() の時点でUTF-8デコーディングが行われる response = await fetch(/* 省略 */); body = await response.text(); text() の時点で文字化けが発生してしまうため、ここに手を入れ る必要がある text() は Response インターフェイスのメソッドで、 Response ストリームを取得して完全に読み込みます。 String で解決する プロミスを返します。 レスポンスは常に UTF-8 としてデコードされます。 https://developer.mozilla.org/ja/docs/Web/API/Response/text どんなPRを出したの?元コードの挙動

Slide 30

Slide 30 text

一応、ミニマムなコードで動作を確認してみる (async () => { const response = await fetch("http://abehiroshi.la.coocan.jp/"); const body = await response.text(); // 243文字目がの中身の `�` にあたる console.log(body[243], dummy.charCodeAt(243).toString(16)); // � fffd })(); dummy.charCodeAt(243) の結果(Code Unit)が fffd であり、こ れはUTF-16における � のバイト列である Node.jsは内部的にUTF-16を採用している つまり、やはり元のバイト列は残っておらず復元は困難である どんなPRを出したの?元コードの挙動

Slide 31

Slide 31 text

text() ではなく arrayBuffer() を使うことでデコード前のバイト 列を取得し、 bodyArrayBuffer 変数で保持しておく bodyArrayBuffer を保持したままUTF-8デコードを行い、 を取得するなどして文字コードを特定する 特定した文字コードで bodyArrayBuffer をデコードする const bodyArrayBuffer = await response.arrayBuffer(); const bodyText = Buffer.from(bodyArrayBuffer).toString('utf-8'); const charset = getCharset(bodyText, bodyArrayBuffer, load(bodyText)); if (charset.toLowerCase() === 'utf-8') { body = bodyText; } else { body = decode(Buffer.from(bodyArrayBuffer), charset); } どんなPRを出したの?こう変更した

Slide 32

Slide 32 text

いろいろとやりとりや確認待ちもあり、2週間ほどかかったがマージさ れる ほどなくして本プルリクエストの変更内容を取り込んだv6.4.0もリリ ースされる 動作確認し文字化け問題が直っていることを確認 Zennにも最新版を適用しリリースできた 付き合っていただいたメンテナに感謝 🎉 結果: 無事マージされる 🎉

Slide 33

Slide 33 text

リンクカードの実装にはopen-graph-scraperを使うと楽 文字化けは復元できる場合と困難な場合がある 「OSSを乗り換える」 「OSSを使うのをやめる」の他に「OSSに貢献す る」の選択肢を持ってみると良さそう まとめ

Slide 34

Slide 34 text

本日のお話は下記にもう少し詳しく書いています OSSにコミットしてサービスの課題を解決した話 文字コード入門 文字化けはなぜ起こるのか?どういう時に復元できるのか? ブログ記事

Slide 35

Slide 35 text

筆者は文字コードや符号化方式の専門家ではないため、勉強しながら 本課題に取り組みました 理解が間違っている箇所があればご指摘ください 🙏 ここまでお聞きいただきありがとうございました さいごに

Slide 36

Slide 36 text

特殊用途文字 (Unicodeのブロック) - Wikipedia とほほの文字コード入門 - とほほのWWW入門 UTF-8 - Wikipedia JIS X 0208コード表 - CyberLibrarian 文字列とUnicode · JavaScript Primer #jsprimer JavaScript における文字コードの初歩 - 30歳からのプログラミング (プログラマのための)いまさら聞けない標準規格の話 第2回 文字コー ド実践編 | オブジェクトの広場 参考

Slide 37

Slide 37 text

XML用語事典 [シフトJIS(Shift_JIS) ] Shift_JIS 文字コード表 特殊用途文字 (Unicodeのブロック) - Wikipedia 合成可能なダイアクリティカルマーク - Wikipedia ability to validate UTF-8 encoding · Issue #83 · ashtuchkin/iconv-lite 参考