mastodonがどうやって分散SNSを実現しているのか、その仕組をざっくりと調べてみた


:warning: **間違ってたらごめんなさい** :warning:

on [shinjukurb#49](https://shinjukurb.connpass.com/event/56695/)


### mastodon

分散SNSです

### 分散SNSの実現方法

> Based on ActivityStreams, Webfinger, PubsubHubbub and Salmon.

とのことなんで、追って流れを見ていきましょう

## 超ざっくり

### 登場人物

- mastodon instance
  - social network server
  - こいつが誰でも立てられて連携できる
  - pubsubで言うところのsubscriberでありpublisherである
- user
  - mastodon利用者

### アクション

- to-to
  - tweet on twitter
- Reply
  - reply on twitter
- Boost
  - retweet on twitter
- Favorite
  - favorite on twitter
- Follow
  - favorite on twitter
- timeline
  - timeline on twitter

### 流れ

instance XのUser Aさんの情報をinstance Yで見る

1. (webfinger) User Aさんのtimelineを見たいので、User Aさんのtimeline取得endpointを探す
1. (PubSubHubbub) User Aさんをfollow
1. (activestream) User Aさんのfeed取得
1. (PubSubHubbub) User Aさんの更新が更に通知される

instance XのUser Aさんにinstance YのUser Bさんがreplyし、それをinstance ZのUser Cさんが閲覧する

1. (activestream) User Aさんのfeed取得
1. (activestream) User Aさん向けのreply
1. (salmon) replyがAさんのmention受付エンドポイントに通知される
1. (salmon) mentionを他のインスタンスに通知依頼
1. (PubSubHubbub) インスタンスに展開
1. User Cさんが会話のやり取りを見る！

# 要素技術の説明

## [Webfinger](https://en.wikipedia.org/wiki/WebFinger)

https://webfinger.net/

> WebFingerは、セキュアなトランスポートを介して標準的なHTTP（Hypertext Transfer Protocol）方式を使用してURIで識別される、インターネット上の人または他のエンティティに関する情報を検出するために使用されます。
> WebFingerリソースは、照会されるエンティティを記述するJavaScript Object Notation（JSON）オブジェクトを返します。 JSONオブジェクトはJSONリソース記述子（JRD）と呼ばれます。

= Userのfeed discoveryです。

というわけで、webfinger叩いてみる

### 実際の動き

host-metaからwebfinger情報を取得

https://mstdn.jp/.well-known/host-meta

```xml
<?xml version="1.0"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  <Link rel="lrdd" type="application/xrd+xml" template="https://mstdn.jp/.well-known/webfinger?resource={uri}"/>
</XRD>
```

webfingerにてuserの情報をdiscoveryする

https://mstdn.jp/.well-known/webfinger?resource=acct%3Athreetreeslight%40mstdn.jp

```json
{
  "subject": "acct:threetreeslight@mstdn.jp",
  "aliases": [
    "https://mstdn.jp/@threetreeslight",
    "https://mstdn.jp/users/threetreeslight"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://mstdn.jp/@threetreeslight"
    },
    {
      "rel": "http://schemas.google.com/g/2010#updates-from",
      "type": "application/atom+xml",
      "href": "https://mstdn.jp/users/threetreeslight.atom"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://mstdn.jp/@threetreeslight"
    },
    {
      "rel": "salmon",
      "href": "https://mstdn.jp/api/salmon/140702"
    },
    {
      "rel": "magic-public-key",
      "href": "data:application/magic-public-key,RSA.x6myjejqg0H7pCoE_gX6_AC_sbkSR5C8VKJVHZe1LJqrKjqjb4Wg8AIbztz5qzyAOVF1SW8Uswn9VZRjUd5i1nAiLCioC0nV_-c4EEa5hD4-VVG4zJfKHUPmdZg_rh13r7hpWmJi3VBkPAE3Jo7jSlPoESWROrM_7-397cwqNODRY50K5MDoiZGUihAHamuZNOwjaA7X3yoo7CzQ_q2U6mGtTDFtaG3NtVD48siokEmdhgoDoKDY3onA7qFQVfotZ-CiQNw4m8uXByW6FvWJ1frG9FO5z5jmIRZKBaY0c3OmdB2XB0aRqYLFdgm1CDXawaf4chb6jGUEDIP0NaU50w==.AQAB"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://mstdn.jp/authorize_follow?acct={uri}"
    }
  ]
}
```

これでどこ見に行けばその人のfeed取得できるか分かる！やったね！

## [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format))

>  ユーザーアクティビティの場合、Atomは、アクティビティ固有のメタデータの大部分を機械で解析可能な形式で表現する能力が欠けています。
>  アクティビティストリームは、この追加のアクティビティメタデータを既存のAtomエントリおよびフィード内で表現できるように設計されたXML形式です。
>  - http://activitystrea.ms/specs/atom/1.0/

というわけでAtom拡張しました。
こんな情報でわかります。

> Activityはactor、動varb、object、およびtargetで構成されます。
>
> 「ジェラルディンは彼女のアルバムに写真を掲載しました」または「ジョンはビデオを共有しました」という物語の上で、またはオブジェクトと一緒に行動する人物の話をします。

拡張されたschema情報をみてみると
https://github.com/activitystreams/activity-schema/blob/master/activity-schema.md

### 実際の動き

別インスタンスの@trebyとコミュニケーションとったときの@threetreeslight streamをみてみる

https://mstdn.jp/@threetreeslight.atom

```diff
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:media="http://purl.org/syndication/atommedia" xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:mastodon="http://mastodon.social/schema/1.0">
  <id>https://mstdn.jp/users/threetreeslight.atom</id>
  <title>threetreeslight</title>
  <subtitle></subtitle>
  <updated>2017-04-21T13:49:18Z</updated>
  <logo>/avatars/original/missing.png</logo>

  <author>
    <id>https://mstdn.jp/users/threetreeslight</id>
    <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
    <uri>https://mstdn.jp/users/threetreeslight</uri>
    <name>threetreeslight</name>
    <email>threetreeslight@mstdn.jp</email>
    <link rel="alternate" type="text/html" href="https://mstdn.jp/@threetreeslight"/>
    <link rel="avatar" type="" media:width="120" media:height="120" href="/avatars/original/missing.png"/>
    <link rel="header" type="" media:width="700" media:height="335" href="/headers/original/missing.png"/>
    <poco:preferredUsername>threetreeslight</poco:preferredUsername>
    <mastodon:scope>public</mastodon:scope>
  </author>

  <link rel="alternate" type="text/html" href="https://mstdn.jp/@threetreeslight"/>
  <link rel="self" type="application/atom+xml" href="https://mstdn.jp/users/threetreeslight.atom"/>
  <link rel="hub" href="https://mstdn.jp/api/push"/>
  <link rel="salmon" href="https://mstdn.jp/api/salmon/140702"/>

  <entry>
    <id>tag:mstdn.jp,2017-05-24:objectId=13974575:objectType=Status</id>
    <published>2017-05-24T06:31:33Z</published>
    <updated>2017-05-24T06:31:33Z</updated>
    <title>New status by threetreeslight</title>
+    <activity:object-type>http://activitystrea.ms/schema/1.0/comment</activity:object-type>
+    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
    <content type="html" xml:lang="ru">&lt;p&gt;&lt;span class="h-card"&gt;&lt;a href="https://imastodon.net/@treby" class="u-url mention"&gt;@&lt;span&gt;treby&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; 食べたい&lt;/p&gt;</content>
+    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="https://imastodon.net/users/treby"/>
+    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
    <mastodon:scope>public</mastodon:scope>
    <link rel="alternate" type="text/html" href="https://mstdn.jp/users/threetreeslight/updates/6205287"/>
    <link rel="self" type="application/atom+xml" href="https://mstdn.jp/users/threetreeslight/updates/6205287.atom"/>
    <thr:in-reply-to ref="tag:imastodon.net,2017-05-24:objectId=967970:objectType=Status" href="https://imastodon.net/users/treby/updates/422592"/>
  </entry>

  <entry>
    <id>tag:mstdn.jp,2017-04-21:objectId=2405319:objectType=Status</id>
    <published>2017-04-21T13:59:07Z</published>
    <updated>2017-04-21T13:59:07Z</updated>
    <title>New status by threetreeslight</title>
+    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
+    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
    <content type="html" xml:lang="en">&lt;p&gt;しかし横１０２４だと使うの難しい。&lt;/p&gt;</content>
    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
    <mastodon:scope>public</mastodon:scope>
    <link rel="alternate" type="text/html" href="https://mstdn.jp/users/threetreeslight/updates/1202938"/>
    <link rel="self" type="application/atom+xml" href="https://mstdn.jp/users/threetreeslight/updates/1202938.atom"/>
  </entry>

</feed>
```

各アクション（Activity）が定義できたね！やったね！

replyのやり取りをどうやってとりまとめているかは追いきれませんでした。すいません :swear:

### ref

- http://activitystrea.ms/

## [PubSubHubbub](https://en.wikipedia.org/wiki/PubSubHubbub)

よく噛むので読み方: パブサブハバブ

#### PubSub with poling is Hard

Publisherが3つあってSubscriberが3つあるととすると常に9箇所ポーリング！

```
# Publisher Pとしては無意味なpolingが発生されて辛い
Publisher P <-poling- Subscriber X
Publisher P <-poling- Subscriber Y
Publisher P <-poling- Subscriber Z

# Subscriber Xとしては更新されていないかもしれないpolingがつらい
Publisher O <-poling- Subscriber X
Publisher P <-poling- Subscriber X
Publisher Q <-poling- Subscriber X
```

負荷を捌くためには、PublisherとSubscriberをスケールしなければいけない
じゃあどうするか？

#### PubSub with poling is Hard

じゃあどうするか？
mailmagzineみたいに更新受取登録しておいて、連絡を受けるようにする。

Subscriberが更新受付たい旨を登録する。
Publisherは更新があったら登録されているsubscriberに更新を通知する。

```
Hub P <-registered- Subscriber X
Hub P <-registered- Subscriber Y
Hub P <-registered- Subscriber Z

Publisher P -update notify-> Hub P
Publisher P <-get contents-  Hub P

Hub P -post contents-> Subscriber X
Hub P -post contents-> Subscriber Y
Hub P -post contents-> Subscriber Z
```

## [salmon](http://www.salmon-protocol.org/)

> RSS、Atom、PubSubHubbubなどのテクノロジでは、読者にリアルタイムの更新フローが提供されますが、これは会話の断片化につながります。
> コメント、評価、およびアノテーションは、アグリゲータでますます発生し、元のソースからは見えません。
>
> サーモンプロトコルは、アグリゲーターとソースが会話を統一できるオープンでシンプルな標準ベースのソリューションです。 当初、パブリックコンテンツを中心とした一般の会話に焦点を当てています。
> - http://www.salmon-protocol.org/salmon-protocol-summary

replyの仕組みとそれが各インスタンスに展開される流れ

1. entryにreply(commnet)する際にはsalmon endpointにも通知する
1. salmon endpointが受けた通知をサーバー内に保存し、entryと紐付ける
1. publisherはreply(commnet)をpubsubhubbubを通してsubscriberにrepublishする

ref

- http://www.salmon-protocol.org/
- http://www.salmon-protocol.org/salmon-protocol-summary
- https://github.com/salmon-protocol/salmon-protocol

# ref

だいたいココ読めば分かるのでおすすめです。

- [OStatusの仕様をかいつまんで適当に和訳するよ](http://hitoasa.hateblo.jp/entry/20101013/1286950786)
- [Mastodonとその脱中央集権の仕組み by anatoo](https://speakerdeck.com/anatoo/mastodontosofalsetuo-zhong-yang-ji-quan-falseshi-zu-mi)
- [GNU social (OStatus) 自体の仕様に関する情報源まとめ](http://muziyoshiz.hatenablog.com/entry/2017/04/30/143632)
- [okapies/mastodon-ostatus.md](https://gist.github.com/okapies/60d62d0df0163bbfb4ab09c1766558e8)