WebSubの仕組みで気象庁防災情報をリアルタイムに受け取ろう

# 🔷はじめに
地震や台風のような災害情報をリアルタイムに取得する方法はないかと、「気象庁　情報取得」で検索してみたところ、気象庁が公開している「気象庁防災情報」を見つけました。

今回は、この「気象庁防災情報XMLデータ」を受け取るAPIを作って、リアルタイムに更新情報を受け取ってみます。

<font color="#ff0000">
**※注意：「気象庁防災情報XMLデータ」は、2020年8月末に、サービス終了予定です。**
</font>
## 🔶気象庁防災情報XML

気象庁が2013年からをXMLで公開している各種災害情報。
提供も、更新情報を自分で取りに行く**PULL型**だけではなく、更新情報を知らせてくれる**PUSH型**でも提供しています。
📖参考：[気象庁防災情報XMLフォーマット　情報提供ページ](http://xml.kishou.go.jp/index.html)

今回はこのPUSH型を使って、情報を受け取ります。 仕組みとして**WebSub**という、オープンな標準プロトコルを利用して、更新情報を知らせてくれます。
![websab (1).png](https://qiita-image-store.s3.amazonaws.com/0/38099/cc57c1fd-5584-90c0-e2e3-5805f857e975.png)



## 🔶WebSub

旧・PubSubHubbub。
データ変更を知らせる事を目的とした、出版者-購読者型（PubSub）の標準プロトコル。
主に、ニュースやブログのフィードの更新を知らせる時に使われている。
📖参考：https://ja.wikipedia.org/wiki/WebSub

以下の3者で構成される。

- 出版者（Publisher）
    - 情報を発信するプログラム。
     - 発信の際に、Hubに知らせる。
    - 今回は「気象庁防災情報」を提供するサービスが担当する。
- 購読者（Subscriber）
    - 情報を購読するプログラム。
    - 今回は、自分で作る。
- Hub
    - 出版者が情報を更新した事を、購読者に知らせる。
    - 今回は、Googleが用意しているHubサービスが担当する。
    
![websab2.png](https://qiita-image-store.s3.amazonaws.com/0/38099/a5e34c67-f69d-6a3b-c7a2-339b55fc94b2.png)




出版者（Publisher）は「情報の更新」等のタイミングで、Hubに対してリクエストを送る。
リクエストを受けたHubは、事前に申請があった購読者（Subscriber）に対して、「更新情報」等を送る。

# 🔷作業手順

作業は、大きくは以下の3つの作業を行う。

1. 購読者（Subscriber）の準備
1. 気象庁に購読申請の登録
1. 気象庁からの更新通知の購読確認

# 🔷購読者（Subscriber）の準備

## 🔶購読者（Subscriber）とは

WebSubの仕組みで言う購読者（Subscriber）とは具体的に何かというと、**Hubからのリクエストを受け取れる、REST API（以下、購読者API）**です。

## 🔶Hubから購読者（Subscriber）へのリクエスト

Hubは以下の2種類のリクエストを送ってくるので、**この2種類のリクエストを受けられれば、購読者（Subscriber）としての条件を満たせます。**

### ✅購読確認リクエスト
WebSubの仕組みでは、まずHubに購読申請リクエストを送ります。この購読申請リクエストの中に、購読者APIのURLもセットします。
すると、Hubから指定した購読者APIに対して、GETメソッドのリクエストが送られてきます。
リクエストのクエリストリングには、以下のパラメータがセットされています。

- **hub.verify_token**
   - Hubに購読申請する時に指定した、文字列がセットされて送られて来ます。
   - 購読者APIにはHub以外からもリクエストが送れてしまうので、この値を確認する事で、成りすましを防ぎます。
- **hub.challenge**
   - Hubから渡される値で、**一番重要な値です。**
   - Hubが生成するランダムな文字列。
   - この値をそのままレスポンスボディにセットして、Hubにレスポンスとして返す事で、購読開始の意思表示とします。
- **hub.topic**
   - Hubに購読申請する時に指定した、購読したいフィードのURL。
- **hub.mode**
   - Hubからの購読開始の確認時は`"subscribe"`という文字列がセットされて送られて来ます。
   - ちなみに、購読終了の確認時は、`"unsubscribe"`という文字列がセットされます。
- **hub.lease_seconds**
   - 購読意思確認リクエストは定期的に送られてくるもので、次回のリクエストまでの時間。

このHubからのリクエストに対して`hub.challenge`の値をレスポンスボディにセットして返すと、購読開始となります。

![購読確認 (1).png](https://qiita-image-store.s3.amazonaws.com/0/38099/ee7c716c-e67d-2001-03e3-5e9ace7a5815.png)


### ✅更新通知リクエスト
WebSubの仕組みでは、出版者（Publisher）からHubに更新通知リクエストがあれば、Hubは全ての購読者（Subscriber）に、更新通知をPOSTメソッドのリクエストで送ります。

![更新通知 (1).png](https://qiita-image-store.s3.amazonaws.com/0/38099/3638e0eb-caac-21b5-10ab-dbee8b62eb30.png)


## 🔶実装

Subscriberの準備方法については、気象庁で用意しているドキュメントに詳しく書かれています。
📖参考：[情報提供に係る仕様とSubscriberの構築について](http://xml.kishou.go.jp/open_trial/detailinformation.pdf)

APIサーバーに要件は無いけど、24時間365日の稼働を求められているので、今回はAWSの**API Gateway+Lambda**で構築します。

今回作るプログラムの全体は以下の通りです。

![今回の構成.png](https://qiita-image-store.s3.amazonaws.com/0/38099/93c06108-b332-2ae6-b413-1d90179670eb.png)



### ✅API GatewayでREST APIの作成
API Gatewayで、以下のようなエンドポイントを作ります。
パスは何でもいいので、わかりやすい名前で。

■　購読確認リクエスト受け取り用API

```
[GET] /websub/subscribe
　　以下のクエリストリングを受け取ってLambdaに渡す。
　　?hub.verify_token=○○&hub.challenge=○○&hub.topi=○○&hub.mode=○○&hub.lease_seconds=○○
```

■　更新通知リクエスト受け取り用API

```
[POST] /websub/subscribe
　　リクエストボディの更新情報を、そのままLambdaに渡す。
```

### ✅Lambdaで内部処理の作成

今回は、node.jsで内部処理を書きます。

```javascript

const verifyToken = 'himitsunoango';

/* API Gatewayに返す値の雛形 */
const callbackData = {
        "statusCode": 200,
        "headers": {
          "Content-Type" : "application/json; charset=utf-8",
          "Access-Control-Allow-Origin" : "*"
        },
        "body": "",
        "isBase64Encoded": false
      };
      
/**
 * メイン処理
 * @param event : API Gatewayから渡される値
 * @param context : Lambdaの各種情報
 * @param callback : API Gatewayに値を返す時に使う
 * 
 */
exports.handler = (event, context, callback) => {
  
  console.log(JSON.stringify(event));
  
  let reqMethod = event.httpMethod;
  let reqResource = event.resource
  
  if (reqMethod === "GET" && reqResource === "/websub/subscribe") {
    console.log("■ ■ ■ 購読確認処理の実行 ■ ■ ■");
    subscribe(event, callback);
  } 
  else if(reqMethod === "POST" && reqResource === "/websub/subscribe") {
    console.log("■ ■ ■ 受け取り情報の確認処理の実行 ■ ■ ■");
    console.log(event.body);

    // 本当は受け取った更新情報を使った処理をここに書くが、今回は受け取って終わり。

    callbackData.body = "success"; // 何かしらレスポンスは必ず返すこと。
    callback(null, callbackData);
  }
  
  context.done();
    
};

/* 購読確認処理 */
function subscribe(event, callback) {

  /* 指定したトークンである事を確認（なりすましではない） */
  if (event.queryStringParameters['hub.verify_token'] !== verifyToken) {
      callback(null, 'query string error (verify_token : ' + event.querystring['hub.verify_token'] + ')');
      context.done();
  }
  /* API Gatewayへの戻り値（注意：bodyは文字列型で返す） */
  callbackData.body = event.queryStringParameters['hub.challenge'];
  callback(null, callbackData);
  
  return;
}
```
処理自体は大した事は書いていません。

`[GET] /websub/subscribe`にリクエストが来たら、購読の意思表示として、レスポンスボディにクエリストリングの`hub.challenge`の値をそのままセットして返しています。

`[POST] /websub/subscribe`にリクエストが来たら、リクエストのボディの内容（コンテンツの更新情報）を、とりあえずログに出力しています。

## 🔶動作確認

気象庁のドキュメントには必ず動作確認を行うように書かれているので、確認を行います。
手順については、気象庁のドキュメントに詳しく書かれていますが、参考までに流れを紹介します。
📖参考：[XML電文公開（PUSH型）に係る仕様とSubscriberの構築について](http://xml.kishou.go.jp/open_trial/detailinformation.pdf)

[Google PubSubHubbub Hub](http://pubsubhubbub.appspot.com/)で提供されている、debug機能で動作確認を行います。
![debug0.png](https://qiita-image-store.s3.amazonaws.com/0/38099/e3a14c01-58ae-72de-7eba-245d1f6cd459.png)


### ✅購読者（Subscriber）をHubに登録

購読者（Subscriber）をdebugする機能は以下のような画面です。

![debug01.png](https://qiita-image-store.s3.amazonaws.com/0/38099/6bc734ae-f1b9-3dc3-4abe-b9a92ee69b96.png)


必要なのは赤枠の部分です。以下のように値をセットして、[Do it!]ボタンを押下します。

- Callback URL
    - 今回用意した、「購読確認リクエスト受け取り用API」のURLをセットします。
- Topic URL
    - テストで購読するfeedのURL。何でもいいのですが、今回はitmedeiaニュースのフィードを使います。
    - `https://rss.itmedia.co.jp/rss/2.0/news_special.xml`
- Mode
    - 購読開始が上手く行くか確認したいので、`Subscribe`をセットします。
- Verify token
    - 成りすましを防ぐための暗号。今回はLambdaのソースに`himitsunoango`と書いたので、その通りセットします。

[Do it!]ボタンを押下しても、画面上は特に何もありませんが、Hubは「購読確認リクエスト受け取り用API」に、購読開始の確認のリクエストを送っています。

Cloud WatchでAPI Gatewayのログを見ると、リクエストが送られている事を確認できます。

![log1.png](https://qiita-image-store.s3.amazonaws.com/0/38099/ab23972c-5846-3e7e-b070-a2316a3e5ac0.png)

### ✅購読者（Subscriber）に更新通知を送信

Hubに、用意した購読者（Subscriber）の購読申請が上手く行ったら、出版者（Punblisher）として、Hubに更新通知リクエストを送ります。

以下が、Publishのテスト画面です。
![debug02.png](https://qiita-image-store.s3.amazonaws.com/0/38099/605ba2e4-9bf5-982f-e327-3f96d220500f.png)

赤枠の「Topic URL」に値をセットして、[Do it!]ボタンを押下します。

- Topic URL
    - 購読申請時にセットしたfeedのURLをセットする。
    - `https://rss.itmedia.co.jp/rss/2.0/news_special.xml`

[Do it!]ボタンを押下すると、出版者（Punblisher）がHubに、フィードの更新を通知するリクエストを送った時と、同じ振る舞いをします。
それを受けてHubは、購読者（Subscriber）に更新通知を送ります。

画面上では特に何も無いですが、　Cloud WatchでAPI Gatewayのログを見ると、「更新通知リクエスト受け取り用API」に、Hubからリクエストが送られている事を確認できます。

### ✅購読者（Subscriber）のエラーチェック

更新通知リクエストを受け取れたら、購読者（Subscriber）にエラーが無いかチェックします。

![debug03.png](https://qiita-image-store.s3.amazonaws.com/0/38099/4268ee9d-b414-44c9-774c-2b873c39c59e.png)

赤枠の部分を以下のように値をセットして、[Get Info]ボタンを押下します。

- Callback URL
    - 今回用意した、「購読確認リクエスト受け取り用API」のURLをセットします。
- Topic URL
    - 購読申請時にセットしたfeedのURLをセットする。
    - `https://rss.itmedia.co.jp/rss/2.0/news_special.xml`

診断結果が表示されるので、特に赤枠の項目でエラーが発生していない事を確認する。
![check.png](https://qiita-image-store.s3.amazonaws.com/0/38099/51c615bf-31bb-eff0-a636-89a1ee9725c1.png)

### ✅テスト購読終了の申請を行う

テストはこれでOKです。
最後に、テストで購読申請したので、購読終了の申請を行います。

![debug04.png](https://qiita-image-store.s3.amazonaws.com/0/38099/b30cb45b-4210-70f3-90a9-7648cd0595e2.png)

購読終了の申請は、Modeの値を`"Unsubscribe"`に変更して、[Do it!]ボタンを押下します。

# 🔷気象庁に購読申請の登録

ユーザー登録の申請は、以下でダウンロードできるtxtファイルに必要事項を記入して、メールに添付して送るだけです。
📖参考：[ユーザー登録について](http://xml.kishou.go.jp/open_trial/registration.html)

**メールを送っても、特に返信等は無い**ので、ただ待つしかありません。

登録まで2週間程度かかるような事が書かれていますが、今回は申請した翌営業日には登録が完了していました。

# 🔷購読開始の確認

気象庁から購読登録完了メールが来たら、「更新通知リクエスト受け取り用API」にリクエストが来ていないか、Cloud Watchで確認します。
![log2.png](https://qiita-image-store.s3.amazonaws.com/0/38099/85970b2a-1ba9-6156-9810-c4f97274a447.png)
ログを見ると、無事に情報が受け取れているようです。
今回は、気象庁からの更新通知を受け取って終わりですが、このデータを利用して、Slackに投げたり、何らかのアプリにPUSH通知として飛ばしたり、色々と活用できそうです。

# 🔷注意
## 🔶24/365運用必須
気象庁のドキュメントにも書いてあるように、購読者（Subscriber）は絶対に止めないように、24/365運用を心掛けましょう。

購読開始後も、Hubは「購読確認リクエスト受け取り用API」に時々リクエストを投げて、生存確認をします。
この時、適切に**レスポンスを返さなければ、購読登録を取り消されていまいます。**

また、不確定な話ですが、「更新通知リクエスト受け取り用API」も、レスポンスを返さないと、Hubから死亡判定を受けて、購読登録を取り消される可能性があります。
（この辺は、Hubの仕様なので、Googleに問合せないと分かりませんが。。）

今回、「更新通知リクエスト受け取り用API」を**メンテナンスで数時間止めていたら、更新通知が来なくなりました。**
そのため、「更新通知リクエスト受け取り用API」に、今後何らかの処理を書き足すにしても、Hubには確実にレスポンスだけは返すようにしましょう。

## 🔶購読を止められたら

新規登録と同じ要領で、気象庁に購読申請の登録をしましょう。

ちなみに、申請してから購読再開まで5営業日かかりました。

# 🔷参考

- [Slackに気象庁の情報（災害情報）を投下するHubotを作った（JavaScript）](https://qiita.com/Kakudo/items/04de60570b269145aef5)
- [気象庁防災情報XMLフォーマット　情報提供ページ](http://xml.kishou.go.jp/index.html)
- [WebSub W3C Recommendation](https://www.w3.org/TR/websub/)