みんな大好き気象庁が発表する防災情報*1をXML化した上にプッシュ配信してくれるらしいので、受信することにしました。
注:試行配信の段階での情報なので、今後気象庁の心変わりで配信止めたり、本配信の際に使用が変わったりするかもしれませんよ、と。もちろん、内容の正確さとか一切未保証です。
あらすじ
去年の12/12に気象庁から気象庁防災情報XMLフォーマット形式電文の公開の試行開始についてという情報が出ました(詳細のPDF)。出た情報は二つあって
- 防災情報をXMLで公開するよ
- 防災情報の公開通知をPubSubHubbubでプッシュ配信するよ
ということで、今まで気象庁のWebサイトを定期的にクロールしてHTMLをスクレイピングして天気予報拾ってた人達にとって朗報*2ですね。
で、取り敢えず受信してみようと思い立った。これが12/25。それから後輩の家でやってたクリパ乱入したりして、実際にサブスクライバ書いて登録申請投げたのは暮れも押し詰まった27日。登録が通って配信が始まったのは1/4でした。仕事始め最初のお仕事だったんでしょか。
最大の問題は「PubSubHubbubってなんですねんな」ということ。PubSubHubbubではクソ長いので以下PuSHで*3。気象庁Webサイト内電文公開の仕組みを見たりぐぐって出てきた記事眺めたり仕様書見たりして理解したことを綴ってみます。
ちなみに、XML電文公開(試行)に係る仕様とSubscriberの構築についてという文書を気象庁が出してるんで、コレ読んで理解できたら以下の記事いらんという説はあります。
PuSHの概要
- HTTPだけで完結する
- URI一個用意する必要があって、そのURIを叩くことで情報(Atom)がプッシュされる
- 叩かれるURIをサブスクライバと言う。叩いてくるのはハブ。ハブを叩くのがパブリッシャ。パブリッシャがハブを叩くと、ハブが登録されたサブスクライバへAtomをバシバシぶっこむ。
- 今回は、気象庁がパブリッシャで、ハブはGoogleのAlert Hub、サブスクライバがわたしたち。
- 叩かれるURIをサブスクライバと言う。叩いてくるのはハブ。ハブを叩くのがパブリッシャ。パブリッシャがハブを叩くと、ハブが登録されたサブスクライバへAtomをバシバシぶっこむ。
要はWebHookで情報が降ってくるわけです。その登録を自動化したよー、というのがPuSHなんでしょう。ほんまか。
PuSH登録時
サブスクライバがハブへ「俺に情報よこせ」と要求を投げます。すると、ハブが「ほんとにお前情報欲しいんだな」と確認を投げてきます*4。今回、ハブへの登録要求は気象庁がやるんでサブスクライバがまずやるべきなのは登録要求に対する返答。ちなみに、この登録確認GETリクエストは一定秒数(後述)ごとに繰り返されます。
リクエストパラメタ名 | 内容 |
---|---|
hub.mode | 登録するときはsubscribe。解除する際はunsubscribeが入る |
hub.topic | PuSH時に放り込まれるAtomのあるURI |
hub.challenge | 登録返答時に返すレスポンス |
hub.verify_token | 登録要求時に指定できるキーワード |
hub.lease_seconds | 再登録されるまでの秒数 |
ちなみに、気象庁の資料を読むと、hub.topicにはhttp://xml.kishou.go.jp/から始まるURIが入ると読めます。hub.verify_tokenは現状のメールによる登録では登録メールに書く必要があるんですが手打ちでHubに入れてるらしいので、あんまり激しい文字列を入れるとハマりそう*5。
hub.topicがhttp://xml.kishou.go.jp/以下のリソースURI指してて、hub.verify_tokenが自分で決めた文字列と一緒だったら、hub.challengeに入ってる文字列をレスポンスボディとして返せば良い。レスポンスコードは200番台ならなんでもいいように見えるけど、まぁ200でいいんじゃないですかね。で、hub.verify_tokenがおかしいとかhub.topicが変、みたいに登録を蹴る場合はレスポンスコード404を返します(これは指定)。
例示しましょう。
サブスクライバURI | http://www.example.jp/jma-xml/subscriber |
hub.verify_token | verify-token |
こんな感じで気象庁に登録依頼メールを投げると、暫くして気象庁の中の人がハブに登録リクエストを投げ、そして以下のようなGETリクエストがが飛んできます(hub.challenge実際はもっと長いです)。
GET /jma-xml/subscriber?hub.verify_token=verify-token&hub.challenge=2prTOceWNym_GTHpQurr_8rnXXL7yGGPjUFVmiwkqX5IkpxAfiswkN2tP&hub.topic=http%3A%2F%2Fxml.kishou.go.jp%2Fなんとか.xml&hub.mode=subscribe&hub.lease_seconds=432000
これを適当に分割すると、こうなります。
リクエストパラメタ名 | 内容 |
---|---|
hub.verify_token | verify-token |
hub.challenge | 2prTOceWNym_GTHpQurr_8rnXXL7yGGPjUFVmiwkqX5IkpxAfiswkN2tP |
hub.topic | http://xml.kishou.go.jp/なんとか.xml |
hub.mode | subscribe |
hub.lease_seconds | 432000 |
hub.lease_secondsが432000秒って、5日毎に登録確認が飛んでくるってことか。それはともかく。
例えばCGIの場合はこんなお返事をすればいいわけです。ステータスコードを指定していることに注意してください。
Status: 200
Content-Type: text/plain
2prTOceWNym_GTHpQurr_8rnXXL7yGGPjUFVmiwkqX5IkpxAfiswkN2tP
ちなみに、このレスポンスで返す文字列(〜N2tP)の末尾に改行コードがつくと認証にしくじります。
これで登録は成功し、以降、なんかある度にPuSH配信がサブスクライバURIへ投げられることになります。
PuSH配信時
ハブからサブスクライバURIへPOSTリクエストでAtomがぶち込まれます。ヘッダはこんな感じです。
リクエストヘッダ | なかみ |
---|---|
User-Agent | AppEngine-Google; (+http://code.google.com/appengine; appid: s~alert-hub) |
Content-Type | application/atom+xml |
X-Hub-Signature | sha1=適当なSHA1 hash |
X-Hub-Signatureってのがついてるのですが、これについては後述。
で、このヘッダに続いてAtomデータがべーっと続きます。どんなAtomが来るかってのは、気象庁のドキュメントに書いてあります。PuSHとは要するにサブスクライバ登録時におけるhub.topicが指すURIのAtomが更新される度に最新のAtom*6を送ってもらうもの(更新を通知された際に自分でAtomそのものをfetchするのではない)なんですね。
このAtomに電文XMLのURIが書いてあるのでそれをfetchしてくればいい。簡単ですね。そのURIは24時間で消滅するらしいので、まぁAtom食わされた時にfetchして後は手元で読みに行くのが正しいでしょう。
Atomには天気概況など一部の情報を除いてcontentにサマリが入ってるので、細かい情報要らないのなら別にXMLをfetchする必要ないかも。そして、Atom一つで複数のXML情報が入ってることもあるんですが、10近いXML情報が入ってデータサイズ10k超えたりするんで、攻撃対策としてあんまり小さいPOSTデータサイズ上限を切ると切ないことになるかもしれません。また、同じXMLのPuSH通知が降ってきて、既にfetchしたXMLをもう一度fetchすることもあるので、一度書いたファイルを上書きするか、別名で保存するか、考える必要があるかも。また、一秒以内に複数回PuSHされることもあるので、epochでファイル名つけたりすると切なさ炸裂します。
X-Hub-Signatureってなんですか。
仕様書見ると、X-Hub-Signatureはハブへサブスクライバを登録する際にパラメタhub.secretを指定したときに発生するものだそうな。で、hub.secretをキーとしてぶち込むAtomのHMAC-SHA1が入るらしい。登録依頼メールにそんなの書いた記憶ないねんけど。あるとすればhub.verify_tokenぐらいで、これをキーにしてHMAC生成すると、X-Hub-Signatureに一致するSHA1が生成されました。おめでとう。
でもこれ、使えないって噂があったりして、これの存在を前提としていいかは微妙です。
ちなみに仕様書には「httpsでサブスクライバを登録する時にしか使っちゃダメよん」というまぁアタリマエの一文があるわけですが、気象庁へ送るメールは平文だし、Alert Hubの登録画面もhttpでアクセスできてしまいます*7。
1/10 12:42追記
Alert Hubはhub.secret指定せずにhub.verify_token指定してると、hub.verify_tokenをhub.secretとして扱うっぽい?
サブスクライバ作ってみたよ
で、実際にPuSH配信を受信するサブスクライバを作りました。前述のとおり一晩ちょろで最低限作ったわけですが、実際に情報が流れてくるようになると欲が出てきて、今は情報がPuSHされるとローカルのIRCチャンネルに発言*8するようになってたり。
サブスクライバはPOST/GETを受け取れるURIを持てば十分なので、実装は色々考えられます。わたしは、perl使ってCGIとして書きました。
最低限動く(PuSHされる度にXMLをローカルへ保存してゆく)であろうコードを、執筆時点で動かしてるコードから抜粋してgistに置いておきます。御参考までに。
デバッグは、Googleの提供するハブhttp://pubsubhubbub.appspot.com/subscribeを使いました*9。livedoorブログ等の対応してる無料ブログにアカウント作って適当に更新かけてデータPuSHさせるのが良いでしょう。
サブスクライバのセキュリティについて
アクセスログを見てると、サブスクライバをPOST/GETするIPは74.125.0.0/16から来るようです。whoisの通りGoogleなんですが、このレンジだって保証されてるわけでもないので、サブスクライバURIが悪意の第三者に漏れてアタックされる可能性を考える必要があります。
- subscribe時には、hub.topicがhttp://xml.kishou.go.jp/以下のリソースURIであることと、hub.verify_tokenが自分の決めたものであることをチェック
- PuSH時には、データサイズが無闇にでかくないこと、Atom内のhrefがhttp://xml.kishou.go.jp/以下であることをチェック。あと、X-Hub-Signatureが使えればいいんですけど。サンプルコードではContent-Length見てるだけですが、ApacheさんがPOSTされるデータ全て受信しようとすることに変わりはないです。Apacheが受信するデータサイズを制限するにはLimitRequestBodyを使いましょう。
あとは、バグのないサブスクライバということでしょうかね。にょる。
*1:天気予報やら気象情報やら
*2:XMLを適当にパースせにゃならんのわけですが、HTML眺めてスクレイパー書くよりは仕様書見てXMLパーサ書くほうが幸せになれる、はず
*3:こう表記すると某エロゲ雑誌と区別が…
*4:今回はテスト運用ということで気象庁に登録依頼メールを投げる必要があるのですが、PuSHが想定する運用は、情報欲しい人が自分で登録。その際に、誰かが勝手に他人のサブスクライバURIをハブに登録できない仕掛けが要る
*5:ちなみに、PuSHの仕様上hub.verify_tokenは必須ではありません。が、現実問題として無いとhub.topicのURI見るしかない訳で、ちょっとね。
*6: [2012/01/10 19:00追記]こう書くと毎回Atomの中身全部送るんちゃうか、と取れるわけですが、送ってくるのはAtomに追加された最新の部分だけをvalid Atomな形式で送りつける、が正しい様子。