Skip to content

RSS / Atom フィード配信の設計 ​

第1版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

プリザンターには、サイトのレコードを RSS / Atom で配信する機能はありません(外部のフィードを取り込む方法は RSS リーダー を参照)。このページは、サイトの新着・更新をフィードとして公開する本体改修の設計メモです。

推測できない URL、実行ユーザーの Context、ビューの適用、[列名] のテンプレートは iCal フィードの設計 と同じ考え方で、前提にした現行実装(1.5.8.1 のフォーム機能・バックグラウンドサーバースクリプト・SiteSettings.IncludedColumns)もそちらに書いています。ここでは差分を中心にまとめます。

要件 ​

要件内容
サイト単位・推測できない URLiCal と同じ
形式RSS 2.0 と Atom 1.0 の両方
実行ユーザー・ビュー・複数設定iCal と同じ
タイトル・内容の書式標準(画面表示タイトル・内容)か [列名] テンプレート
件数制限フィードに含める件数の上限を設定できる

RSS 2.0 と Atom 1.0 ​

観点RSS 2.0Atom 1.0(RFC 4287)
構造<rss><channel><item><feed><entry>
日時RFC 822RFC 3339
エントリの識別子<guid>(任意)<id>(必須)
更新日時<pubDate>(任意)<updated>(必須)
Content-Typeapplication/rss+xmlapplication/atom+xml

Atom はフィード全体の <title>・<id>・<updated> が必須で、エントリに <author> が無いときはフィード側に <author> が必要です。

生成ライブラリ ​

.NET の NuGet パッケージ System.ServiceModel.Syndication に、共通のデータ構造 SyndicationFeed / SyndicationItem と、書き出し用の Rss20FeedFormatter / Atom10FeedFormatter があります。1.5.8.1 の本体(net10.0。Implem.Pleasanter.csproj)はこのパッケージを参照していないので、csproj に追加します。フィードの組み立ては共通にし、形式の違いは書き出しの段階だけで分岐します。

図を読み込み中…

URL ​

text
GET /feed/{guid}.rss     # RSS 2.0
GET /feed/{guid}.atom    # Atom 1.0

形式はクエリ(?format=rss)でも切り替えられますが、フィードリーダーは URL をそのまま識別子に使うことが多いため、拡張子で分けます。ルートは iCal と同じく既存の Default ルートより前に登録します。

csharp
endpoints.MapControllerRoute(
    name: "FeedRss",
    pattern: "feed/{guid}.rss",
    defaults: new { Controller = "Feed", Action = "Rss" },
    constraints: new { Guid = "[A-Fa-f0-9]{32}" });
endpoints.MapControllerRoute(
    name: "FeedAtom",
    pattern: "feed/{guid}.atom",
    defaults: new { Controller = "Feed", Action = "Atom" },
    constraints: new { Guid = "[A-Fa-f0-9]{32}" });

設定の保存先 ​

iCal と同じく専用テーブルを推奨します。iCal の列に MaxItems(既定 50 程度、0 は無制限)を足した構成です。

図を読み込み中…

iCal と 1 つのテーブルにまとめ、FeedType(ICal / Rss / Atom)で区別する案もあります。

観点統合テーブル分離テーブル
GUID の検索1 テーブルで全形式を引ける形式ごとに引き先を分ける
列形式固有の列が NULL 許容になる必要な列だけ
新しい形式の追加FeedType を足すだけテーブルを追加

レコードとエントリの対応 ​

レコードRSS 2.0Atom 1.0
画面表示タイトル(ItemTitle)<title><title>
内容(Body)<description><summary>
レコードの URL(/items/{Id}/edit)<link><link href>
pleasanter-{SiteId}-{Id}<guid isPermaLink="false"><id>
更新日時<pubDate><updated>
作成日時―<published>

取得はビューの Where を適用し、iCal と違って更新日時の降順に並べ、MaxItems 件で打ち切ります。

図を読み込み中…

csharp
private SyndicationItem BuildItem(Context context, SiteSettings ss, FeedSetting setting, DataRow row)
{
    var id = row.Long("Id");
    var title = setting.TitleStyle == "Custom"
        ? ReplaceTemplate(context, ss, setting.TitleFormat, row)
        : row.String("ItemTitle");
    var content = setting.ContentStyle == "Custom"
        ? ReplaceTemplate(context, ss, setting.ContentFormat, row)
        : row.String("Body");
    return new SyndicationItem(
        title: title,
        content: content,
        itemAlternateLink: new Uri(Locations.ItemEditAbsoluteUri(context: context, id: id)),
        id: $"pleasanter-{ss.SiteId}-{id}",
        lastUpdatedTime: new DateTimeOffset(row.DateTime("UpdatedTime").ToLocal(context: context)));
}

ReplaceTemplate は ss.IncludedColumns(format) で取り出した列を値で置き換える新設のメソッドです。レコードの URL は、通知の {Url} と同じ Locations.ItemEditAbsoluteUri で作れます(ResultModel.cs)。

キャッシュ制御 ​

フィードリーダーは定期的にポーリングするので、変更が無ければ本文を作らずに済むようにします。

ヘッダ値の例目的
Cache-Controlpublic, max-age=3005 分間はキャッシュさせる
ETagフィード内容のハッシュIf-None-Match が一致すれば 304
Last-Modified最新の更新日時If-Modified-Since 以降に変更が無ければ 304

iCal との違い ​

観点iCalRSS / Atom
形式RFC 5545 のテキストXML
日時の意味予定の開始・終了レコードの更新日時
終日判定・VTIMEZONE必要不要(オフセット付き日時で足りる)
並び・件数カレンダー列で絞る更新日時の降順、MaxItems で打ち切り

注意点 ​

項目内容
URL の秘密性漏れると誰でも読める。再発行を用意する
実行ユーザーの無効化・ビューの削除iCal と同じ扱い
HTML の除去内容や説明項目の HTML・Markdown は除去してから出力する
文字コードXmlWriterSettings.Encoding に UTF-8 を明示する
MaxItems0(無制限)は全件取得になるので負荷に注意
WikiReferenceType が Wikis のサイトは一覧の概念が違うので対象外にする
リーダーの互換性Atom に対応していないリーダーもある

関連ページ ​

変更履歴

第1版外部連携の改修・設計メモ(iCal・RSS/Atom・Webhook 送受信・iPaaS・短縮 URL・POP 受信・マスターデータ同期)を追加