You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用httpx调用APNS推送更新Wallet Pass时遇RemoteProtocolError求助

解决APNS推送Wallet Pass时的httpx.RemoteProtocolError问题

我正在开发一个Web服务,通过APNS推送通知更新Wallet Pass。因为httpx支持HTTP/2,所以用它写了测试代码,但运行时抛出httpx.RemoteProtocolError错误,附上完整报错栈,需要解决这个问题以成功向APNS发送请求。

测试代码

import httpx
import ssl
import asyncio

async def send_push():
    context = ssl.create_default_context()
    context.load_verify_locations(cafile = "/Users/valley/Desktop/External_Finder/Learning_Centers_Development/Arcade-Pass/certs/passcertificate.pem")

    payload = {
        "aps" : ""
    }   

    async with httpx.AsyncClient(http2 = True, cert = "/Users/valley/Desktop/External_Finder/Learning_Centers_Development/Arcade-Pass/certs/ArcadePassCertKey.pem") as client:
        r = await client.post("https://api.sandbox.push.apple.com:2197/3/device/4c526af3e9cd29cc0c6f8954de5f68fd1d00348696fe4a984581e35f19fe1ddf", data = payload)
        print(r.http_version)
        print(r.text)
        
asyncio.run(send_push())

报错信息

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_transports/default.py", line 60, in map_httpcore_exceptions
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_transports/default.py", line 353, in handle_async_request
    resp = await self._pool.handle_async_request(req)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 253, in handle_async_request
    raise exc
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/connection_pool.py", line 237, in handle_async_request
    response = await connection.handle_async_request(request)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/connection.py", line 90, in handle_async_request
    return await self._connection.handle_async_request(request)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/http2.py", line 146, in handle_async_request
    raise exc
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/http2.py", line 114, in handle_async_request
    status, headers = await self._receive_response(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/http2.py", line 231, in _receive_response
    event = await self._receive_stream_event(request, stream_id)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/http2.py", line 262, in _receive_stream_event
    await self._receive_events(request, stream_id)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpcore/_async/http2.py", line 291, in _receive_events
    raise RemoteProtocolError(event)
httpcore.RemoteProtocolError: <ConnectionTerminated error_code:ErrorCodes.NO_ERROR, last_stream_id:0, additional_data:7b22726561736f6e223a22426164436572746966>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test_push_notifications.py", line 24, in <module>
    asyncio.run(send_push())
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 608, in run_until_complete
    return future.result()
  File "test_push_notifications.py", line 19, in send_push
    r = await client.post("https://api.sandbox.push.apple.com:2197/3/device/4c526af3e9cd29cc0c6f8954de5f68fd1d00348696fe4a984581e35f19fe1ddf", data = payload)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1848, in post
    return await self.request(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1533, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1620, in send
    response = await self._send_handling_auth(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1648, in _send_handling_auth
    response = await self._send_handling_redirects(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1685, in _send_handling_redirects
    response = await self._send_single_request(request)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_client.py", line 1722, in _send_single_request
    response = await transport.handle_async_request(request)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_transports/default.py", line 353, in handle_async_request
    resp = await self._pool.handle_async_request(req)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/contextlib.py", line 131, in __exit__
    self.gen.throw(type, value, traceback)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/httpx/_transports/default.py", line 77, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.RemoteProtocolError: <ConnectionTerminated error_code:ErrorCodes.NO_ERROR, last_stream_id:0, additional_data:7b22726561736f6e223a22426164436572746966>

问题分析与修复方案

1. 证书配置错误

APNS要求客户端提供证书+私钥的组合用于身份验证:

  • 若ArcadePassCertKey.pem仅为私钥文件,需将证书文件(passcertificate.pem)和私钥文件合并为一个PEM文件,或通过httpx的cert参数传入元组形式的证书路径和私钥路径。
  • 自定义SSL上下文加载验证证书是多余的,httpx默认会使用系统根证书完成APNS服务器的身份验证,可删除这部分代码。

2. Payload格式与请求参数错误

  • APNS要求请求体为合法JSON,当前"aps": ""不符合规范,Wallet Pass的静默推送Payload应使用{"aps": {}}。
  • 发送请求时需用json=payload而非data=payload,data参数会将字典转为表单格式,而APNS仅接受JSON格式的请求体。

3. 缺少必要的HTTP请求头

HTTP/2协议下的APNS请求必须指定两个关键Header:

  • apns-push-type: 对于Wallet Pass推送,值为passbook。
  • apns-topic: 对应你的Wallet Pass在苹果开发者后台配置的Bundle ID,必须准确匹配。

修复后的代码

import httpx
import asyncio

async def send_push():
    # 证书配置:传入证书与私钥的路径元组,若证书有密码则添加第三个元素(密码字符串)
    cert = (
        "/Users/valley/Desktop/External_Finder/Learning_Centers_Development/Arcade-Pass/certs/passcertificate.pem",
        "/Users/valley/Desktop/External_Finder/Learning_Centers_Development/Arcade-Pass/certs/ArcadePassCertKey.pem"
    )

    # 符合APNS规范的Wallet Pass推送Payload
    payload = {
        "aps": {}
    }   

    # 必要的请求头配置
    headers = {
        "apns-push-type": "passbook",
        "apns-topic": "com.yourcompany.yourapp.pass"  # 替换为实际的Pass Bundle ID
    }

    async with httpx.AsyncClient(http2=True, cert=cert) as client:
        r = await client.post(
            "https://api.sandbox.push.apple.com:2197/3/device/4c526af3e9cd29cc0c6f8954de5f68fd1d00348696fe4a984581e35f19fe1ddf",
            json=payload,
            headers=headers
        )
        print(r.http_version)
        print(r.status_code)
        print(r.text)
        
asyncio.run(send_push())

额外注意事项

  • 确保设备Token为沙箱环境的有效Token,且设备已安装对应的Wallet Pass。
  • 若证书设置了密码,需在cert元组中添加密码参数,例如cert=(cert_path, key_path, "your-cert-password")。
  • 检查苹果开发者后台的推送权限是否已正确配置,确保证书与Bundle ID关联。

内容的提问来源于stack exchange,提问作者Yash Kothari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:45:38