heguro_log

不定期メモ置き場

Mastodonに来たらRubyを覚える羽目になった話

Fediverse (2) Advent Calender 2021 4日目の記事です。 (22日に枠取得、26日 19:55に投稿。 大変遅くなりました!!!)

adventar.org

あすらも (@dk_k@fedibird.com, @qk_k@mstdn.jp) と申します。
ここ1~2年の間、mstdn.jpやFedibirdに高くない頻度で投稿しています。

今年もAdvent Calenderは見る専のつもりでしたが、なんかFediverse Advent Calender第二会場の枠が多めに空いてたので取ってみました。

本当は12/25までに自鯖を立ててそのことを書きたかったのですが、鯖立てが間に合ってないので、私がMastodonに来てから今までの振り返りをしてみます。 タイトルはちょっと誇大表現入ってます。

// RubyMastodonの実装に使われているプログラミング言語です

// 文章ド下手なので読みづらい・違和感がある箇所が多いと思いますが頑張って読んでください

経緯

Fediverseに住み着く

私がMastodonを知ったのは、TwitterのUserStream終了騒動があった2018年8月です。
最初はmstdn.jpにアカウントを作り、LTLの流れにすこしだけ参加しましたが、「当時持っていたPC・スマホタブレットがどれもクソスペで、まともに使えなかった」*1「精神的になんとなく合わなかった」などの要因があり、馴染めずに離れることとなりました。

「唯一つかえるアプリでこれだし泥4.4ユーザーには既に人権ないらしい」 (「問題が発生したため、Mastalabを終了します。」という表示のスクショを添付している)
クソスペの例 (2013年発売のスマホ)

2020年になり、最新スペックのノートPCを購入して、旧PCからデータ移行をしていたところ、以前使っていたMastodonクライアントアプリ (TheDesk) を見つけました。 そこでふと「今なら出来るんじゃないか」と思い、2月の終わりにjpに復帰しました。
当時は (2018年ほどではないものの) 今よりローカルタイムライン (LTL) が賑わっていて、LTLで会話したり、新規を囲ったり、新規を名乗ったり、例の漢字1文字トゥートをしたり、脳死で駄洒落トゥートをしたり、20万ユーザー達成を祝ったり… と楽しんでいました。

2020年の漢字は例の1文字、2020年の絵文字は「🤣」
最近notestockに追加された「今年の文字」機能で2020年の文字を見た結果です。 漢字は例の1文字、絵文字は駄洒落。 ちなみに今年の漢字は「使」、今年の絵文字は「🎉」でした

その後、jp閉鎖騒動やその前後でのjp鯖の長時間ダウンなどによる大移動をきっかけに、Fediverseの概念と広さを知り、Fedibirdのアカウントも使い始めて今に至ります。

最近は投稿頻度が落ちていて、住み着いたと言えるか怪しくなっていますが…

鯖を立てたい

2020年5~6月に一度、とある理由 (黒歴史) で改造Mastodonサーバを立てようとしたことがあります。 その時はMastodonのコードを十数行弄って、さくらのVPSで仮サーバを立てるまではしましたが、モチベが上がらずそこで頓挫しました。

mstdn.jpで表示した独自ドメインの「あすとら(test)」アカウント
当時データベースが壊れていて投稿・アカウントの削除処理が正しく動いていなかったjpには、今も仮鯖の跡が残っています

これ以降、たまにVirtualBoxMastodonを立てたり、何もわからないなりにMastodonのコードを眺めてみたりしていました。

同年10月くらいから、「Mastodon楽しいけど通信量ヤバい → 通信量を減らしたい → そのための機能 *2 を付けたサーバーを立てたい」という感じで再び自鯖欲が出てきました。
後に「モバイル回線の低速モード (≒通信制限) でも少しは画像を見たい」という理由も加わり、2021年になってから、とりあえず今年中に鯖を立てるという目標をこっそり立てました。

そのまましばらく何もしていませんでしたが、ここ数ヶ月で自鯖に引っ越された方をよく見かけるようになり、「なんか自鯖ブーム起きてるっぽいしやるか~」とようやく準備にとりかかったのが11月下旬です。 この調子なので実際に立つ頃には2022年になってる気がしますが。。。

fedibird.comのメインアカウントのプロフィール画面 「自鯖: 今年中には」と書いてある
夏には既にこう書いていたと思います。 今年中と言っても10月くらいには実現しているはずだった

本家にPRするきっかけ

準備を始めたのと同時期に、初めてのiPhone (iPhone SE 第2世代) を購入し、初期設定をしていました。
初期設定が終わり、App StoreにあるいろいろなMastodonクライアントアプリを試していたところ、「Mastodonクライアントの認証中に、WebAuthn (指紋認証とかYubiKeyとかでログインするやつ) を使ってログインすると、認証画面ではなくWebのMastodonのホームに飛ぶ」というバグを見つけました。
MastodonのコードをVSCodeで検索しまくった結果、原因と直し方の見当はつきました。 ただコードをどう書き換えて直すのが正解か判断できる自信は無かったので、とりあえず不具合をボロボロの英語 *3長々と説明したissueを投げました。
その後「あなたがPull Requestして (≒直して) くれてもいいのよ」 (超絶意訳) と言っていただき、折角なので「数日以内にやる、できなかったら自分には無理だと伝える」と決めて自分で直してみることにしました。

PRを投げるまで

今までもMastodonのコードを雰囲気で適当に弄ることはありましたが、本家に適用されるかもしれないとなると、適当ではなく、これで直るとある程度確信できるようになってから出したいと思いました。

私はこれまで、プログラミング言語ではJavaScript (ECMAScript) がチョットデキルのと、他のいくつかの言語 (Java, Pythonとか) を軽く触ったことがあるくらいで、Rubyを勉強したことはありませんでした。
この状態からRubyRailsを0から勉強しても間違いなく追いつかないので、「該当コードを適当に眺めて、分からないところはWeb検索してRubyや各種ライブラリ (Gem) の公式ドキュメントやブログなどと見比べる」作業を何度も繰り返しました。
また、リアルタイムデバッグの方法が分からなかったので、鯖立てのために用意していたOracle Cloudのテスト用サーバにVSCode SSHで接続して、サーバ上のコードに Rails.logger.warn を大量に挿入して実際の動作ログを見るという、いわゆるprintデバッグで処理の流れを把握しました。

その途中で、他の言語で見たことがない、RubyRails特有の書き方や仕様 (:symbol とかmix-inとか) に遭遇することが何度かありました。 以前適当に弄っていたときは「わからん」で済ませてそれ以上追求していませんでしたが、今回は可能な限り調べてその仕様を理解しようとしました。 *4

これはissueを投げる前の原因追及時からですが、 /spec 以下にあるテストコードの確認も処理の理解に役立ちました。
1から勉強せず場当たり的にやっていたため、「何故このリクエストが来た時このファイルのここの処理がこう発動するのか」というのが全く分かってなかったんですが、このテストコードに分かりやすい処理の概要説明と実際に動作するサンプルコードが書かれていたので、そこで呼び出されている関数などを検索することで、簡単に目的の処理を特定できました。
(最初はテストコードだと分からず、実際の動作でここがいつ呼ばれるのか探ってました)

該当するコードのスクリーンショット。 「using a valid webauthn credential」「instructs the browser to redirect to home」などの記述がある
当時読んだテストコード。 「有効なWebAuthn認証情報がPOSTされたとき」「ホームにリダイレクトするようブラウザに指示する」みたいな説明を書いてくれてます。 ここにある「redirect_path」を検索して、該当する処理にたどり着けました

こんな感じで数日かけてコードを弄って動作テストを繰り返した結果、たった1行の書き換えで直るだろうということがわかり、拍子抜けしながらPRを作成しました。

// VSCodeに「Ruby Solargraph」という拡張機能を入れて入力補完できるようにしたんですが、Gemの機能 (Paperclip, Deviseとか) に関する候補を出せず、Web上のリファレンスを都度確認する必要がありました。 Mastodonディレクトリで yard gems するなど色々試しても何故か変わらず。 なにか方法があるんでしょうか?

結果

PRは無事にマージされ、本家のCommitsに自分のアカウント名が初めて載りました。 次バージョンで修正が反映されるはずです。
たった1行の変更が取り込まれただけで喜ぶのはどうかと思わなくはないですが、正直めっちゃ嬉しかったです。

また、RubyRails特有の仕様を調べて知ったことで、Mastodonの各ファイル同士の繋がりなど、今まで分からなかったことが大体分かって、一気に視界が広がり、全体像がなんとなく見えるようになりました。 これでほんの少しですがMastodonの実装に詳しくなれた気がします。

最後に

ここまで好き勝手に文章を書いて公開したのは久しぶりです。 Fediverseに来てからは初めてかもしれません。

復帰当時は、Twitterの公開アカウントでの活動を過去の黒歴史のためにほぼストップして数年経ったという状況でした。
またネット上に (一応) 名前をもった存在として足を踏み入れ、こうして文章を公開できたのは、Mastodon、そしてFediverseで関わってくださった方々のお陰だと思っています。 ありがとうございました。

リアルの事情などいろいろあって鯖立て作業が止まっていましたが、鯖用ドメインを既に買ってしまったので近いうちに作業に戻ろうと思います。

この記事の締めとして (?) 、今回のために過去の投稿を見直した際に見つけて以来、ずっと頭の隅に残っている投稿をおすそ分けさせていただきます。

睡眠はしっかりとりましょう! *5
お読みいただきありがとうございました!

脚注

*1:2018年当時はAndroid2.xの時代からある軽量な2ch専ブラやTwitterクライアントを使っていて、「軽量最強! 重くて端末容量をひっ迫させる公式Twitterアプリを入れるなど有り得ない」とマジで思ってました。 そんな状況で、2017年に広まったイケイケなサービスであるMastodonに適応できなかったのも無理はないかも。

*2:連合先サーバから来るでかい (数MBとかある) 画像をなんやかんやしてファイルサイズを減らす処理を検討してます。

*3:英和和英辞典と睨めっこしながら2時間かけて書いた結果がこれ ("a issue" とか…) です。 でも多分伝わったのでヨシ!

*4:当時のスレッド 元リンク①
f:id:heguro:20211226190730p:plain:w500f:id:heguro:20211226190805p:plain:w500

*5:なお、この部分を書いたの深夜4時

Cloudflareで送信元ポート番号を取得する

Cloudflareではヘッダーに発信元IPアドレスしか載らないから不可能だと思っていたら、最近出来るようになってたので記録します。

経緯

IPv4 アドレスが共有されている環境でよろしくないことをされた時のために、クライアント (発信者) の IP アドレスだけでなく送信元 (発信元) ポート番号も記録したほうがよいらしいです。

leia.5ch.net
www.jpne.co.jp

リンク先 PDF の p71 「サーバのアクセスログに関する対応」 より:

  • RFC 6302 では、ログに送信元ポート番号を含めることを推奨
  • 平成27年に、プロバイダ責任制限法第4条第1項 「発信者情報を定める省令」 の一部が改正され、開示の対象となる発信者情報にポート番号が追加

やり方

今年6月に実装された、 変換ルール (Transform Rules) の中の HTTP Request Header Modification (HTTPリクエストヘッダの修正) という機能を使います。
blog.cloudflare.com

※2021/10/25時点では 「ヘッダーを (の) 修正」 (Header Modification) という名前でしたが、2021/12/05現在「 HTTP Request Header Modification」 に変わっています

f:id:heguro:20211025004344p:plain
サイトのダッシュボードから、「ルール」 → 「変換ルール」 → 「変換ルールを作成」 → 「HTTP Request Header Modification」
f:id:heguro:20211025012148p:plain
デフォルトで表示される式ビルダーでは「条件なし」に設定できないので、「式を編集」をクリック
f:id:heguro:20211025002335p:plain
条件を true、ヘッダーの設定方法を Set dynamic、 値を cf.edge.client_port とする

CF- から始まるヘッダー名は使えません。 また、 X-Forwarded-Port などのありきたりな名前にすると、後に公式実装されて競合する可能性も無くはないので、被らさそうなものにするとよいでしょう。

これで、Cloudflareを経由する全てのリクエストでヘッダに送信元ポート番号が載るようになります。 Apache や nginx のログ対象に加える *1 なり、Webアプリの実装を弄るなりして、IPアドレスと一緒に記録しましょう。

他の情報も取得する

発信元ポート番号以外にも色々取得できます。

使えそうなフィールドを適当に載せてみました。

f:id:heguro:20211025014339p:plain

Mod-CF-ASN: ip.geoip.asnum
Mod-CF-Client-Port: cf.edge.client_port
Mod-CF-Client-Trust-Score: cf.client_trust_score
Mod-CF-Geo: concat("continent=", ip.geoip.continent, "; country=", ip.geoip.country)
Mod-CF-Http-Version: http.request.version
Mod-CF-Is-Good-Bot: cf.client.bot
Mod-CF-Original-Host: http.host
Mod-CF-Recieved-Timestamp: http.request.timestamp.sec
Mod-CF-Recieved-Timestamp-Msec: http.request.timestamp.msec
Mod-CF-Threat-Score: cf.threat_score

実際のヘッダー例

2021/10/25 に、この設定をして実際にサーバーで取得できた情報です。 ブラウザが送信したヘッダは除いています。 xxxx や example の部分は伏せてます。

CDN-Loop: cloudflare
CF-Connecting-IP: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
CF-IPCountry: JP
CF-Ray: 6a34abf38fdc0a66-KIX
CF-Visitor: {"scheme":"http"}
Mod-CF-ASN: xxxx
Mod-CF-Client-Port: 58846
Mod-CF-Client-Trust-Score: 90
Mod-CF-Geo: continent=AS; country=JP
Mod-CF-Http-Version: HTTP/3
Mod-CF-Is-Good-Bot: false
Mod-CF-Original-Host: xxxx.example.com
Mod-CF-Recieved-Timestamp-Msec: 10
Mod-CF-Recieved-Timestamp: 1635093886
Mod-CF-Threat-Score: 0
X-Forwarded-For: xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
X-Forwarded-Proto: http

注意点

Freeプランだと、URLリライトとヘッダーの修正で合わせて2→10枠まで

2021/12/05時点で10枠になってました!!
まだドキュメントでは2枠のままなので、正式な変更なのかわかりませんが…

以下古い記述です:

2枠埋まっていると新規作成ができません。 ルールを無効化しても枠が減らないので、複数のルールで運用したい場合かなりつらいです。
ページルールのように枠を買うこともできません。
Proプランにすると5枠に増えますが、このためだけに$20/月はなかなか払えないと思います…

枠数が少ない中でやりくりしていると、間違ってルールを消してしまう可能性も高まります。
該当するヘッダーの行が無くても不具合が起こらないよう実装しましょう。

Cloudflare Workers で再度同じドメインに fetch する場合、 クライアントでなく Workers の情報が入る

変換ルールの適用条件を絞る、 Workers 側でヘッダを弄るなどで対応しましょう。

そのほか

  • Cloudflare Workers 単体では出来ないようです
  • 10年ぶりくらいにブログ書いた… (この名義では初めて)

*1: 方法は 「サーバーソフトウェア名 x-forwarded-for ログ」 などでググれば出ます