LWPでtimeout指定が効かない
自作実装のActivityPubサーバーから、フォロワーさんの所属しているリモートサーバーに投稿を送信する時に、お相手のリモートサーバーがなんらかの事情で落ちてるケースがある。
レスポンスは500番代のエラーとなる。
perlで定番のLWP UserAgentを使ってリクエストを投げてるんだけど。
400番代のエラーと違って、サーバーに問題が発生している500番のエラーの場合、返事が戻ってくるのに時間がかかる、待たされる。投稿するためのPOSTもアカウント情報取得のためのGETもなかなか戻ってこない。
デフォルトだと、リクエストを投げてお相手の反応が返ってくるまで180秒(3分)待つことになっている。
LWP::UserAgent - Web ユーザエージェントクラス
https://perldoc.jp/docs/modules/libwww-perl-6.04/LWP/UserAgent.pod
timeout( $secs )
秒単位のタイムアウト値を取得または設定します。 デフォルトのtimeout()の値は180秒、つまり3分です。
サーバへの接続においてtimeout秒反応がないと、リクエストは中断します。 つまり、トランザクションが完了してrequest()メソッドが実際に返るまでの 時間を意味します。
ということなのでtimeoutに10秒とか設定してみてたんだけど、どうもそれが効いてない。
検索したら、やっぱりtimeoutの指定は効かないことがあるらしい。
LWP::UserAgentのタイムアウトがうまく効かなかった事象の調査 (序章)
https://papix.hatenablog.com/entry/2020/12/25/180640
もう少し粘って検索してみると
lWP::UserAgentの「:content_cb」(コールバック)のサブルーチンでSIGNALを設定してalarmで対応できるという記事を発見。
LWP::UserAgent get callback with timeout
https://stackoverflow.com/questions/29071348/lwpuseragent-get-callback-with-timeout
リクエストをサブルーチンで処理することになるのが、素人のわたし的に難しそう…影響範囲がわからない。
思いつきでデフォルトを180秒にしたわけじゃあるまいし、なにか理由がありそう。それをここで指定しちゃうと、全部に影響するわけだしなあ、と。
てことで、それならリモートに投稿を送信するリクエストのサブルーチン限定にしてしまえば大丈夫っぽいんじゃないかと。
サブルーチン丸ごとSIGNALのALRMを設定してevalで捕まえることにした。
結果オーライとか、やっつけ仕事だけは昔から得意だし。
my $res = "";
eval{
local $SIG{ALRM} = sub{die "timeout";};
alarm $self->{timeout};
$res = $self->post_actpb({url=>$u, content=>$args->{json}});
alarm 0;
};
if( $@ ){
printf qq{ERROR deliver %s ::: %s}, $u, $@;
}
設定を15秒にしてみたら、意図通りにtimeoutをキャッチして、待ち時間が少なくなった。
ちゃんとしたActivityPubサーバーのMastodonなんかだと、リクエストの送受信は裏側でやってるんで、アクセスしてるユーザーが待たされることはないはず。
わたしの自作ActivityPubサーバーは最低限で、いろいろ手抜きしていて表示するだけのために3分以上待たされるんだよなあ。自業自得というか。
サーバーのお守り代わりの画像
(松戸市立博物館)
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」
おひとり様ActivityPubサーバーの自作実装
ホームページに「おひとり様ActivityPubサーバーの自作実装」ページを追加した。元ネタはこのブログに書いてきたエントリ。
ブログは便利なんだけど、ひとつのテーマのエントリを追いかけるのが面倒くさい。タグづけしたところで、わかりやすい体裁にはならない。なもんで、個人ホームページの静的ページで再構成してみようとちょっとずつ。
おひとり様ActivityPubサーバーの自作実装
https://www.doncha.net/activitypub.html
ていうか、2023年8月頃にActivityPubサーバーを実装…たかだか去年のことなのに、もはやいろいろ忘れていて、自分にビックリ、だ。機能追加のたびに、自分で自分のブログのエントリを辿って確認するのが怠い。
それと、この自作ブログは日常雑記を対象に作ったもので、スクリプトやHTMLなんかのコードの記述・掲載は後からつけたし。めちゃくちゃ見にくい。
ホームページでペラ1枚の静的ページにしてしまえば、問題はすべて解決。
エディタで直接html、cssを書くだけなので、タグやコードなんかもやりたい放題。
webfingerなどFediverseからアカウントとして認識されるのに必要なファイル
https://www.doncha.net/activitypub/activitypub001.html
公開鍵・秘密鍵を使って署名の生成と認証
https://www.doncha.net/activitypub/activitypub002.html
↑xmlなんかの不等号記号だけは変換する必要があるけど、それ以外は全部OKなのが気楽。
そもそもブログは動的なので、入力されたものはリスク回避もろもろのために入力内容を確認してよろしくない記述、文字は別のものに置き換えたり削除したりしている。
ので、書き込む時にどうしても不自然で不便になってしまいがち。
静的ページの自由度には敵わない。
松戸市立博物館の特別展で変な土器をたくさん見てきた!
展示の数も内容もはんぱなく充実していて驚いた。ハコモノ行政の正しい使い方だなあ。
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」
ActivityPubの投稿公開範囲
あれ、どうするんだっけ?と忘れがちでしょっちゅう検索してしまう投稿の公開範囲をメモしておこう(間違ってるかもしれない…気がついたら修正)
ActivityPubで、Fediverseに投げ込んだNoteの公開範囲を選べる。
・全公開
誰でも見ることができる。
投稿が流れるタイムライン
→連合、ローカル、ホーム
・未収載
誰でも見ることができる。
投稿が流れるタイムライン
→ホーム
・フォロワー限定
フォロワーだけが見ることができる
投稿が流れるタイムライン
→ホーム
・ダイレクト(DM)
指定したお相手だけが見ることができる
タイムラインには流れない
※「ホーム」というのは、自分・フォロワーの「ホームタイムライン」
「未収載」と「フォロワー限定」がわかりにくいのでメモ
・未収載は
フォロワーのホームに投稿が流れる。自分をフォローしていないユーザーが、フォロワーのホームに流れている自分の投稿を見ることができる。
・フォロワー限定は
フォロワーのホームに投稿が流れる。自分をフォローしていないユーザーが、フォロワーのホームを見ても、自分の投稿は見えない。
投稿の公開範囲と宛先の組み合わせ
(Activityの指定)
公開/宛先 | to | cc |
---|---|---|
全公開 | Public | followers |
未収載 | followers | Public |
フォロワー限定 | followers | なし |
ダイレクト | お相手のinbox指定 | なし |
ActivityPubの約束事として公開範囲については上記で全部。
このほかに、もっと細かく公開範囲を指定できるサーバーもあって。
「Fedibrid」https://fedibird.com
「公開/未収載/フォロワー限定/相互フォロー限定(対応サーバーのみ)/サークル/DM/自分限定」
↑こちらは、今日時点で投稿の公開範囲は7種類から選べる。
公開範囲は意識しておきたいところだけど、うっかりやらかしてしまうので、自戒を込めてのエントリ。
ウチのおひとり様ActivityPubサーバーは。
設定ファイルのpublic_levelで公開か未収載を選択する。
・現在のデフォルトは公開
「to」がPublic、「cc」がわたしのfollowers
・Mentionとダイレクトは「to」がお相手のinbox
・Mentionの「cc」はわたしのfollowers
・ダイレクトの「cc」はなし
神田明神の妖怪展。有名なガシャドクロを始め、規模こそ小さかったけど、面白かった。
常設展もかなり面白かった。オススメ。
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」
おひとり様サーバーの改修
ちょろちょろ改修してるんで、忘れないようにメモ。
5分以上前の自分はあかの他人、だからなぁ。
おひとり様ActivityPubサーバーのソースコードを外部モジュール化した。
ため池とcron側と両方共通して使うルーチンばかりで、ふたつのファイルに対して同じ修正するのが面倒くさくなった(今さらかよ)ここんとこ1ファイルでやっつける安いスクリプトしか書いてなかったんで、久しぶり。
これで改修やつけ足しも楽になった。
Like一覧・Mention一覧をつけ足した。
LikeはWeb拍手がわりの使い方のほか、ブックマーク的な使い方もしてるので一覧ページが欲しかった。イベントや博物館美術館情報の展示情報など、わたし的に貴重な情報が多い。
LikeのJSON保存は個数制限は外して期間制限だけにした。上限14日間。
Likeを削除するのはウチだけの話。意図してLikeを外すワケじゃないのでUndoは飛ばさない。
Likeの情報はお相手の所属するサーバーが持ってるんでこちらでLikeのJSONを削除しても影響はない。こんなところも分散型、ActivityPubの特徴、ということだなあ。
わたし宛のMentionはデータベースに登録してもいいと思ったんだけど、なにかの拍子に(Fediverseに)飛ばしてしまうと危険なので、とりあえずJSONそのまま保存。一覧して確認できればいいかな、ぐらいの感じ。
サークル機能に対応(?)
飛んでくる投稿はCreateというActivityの中のobjectという箱に入ったNoteがその実体。なので、投稿を取り出すにはCreateというActivityのJSONをデコードしてobjectを取り出す。
objectには投稿本文や投稿日時など投稿についてのすべての情報がハッシュ配列になって格納されている。
…てのが、ほとんどなんだけど。
objectがハッシュ配列じゃないActivityが飛んできてエラーとなってロックがかかってビックリ。配列じゃないobjectなんてまったくの想定外だった。
JSONを覗いてみたらobjectが配列じゃなくて、1行の文字列。
HTMLエンコードされたURIとなにやらハッシュ文字列とキーワードっぽいbearという文字。投稿の宛先や投稿者、投稿日時がないんだけど大丈夫か。
エンコードされているURIにブラウザでアクセスしてみたら400番代のエラーページが表示される。ハッシュ文字列がOAuthっぽくもあるけどなんのこっちゃわからない。
手がかりがまったくなくて困って、ため池に投稿したらFedibird管理人ののえるさん(@noellabo@fedibird.com)に拾ってもらって、該当ドキュメントを教えてもらった。
https://docs.joinmastodon.org/spec/bearcaps/
詳細はこちらにも↓
https://fedibird.com/@noellabo/104790008237308249
飛んできたobjectのURIに対して、GETリクエストする時のヘッダにAuthorizationをつければ問題なくURIを開くことができて、内容を読めた。宛先がない投稿を勝手に開いていいのかという問題は残るので、要調査。
とはいえ、これは投稿として開いて読む場合。サークル機能?にこちらからリクエストを投げる時は投稿先などちゃんと調べなきゃダメ。今日はとりあえず読むところまで。
まだまだわからないことだらけだ。
初詣の神社はアスクル御神籤でありがたみもないんで、昨日改めて浅草寺。
ここは「凶」が多くてひとに厳しい御神籤なんだけど、今年は一発で「吉」を引けた。去年がいろいろ厄年だったので、これで今年がいい年になればいいなぁ。
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」
おひとり様ActivityPubサーバーにBlock実装
おひとり様APサーバーにBlockを実装した。
ActivityでBlockが飛んできてもめっちゃレアケースなのでスルーしてたんだけど念のため。
というか、アドベントカレンダーに参加して投入したエントリ 「Fediverseの辺境で秘密基地」 や 「SNSのソーシャルディスタンス」 がどこかでブーストされたらしくて、珍しくぷちバズった。
Followリクエストも飛んできてフォロワーさんも増えた。のと、珍しくBlockリクエストも飛んできた。記憶に間違いがなければ、これまで半年ほどやっててBlockリクエストは3度目。
届けたいところじゃないところに届いたんだなぁ…じゃなくて、ちゃんと対応を考えないといけない。ツイッターのブロックと同じ扱いで、ブロックされた該当アカウントの投稿がブーストやなんかで飛んできてもタイムラインに流さない・間違ってフォローリクエストを送ったらまずい。
てことで、ブロックされてるリストを作成して、該当アカウントを登録することにした。
おひとり様サーバーでは投稿はJSONファイルのまま、Signature検証がOKだったら、所定のフォルダに移動してるんで、その前にブロックリストを確認してそこで弾くようにした。Followリクエストも同様。
ActivityPubというプロトコル、とかいうとなんか難しそうだけど、約束事の塊とその約束事を守ることで成立している。極端な話というか乱暴な話、約束事を守る/守らないはサーバー管理者次第。
だからこそ仕様上のMUSTやSHOULDはちゃんと対応する必要がある。
久しぶりに上手く巻けた、かな。
弁当用で、出汁少なめなので失敗は少ない。ウチで食べる用だと出汁を150ccぐらい入れるんでゆるゆるで難易度があがるんだよなあ。
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」
ActivityPubサーバーのGETリクエストに署名
おひとり様サーバーのGETリクエストを署名付きで投げるようにした。
今のところ、特に問題が出てるようには見えない。
前々から気になってたところ。
actor情報や投稿を取得するために、アクセスすると401や403で弾かれることがあって、それってリモートサーバーからのアクセスを拒否してるのかなあ、ぐらいに思いつつもなんかひっかかってた。
GETリクエストにひょっとするとSignatureが必要なわけ?と。
でも、POSTと違ってGETだよ?Body(content、本文)もないのに、いったい何に対してサインする?んなもんないよなあ、とも思ってた。
2023Fediverseアドベントカレンダー第3会場 19日目の黒ヰ樹さんの記事
「DenoとHonoでThreadsのContent-Type: application/activity+jsonをGETする」
当初ActivityPubのHTTP Message Signaturesはhostとdateを認証するPOSTのみで使われてきました。
ThreadsのContent-Type: application/activity+jsonをGETするにはこの署名が必須なので、Threadsをフォローできないサーバーがあったりなかったりしたんですね。
↑こちらに詳細があった(感謝)
GETでのHTTP Signatureは
(request-target)
host
date
の3つを署名対象にすればOKということらしい。
(ひょっとしてわたしが知らなかっただけで、100年前からの常識だったのかな?)
この署名付きでGETを投げて403で弾かれることがあるけど、401は今のところない。これで大丈夫っぽい。
HTTPのレスポンスは。
401 認証できない
403 認証できるけど権限がない
404 そんなファイル、ディレクトリはそもそもない
403を返すと「そんなファイル」はある、ということを自白してるのでセキュリティ的にあえて404を返してることがある。なので、403と404は同じで原因の特定ができないとしても、401が返ってくるのはこちらに何らかの問題がある、ということ。
表示が無駄に重くなるだけなので、うちのおひとり様サーバーでは、400番台が返ってくるサーバーはリストに登録してアクセスしないようにしてた。
とりあえず。この対応で401は回避、できるかな。
今日は3回目のゲ謎。哭倉村。
ほんと、今年No.1だなー。
[12/21 14:07:43]追記。
401でエラーを出してた某サーバーのactor情報がこの変更で取得できたので、やっぱりGETにSignatureがなかったのが問題だったのか…。
[2024/01/09 11:46:23]追記。
HTTP SignatureをつけてGETしても403で弾かれるサーバーがあった。
いろいろ試してみたところ、お相手のHTTP HeaderのkeyIdのURLについてる「#main-key」が原因っぽかった。
お相手からのポストの署名を検証するためにPublickeyを取得する必要があって、Publickeyを取得するにはこのkeyIdのURLにリクエストを投げてPersonのJSONをもらわなきゃいけない。
Mastodon系などはkeyIdのURLにそのままGETリクエストしていて問題はなかった。んだけど、なんか「#main-key」付きって微妙なURLだなあ、とか思ってたこともあって、試しにURLの「#main-key」を削除してリクエストしたら問題なく200が返ってきて、無事JSONを取得できた。
だけど、今度はそれはそれで、keyIdのURLに「#main-key」なんてつけないで欲しいよなあ。「keyId」の意味がないのでは?
» ローカル環境で電子書籍を作る、Macアプリ・Windows版ツール 「かんたんEPUB3作成easy_epub」