本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは白方健太郎さんで、テーマは「実践Fediverse」です。
本稿のサンプルコードは、WEB+DB PRESS Vol.114のサポートサイト から入手できます。
Fediverse──非中央集権型ソーシャルネットワーク
2017年春に日本でブームとなったMastodon が構成するような非中央集権型ソーシャルネットワークは、Fediverseと呼ばれます。Fediverseは、中央に全体を制御するサーバがおらず、各サーバが連合と呼ばれるゆるいネットワークを構成することが特徴です。
本稿では、Fediverseに参加するために最小限実装する必要がある機能と、Perlにおける実装方法を説明します。
Fediverseを構成する仕様群
Fediverseは複数の仕様を組み合わせることで実現されています。本節では、それぞれの概要を紹介します。
ActivityPub──Fediverseを実現するための中心プロトコル
ActivityPub は、2018年1月にW3C(World Wide Web Consortium )によって勧告された仕様で、非中央集権型ソーシャルネットワークを実現するプロトコルを定めています。ActivityPubではいわゆるユーザーのことをアクターと呼び、アクター間で次項で説明するActivity Streams 2.0 (以下、AS2)形式のデータをHTTPSプロトコルでやりとりします。
ActivityPubでやりとりされるデータのMIME(Multipurpose Internet Mail Extensions )型にはapplication/ld+json; profile="https://www.w3.org/ns/activitystreams"
を使います。HTTPSリクエストのAccept
ヘッダやレスポンスのContent-Type
ヘッダには、このMIME型を指定します。
COLUMN ActivityPub仕様の読み方
ActivityPubサーバを作成するには当然ActivityPub仕様を読む必要が出てきますが、この仕様の読み方にはコツがあります。
ActivityPub仕様は大きく分けると、第1章と第2章の序論、第3章から第5章のデータモデルに続いて、第6章に「Client to Server Interactions」 、第7章に「Server to Server Interactions」があります。
Fediverseに参加するサーバの作成を目的としている場合、このうち第6章は読む必要がありません。第6章の仕様は、現在Mastodonなどが提供しているクライアント用REST APIを置き換えることを目的としたものです。Fediverseの連合プロトコルとは直接関係なく、この仕様を実装しているサーバもほとんどありません。連合プロトコルに関連するものというイメージを持って読むと混乱するので、第6章は読み飛ばして、連合プロトコルについて書かれている第7章を読み進めてください。
Activity Streams 2.0──ActivityPubのデータモデル
AS2は、2017年5月にW3Cによって勧告された仕様で、ソーシャルネットワークで使われるさまざまな語彙を、JSON-LD というJSONベースの仕様を利用して定めています。
AS2およびそのベースとなったJSON-LDは非常に大きな仕様ですが、Fediverseに参加するために必須の要素はその一部です。以下に、ActivityPubで使用される、AS2で表現されたオブジェクトの例を示します。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/foobar#follow/1",
"type": "Follow",
"actor": "https://example.com/users/foobar",
"object": "https://actub.ub32.org/argrath"
}
@context
属性はJSON-LDに特有の属性で、XMLにおける名前空間とスキーマに相当するものです。AS2の名前空間はhttps://www.w3.org/ns/activitystreams
です。この属性には、配列を指定することで複数の名前空間の情報を1つのオブジェクトで利用することもできます。
id
属性はネットワーク全体でユニークとなるURL形式の文字列、type
属性はオブジェクトの型を示します。そのほかの属性はオブジェクトの型によって異なるので都度説明します。
HTTP Signatures──ActivityPub通信の検証プロトコル
ActivityPubでは、あるアクターからほかのアクターへの通知はHTTPSプロトコルのPOSTメソッドを使います。その際、アクターのなりすましを防ぐために、通知が正当なものであるかを検証する手法が必要です。
ActivityPubではどのような手法を用いるかは定めていませんが、Fediverseで現在主に使われているのは、IETF(Internet Engineering Task Force )ドラフトとなっているHTTP Signatures です。HTTP Signaturesでは、各アクターは鍵ペアを持ちます。オブジェクトの送信時には秘密鍵を使ってリクエストに署名し、それをHTTPヘッダのSignature
フィールドに指定してリクエストを送信します。受信側は送信アクターの公開鍵を取得して署名を検証し、成功したもののみを正当なリクエストとして受け付けます。
COLUMN OStatus──ActivityPub以前のFediverse仕様
Fediverseの概念はActivityPubの勧告以前からあり、当初はOStatusと呼ばれる仕様群が使われていました。OStatusは、データモデルにAtom Activity Streams、購読および配信にPubSubHubbubといった仕様を集めたもので、2017年春に日本でMastodonが流行した時点ではMastodonはOStatusサーバでした。
しかし、OStatusは細かいプライバシー設定ができないなどの弱点があり、Mastodonは2017年11月に当時ドラフト版のActivityPubにも対応しました。その後、ほかのFediverseサーバもActivityPubに対応するようになりました。
それ以降もOStatusとActivityPubの両方に対応していたMastodonですが、2019年10月にOStatus対応を削除しました。したがって、現在Fediverse上で使われているプロトコルはほぼActivityPubだけになっています。みなさんが今後Fediverseに参加するサーバを作成する場合は、ActivityPubにだけ対応すればほぼ問題ありません。
Fediverse参加に必要な機能の実装
前述のとおりFediverseは連合と呼ばれるゆるいネットワークを構成していますが、これを実現するための中心的な機能がリモートフォローです。リモートフォローは、購読者が自身と異なるサーバにいる発信者の更新情報を購読できる機能です。これにより、発信者と購読者が異なるサーバにいてもソーシャルネットワークを構成することが可能になり、中央サーバが不要となります。したがって、Fediverseに参加するためにはリモートフォローに対応する必要があり、そのためには次の機能を提供する必要があります。
ユーザー名からアクター情報URLへの変換
フォローリクエストの処理
更新通知の送付
以降でそれぞれの処理での具体的な方法を説明します。
ユーザー名からアクター情報URLへの変換
Fediverse上でのユーザー名は、argrath@actub.ub32.org
のように「ローカルユーザー名@サーバ名」の形で表現されます。また、ActivityPubでの通信を行うためには、相手先アクター情報のURLが必要です。したがって、購読アクターが発信アクターをリモートフォローするためには、まず発信アクターのユーザー名をURLに変換する必要があります。この変換処理についてActivityPubに規定はありませんが、Fediverseでは慣習的に図1 の手順が使われます。図のWeb Host MetadataとWebFingerによるやりとりについて、順に見ていきます。
図1 ユーザー名からアクター情報URLへの変換手順
Web Host MetadataプロトコルによるWebFingerエントリポイント検索
最初に、後述するWebFingerプロトコルのエントリポイントを取得するために、Web Host Metadata プロトコルを用います。このやりとりでは、https://サーバ名/.well-known/host-meta
にGETアクセスすることで情報を得ます。したがってargrath@actub.ub32.org
の場合は、https://actub.ub32.org/.well-known/host-meta
にアクセスします。ここで返される情報は次の形になります。
<XRD>
<Link rel="lrdd" type="application/xrd+xml"
template="https://actub.ub32.org/.well-known/webfinger
?resource={uri}"/>
</XRD>
ここで得られたhttps://actub.ub32.org/.well-known/webfinger?resource={uri}
が、WebFingerプロトコルのエントリポイントのURLテンプレートです。
WebFingerプロトコルによるアクター情報検索
次に、WebFinger プロトコルを用います。先ほど得られたURLテンプレートの{uri}
の部分をユーザー名に置換したhttps://actub.ub32.org/.well-known/webfinger?resource=argrath@actub.ub32.org
にGETアクセスすると、次のような情報が返されます。
{
"subject": "acct:[email protected] ",
"aliases": [
"https://actub.ub32.org/argrath"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://actub.ub32.org/argrath"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://actub.ub32.org/argrath"
}
]
}
この情報のうち、rel
属性がself
であるブロックのhref
属性の値がアクター情報のURLとなります。したがって、ユーザーargrath@actub.ub32.org
のアクター情報のURLは、https://actub.ub32.org/argrath
となります。
フォローリクエストの処理
購読アクターは、発信アクター情報のURLを使ってフォローリクエストを送信し、発信アクターはリクエストを承認または拒否します。これは図2 の手順になります。順に見ていきましょう。
図2 フォロー処理手順
アクター情報の取得
先ほど入手した発信アクターのURLにHTTPS GETリクエストすることで、アクター情報を取得します。
発信アクターが返却する必要があるアクター情報は、Person
クラスを使った次のようなものになります。
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://actub.ub32.org/argrath",
"type": "Person",
"url": "https://actub.ub32.org/argrath",
"inbox":
"https://actub.ub32.org/argrath/inbox",
"outbox":
"https://actub.ub32.org/argrath/outbox",
"followers":
"https://actub.ub32.org/argrath/followers",
"following":
"https://actub.ub32.org/argrath/following",
"preferredUsername": "argrath",
"publicKey": {
"publicKeyPem":
"-----BEGIN PUBLIC KEY-----\nMIIB(省略)",
"id": "https://actub.ub32.org/argrath",
"owner": "https://actub.ub32.org/argrath"
}
}
本稿で示す処理で用いるのは、通知の受信先であるinbox
属性と、HTTP Signaturesの検証で用いるpublicKey
属性です。
フォローリクエストの送信
次に、購読アクターは発信アクターにフォローリクエストを送ります。これは発信アクターのinbox
エントリに次のようなFollow
オブジェクトを送信することで行われます。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/foobar#follow/1",
"type": "Follow",
"actor": "https://example.com/users/foobar",
"object": "https://actub.ub32.org/argrath"
}
actor
属性はこのオブジェクトの送信元である購読アクターのURL、object
属性は送信先である発信アクターのURLを示します。
フォローリクエストを承認する場合
発信アクターが受け取ったFollow
オブジェクトを承認する場合、ローカルのフォロワーリストに購読アクターを追加したあと、フォローを承認したことを示すために購読アクターのinbox
エントリにAccept
オブジェクトを送信します。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://actub.ub32.org/argrath/accept/1",
"type": "Accept",
"actor": "https://actub.ub32.org/argrath",
"object": {
"id": "https://example.com/users/foobar#follow/1",
"type": "Follow",
"actor": "https://example.com/users/foobar",
"object": "https://actub.ub32.org/argrath"
}
}
actor
属性はこのオブジェクトの送信元である発信アクターのURLを示し、object
属性には受け取ったFollow
オブジェクトをそのまま設定します。
フォローリクエストを拒否する場合
何らかの理由でフォローリクエストを拒否することを明示的に通知したい場合は、Accept
オブジェクトの代わりにReject
オブジェクトを送信します。Reject
オブジェクトの構造は、クラスを示すtype
属性がReject
になる以外はAccept
オブジェクトと同じです。
なお、ActivityPubでは、明示的にReject
オブジェクトを返さずにフォローリクエストを暗黙に拒否してよいことになっています。
アンフォロー処理
フォロー処理を実装すればFediverseに参加できますが、実際にはアンフォロー処理も実装しないと望まない相手に更新通知を送り続けることになります。したがって、アンフォロー処理の実装も必要です。
購読アクターがフォローしているアクターをアンフォローするとき、次のようなUndo
オブジェクトを送信します。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://example.com/users/foobar#undo/1",
"type": "Undo",
"actor": "https://example.com/users/foobar",
"object": {
"id": "https://example.com/users/foobar#follow/1",
"type": "Follow",
"actor": "https://example.com/users/foobar",
"object": "https://actub.ub32.org/argrath"
}
}
actor
属性はこのオブジェクトの送信元である購読アクターのURL、object
属性にはフォロー時に送信したオブジェクトをそのまま設定します。
アンフォロー処理は自動的に成功するため、発信アクターはオブジェクトの返信は行わず、単にローカルのフォロワーリストから当該アクターを削除します。
更新通知
続いて、新しい短文(Twitterではツイート、Mastodonではトゥートと呼ばれるもの)が投稿されたとき、フォロワーに通知を送る更新通知処理について説明します。
Noteオブジェクトの作成
まず、投稿された短文自体を表現するNote
オブジェクトを作成します。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://actub.ub32.org/argrath/123",
"type": "Note",
"url": "https://actub.ub32.org/argrath/123",
"published": "2018-03-03T09:54:50Z",
"content": "はいさーい",
"attributedTo": "https://actub.ub32.org/argrath",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://actub.ub32.org/argrath/followers"
]
}
to
属性に指定されているhttps://www.w3.org/ns/activitystreams#Public
は、一般公開を示す固定文字列です。cc
属性に指定されているhttps://actub.ub32.org/argrath/followers
はアクター情報のfollowers
属性で示されているURLで、フォロワーであるアクター全体を示します。これにより、この短文は「一般公開し、フォロワーアクターに通知を送る」という指定になります。この部分を変更することによって、フォロワーにのみ公開する、あるいは特定のアクターにのみ公開するオブジェクトも作成できます。
Createアクティビティの送信
次に、このNote
オブジェクトを入れ子として含んだCreate
オブジェクトを作成します。
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://actub.ub32.org/argrath/123/activity",
"type": "Create",
"actor": "https://actub.ub32.org/argrath",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://actub.ub32.org/argrath/followers"
],
"object": {
"id": "https://actub.ub32.org/argrath/123",
"type": "Note",
"url": "https://actub.ub32.org/argrath/123",
"published": "2018-03-03T09:54:50Z",
"content": "はいさーい",
"attributedTo": "https://actub.ub32.org/argrath",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://actub.ub32.org/argrath/followers"
]
}
}
to
属性とcc
属性はもとのNote
オブジェクトの内容をコピーし、object
属性にはもとのNote
オブジェクトそのものを指定します。
こうして作成したオブジェクトを、前述のHTTP Signaturesで署名して各フォロワーアクターのinbox
エントリにPOSTします。
<続きの(2)はこちら 。>
特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT