Perl Hackers Hub

第59回Fediverse入門―非中央集権型SNSサーバを作ろう!(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは白方健太郎さんで、テーマは「実践Fediverse」です。

本稿のサンプルコードは、WEB+DB PRESS Vol.114のサポートサイトから入手できます。

Fediverse─⁠─非中央集権型ソーシャルネットワーク

2017年春に日本でブームとなったMastodonが構成するような非中央集権型ソーシャルネットワークは、Fediverseと呼ばれます。Fediverseは、中央に全体を制御するサーバがおらず、各サーバが連合と呼ばれるゆるいネットワークを構成することが特徴です。

本稿では、Fediverseに参加するために最小限実装する必要がある機能と、Perlにおける実装方法を説明します。

Fediverseを構成する仕様群

Fediverseは複数の仕様を組み合わせることで実現されています。本節では、それぞれの概要を紹介します。

ActivityPub─⁠─Fediverseを実現するための中心プロトコル

ActivityPubは、2018年1月にW3CWorld Wide Web Consortiumによって勧告された仕様で、非中央集権型ソーシャルネットワークを実現するプロトコルを定めています。ActivityPubではいわゆるユーザーのことをアクターと呼び、アクター間で次項で説明するActivity Streams 2.0(以下、AS2)形式のデータをHTTPSプロトコルでやりとりします。

ActivityPubでやりとりされるデータのMIMEMultipurpose Internet Mail Extensions型にはapplication/ld+json; profile="https://www.w3.org/ns/activitystreams"を使います。HTTPSリクエストのAcceptヘッダやレスポンスのContent-Typeヘッダには、このMIME型を指定します。

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で現在主に使われているのは、IETFInternet Engineering Task ForceドラフトとなっているHTTP Signaturesです。HTTP Signaturesでは、各アクターは鍵ペアを持ちます。オブジェクトの送信時には秘密鍵を使ってリクエストに署名し、それをHTTPヘッダのSignatureフィールドに指定してリクエストを送信します。受信側は送信アクターの公開鍵を取得して署名を検証し、成功したもののみを正当なリクエストとして受け付けます。

Fediverse参加に必要な機能の実装

前述のとおりFediverseは連合と呼ばれるゆるいネットワークを構成していますが、これを実現するための中心的な機能がリモートフォローです。リモートフォローは、購読者が自身と異なるサーバにいる発信者の更新情報を購読できる機能です。これにより、発信者と購読者が異なるサーバにいてもソーシャルネットワークを構成することが可能になり、中央サーバが不要となります。したがって、Fediverseに参加するためにはリモートフォローに対応する必要があり、そのためには次の機能を提供する必要があります。

  • ユーザー名からアクター情報URLへの変換
  • フォローリクエストの処理
  • 更新通知の送付

以降でそれぞれの処理での具体的な方法を説明します。

ユーザー名からアクター情報URLへの変換

Fediverse上でのユーザー名は、argrath@actub.ub32.orgのように「ローカルユーザー名@サーバ名」の形で表現されます。また、ActivityPubでの通信を行うためには、相手先アクター情報のURLが必要です。したがって、購読アクターが発信アクターをリモートフォローするためには、まず発信アクターのユーザー名をURLに変換する必要があります。この変換処理についてActivityPubに規定はありませんが、Fediverseでは慣習的に図1の手順が使われます。図のWeb Host MetadataとWebFingerによるやりとりについて、順に見ていきます。

図1 ユーザー名からアクター情報URLへの変換手順
図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 フォロー処理手順
図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)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧