前回の
Perlでの実装
それでは、
ActivityPubはWeb APIベースのプロトコルですので、
AS2オブジェクトモデルの作成
Actubでは、
個々のオブジェクトを表現するクラスを作成する前に、
package WWW::ActivityPub::Base;
use strict;
use warnings;
use Class::Tiny;
sub TO_JSON {
    my %ret;
    my $self = shift;
    for (keys %$self){
    my $key = $_;
    my $jsonkey = $key;
    if($key eq 'context'){
        $jsonkey = '@context';
        if(!defined $$self{$_}){next;}
    }
    $ret{$jsonkey} = $$self{$_};
  }
  return \%ret;
}
1;ここではClass::Tinyモジュールを用いています。これは属性のゲッタとセッタを設定するだけのシンプルなモジュールですが、@context属性があり、@はメソッド名に使えないため、@contextではなくcontext属性として定義し、context属性を@contextに変換して出力するためのTO_関数を定義しています。
この下準備により、Followクラスの定義は次のようにシンプルなものになっています。
package WWW::ActivityPub::Follow;
use strict;
use warnings;
use parent qw(WWW::ActivityPub::Base);
use Class::Tiny qw(
    context id type object
    );
1;MIME型の登録
GETリクエストに対する返却処理は通常のレスポンスなので実装上特筆することはあまりありませんが、application/を使う必要があるため、startupフック内で次のようにしてこのMIME型を登録しておきます。
sub startup {
    my $self = shift;
    ...;
    $self->types->type(as =>
      'application/ld+json; profile=' .
      '"https://www.w3.org/ns/activitystreams"');
}これにより、
# $outは出力するデータ
$self->render(text => $out,
              format => 'as');送信キューの登録
Actubは送信処理を非同期で行う実装となっているので、Followオブジェクトが送信されると、Acceptオブジェクトをジョブキューに追加します。ここでのジョブキューシステムはJonkモジュールを使っています。これはデータベースをバックエンドに使う軽量なジョブキューシステムで、
# $dbhはデータベースハンドル
my $jonk = Jonk->new($dbh);
...;
my $job_id = $jonk->insert('post', $queuestr);送信処理
Actubでは、
データのキューからの取り出し
スクリプトが起動されると、
my $dbhj = DBI->connect(
  "dbi:SQLite:dbname=actub_job.sqlite","","");
my $jonk = Jonk->new($dbhj =>
 {functions => [qw/post/]}) or die;
my $job = $jonk->find_job;
if (defined $job) {
    # 送信処理
    do_post($job->arg);
}HTTP Signaturesによる署名
ActivityPubのオブジェクトを送信する際には、rsa-sha256と呼ばれる方法です。rsa-sha256では次の手順で署名します。
- 署名対象データをSHA256アルゴリズムでハッシュ化する
- ハッシュをRSAアルゴリズムで署名する
- 署名をBase64形式でエンコードする
HTTP Signaturesでは署名対象データも選択できますが、Dateフィールドを対象としています。
通常、postメソッドに必要な引数を与える形で実装します。しかし今回はHTTPリクエストヘッダのDateフィールドの値に署名をする必要があるため、
my $contenttype = 'application/ld+json; ' .
  'profile="https://www.w3.org/ns/activitystreams"';
# リクエストオブジェクトを作成
my $req = POST(
    $url,
    'Content-Type' => $contenttype,
    Content => $content
);
# Dateフィールドに現在時刻を設定
$req->headers->date(time);
# Dateフィールドの文字列表現を取得
my $date = $req->headers->header('date');
# 取得した文字列表現に署名するメソッドを呼び出し
my $sign = Actub::Signature::sign('date: ' . $date);
# 署名した結果を仕様が求める形に整形
my $signature = sprintf
  'keyId="%s",algorithm="rsa-sha256",signature="%s"',
    $from, $sign;
# 署名した結果をSignatureヘッダに設定
$req->headers->push_header(
  Signature => $signature);
# リクエストを実行
my $res = $ua->request($req);署名処理本体はCrypt::OpenSSL::RSAモジュールを使って次のように行います。
sub sign {
    my ($data) = shift;
    # 秘密鍵を取得
    my $pk = get_pk();
    # 署名
    my $key =
        Crypt::OpenSSL::RSA->new_private_key($pk);
    $key->use_sha256_hash();
    my $s = $key->sign($data);
    # Base64エンコードした文字列を返却
    return encode_base64($s, "");
}      通知を受け取ったアクターは、publicKey属性の値を使って、
コンテントネゴシエーションによる情報の提供
前述のとおりActivityPubにおいてGETメソッドで情報を取得する際には、Acceptヘッダにapplication/を指定することになっています。ただ、
sub entry {
    my $self = shift;
    ...;
    # 指定されたIDの短文情報を取得
    my $entry = Actub::Model::Entry::read_row($dbh, $self->param('id'));
    if(is_ap($self->req->headers->accept)){
        ...; # $outにAS2形式のデータを設定
        $self->render(text => $out, format => 'as');
    } else {
        $self->render(template => 'entry', e => $entry);
    }
}
# AS2を要求しているかのチェック
sub is_ap {
    my $arg = shift // '';
    my (@params) = split /,/, $arg;
    for(@params){
        s/^ +//;
        s/ +$//;
        if($_ eq 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ||
           $_ eq 'application/activity+json'){ return 1; }
    }
    return 0;
}is_関数は、application/が指定された場合もAS2形式の情報を返すべき
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
 イミュータブルデータモデルで始める
 実践データモデリング
 業務の複雑さをシンプルに表現!
- 特集2
 いまはじめるFlutter
 iOS/Android両対応アプリを開発してみよう 
- 特集3
 作って学ぶWeb3
 ブロックチェーン、スマートコントラクト、 NFT 



