『入る学科間違えた高専生』の日記

プログラミングのコードを書いたりする予定です。あとは日記等。あといつまで高専生やねん

PushKitのプッシュ通知をPython3(hyper)から送信する

はじめに

ちょっと意味のわからないタイトルになった。いつものことなので大して気にしていない。

developer.apple.com

developer.apple.com

上記のAppleの技術をPython3を使って送信するっていうやつです。

今更感があるがそんなのは関係ないっていうことで。(???)

環境

  • Python3.7.0
  • hyper 0.7.0

pypi.org

開発自体はmacOS 10.14.2で行っていますが、Docker等に乗せても動いたので問題ないかと思います。

準備

こちらの記事などが参考になるかと思います。

qiita.com

ソースコード

環境変数として以下を設定しました。

Name Value
USE_SANDBOX 0 or 1
BUNDLE_ID Xcodeで設定しているBundleIDの最後に .voip を付加したもの

必要なもののインポートとクライアント証明書となるpemファイルのPath取得

from hyper import HTTP20Connection
from pathlib import Path

import json
import os
import ssl

BASE_DIR = Path(__file__).parent
CERT_FILE = BASE_DIR / f'YOUR_VOIP_CERTIFICATE.pem'

サンドボックスを使うかどうかの設定と送信するbodyとヘッダー

if bool(int(os.environ.get('USE_SANDBOX'))):
    host = 'api.development.push.apple.com'
else:
    host = 'api.push.apple.com'

body = json.dumps(
    {'key': 'send by hyper\' HTTP20Connection'}  # ここはなんでもいいです。容量以内であれば。
)
headers = {
    'apns-expiration': '0',  # 使用期限。詳しくは公式ドキュメント
    'apns-priority': '10',  # 優先度。詳しくは公式ドキュメント
    'apns-topic': os.environ.get('BUNDLE_ID')
}

クライアント証明書の設定及び、HTTP/2.0での通信をするコネクションの生成

ssl_context = ssl.create_default_context()
ssl_context.load_cert_chain(CERT_FILE)
connection = HTTP20Connection(host, post=443, ssl_context=ssl_context, force_proto='h2')

curl コマンドで使用する -E , --cert にあたるものの設定です。

HTTP20Connection のコンストラクタ内で port=443 の場合は secure=True になる実装だったので特に secure については設定しませんでした。

https://github.com/Lukasa/hyper/blob/master/hyper/http20/connection.py#L107

実際に送信

def send_request(registration_id):
    connection.request(
        'POST', f'/3/device/{registration_id}', body, headers
    )
    response = connection.get_response()

    if response.status == 200:
        return
    response_body = json.loads(response.read().decode('utf-8'))
    reason = response_body.get('reason')
    if reason:
        print(reason)

最後にコネクションを閉じる

connection.close()

ちょっと補足

from contextlib import closing


def create_connection():
    ssl_context = ssl.create_default_context()
    ssl_context.load_cert_chain(CERT_FILE)
    return HTTP20Connection(host, post=443, ssl_context=ssl_context, force_proto='h2')


with closing(create_connection()) as connection:
    send_request("YOUR_REGISTRATION_ID")

のようにすると最後に HTTP20Connectionclose() 忘れを防げますね。

最後に

PushKitの送信にはまだAPNsのように有効期限のない証明書の設定ができないようなのでつらいですが以前までのようなSocket通信を使ってやっていたものに比べて圧倒的に実装面でも楽で助かります。

ちなみにAPNsの送信の記事です。

nnsnodnb.hatenablog.jp