APNs 提供商 API HTTP/2 使用 php,curl 导致发送多个推送通知时出错

APNs Provider API HTTP/2 using php, curl causes error on multiple push notifications sent

我已经设置了一个 linux 服务器 运行 Ubuntu 15.10 x64。我已经设置 php/openssl/curl 一起使用 HTTP/2 发送。我正在测试的 PHP 脚本如下。基本上,我发送了两条推送消息,它们都使用相同的 curl 句柄,以便按照 Apple 的建议保持连接打开。第一条消息顺利通过并显示在我的设备上,但当它尝试发送第二条消息时,我在 "SSL re-using session ID" 之后收到错误 "Unknown SSL protocol error in connection to api.development.push.apple.com:443"。有没有人对可能出现的问题有任何建议?有人可以尝试脚本并让我知道他们是否遇到同样的事情吗?

以下是我服务器的版本打印输出:

PHP

PHP 7.0.5-2+deb.sury.org~wily+1 (cli) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies

OpenSSL

OpenSSL 1.0.2d 9 Jul 2015

卷曲

curl 7.48.0 (x86_64-pc-linux-gnu) libcurl/7.48.0 OpenSSL/1.0.2d zlib/1.2.8 libidn/1.28 nghttp2/1.10.0-DEV librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: IDN IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets

PHP 代码:

<?php
$ch = curl_init();
$device_token   = 'TOKEN HERE';
$pem_file       = 'YOURFILE.pem';
$pem_secret     = 'PEM PASS';
$apns_topic     = 'com.YOURTOPIC';

//curl_setopt($ch, CURLOPT_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apns-topic: $apns_topic"));
curl_setopt($ch, CURLOPT_SSLCERT, $pem_file);
curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $pem_secret);
curl_setopt($ch, CURLOPT_VERBOSE , true);

echo "Try 1 ================================================" . PHP_EOL;

//setup and send first push message
$url = "https://api.development.push.apple.com/3/device/$device_token";
curl_setopt($ch, CURLOPT_URL, "{$url}");
$sample_alert = '{"aps":{"alert":"hi #1","sound":"default"}}';
curl_setopt($ch, CURLOPT_POSTFIELDS, $sample_alert);

$response = curl_exec($ch);
$httpcode = curl_getinfo($ch);
//var_dump($response);
//var_dump($httpcode);

echo "Try 2 ================================================" . PHP_EOL;

//setup and send second push message
$url = "https://api.development.push.apple.com/3/device/$device_token";
curl_setopt($ch, CURLOPT_URL, "{$url}");
$sample_alert = '{"aps":{"alert":"hi #2","sound":"default"}}';
curl_setopt($ch, CURLOPT_POSTFIELDS, $sample_alert);

$response = curl_exec($ch);
$httpcode = curl_getinfo($ch);
//var_dump($response);
//var_dump($httpcode);

curl_close($ch);

使用 curl verbose 从上面的脚本 运行 输出(个人项目替换为 XXXXX):

Try 1 ================================================
*   Trying 17.110.227.100...
* Connected to api.development.push.apple.com (17.110.227.100) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* SSL connection using TLSv1.2 / XXXXXXXXXXXXXXXXXXXXXXXXXXX
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=api.development.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
*  start date: Jun 19 01:49:43 2015 GMT
*  expire date: Jul 18 01:49:43 2017 GMT
*  subjectAltName: host "api.development.push.apple.com" matched cert's "api.development.push.apple.com"
*  issuer: CN=Apple IST CA 2 - G1; OU=Certification Authority; O=Apple Inc.; C=US
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* TCP_NODELAY set
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x555e84417f80)
> POST /3/device/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
HTTP/1.1
Host: api.development.push.apple.com
Accept: */*
apns-topic: com.XXXXXXX.XXXXXXXXXXXXXXXXXX
Content-Length: 43
Content-Type: application/x-www-form-urlencoded

* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* We are completely uploaded and fine
< HTTP/2.0 200
< apns-id:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
<
* Connection #0 to host api.development.push.apple.com left intact
Try 2 ================================================
* Found bundle for host api.development.push.apple.com: 0x555e8442afb0 [can multiplex]
* Hostname api.development.push.apple.com was found in DNS cache
*   Trying 17.110.227.100...
* Connected to api.development.push.apple.com (17.110.227.100) port 443 (#1)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* SSL re-using session ID
* Unknown SSL protocol error in connection to api.development.push.apple.com:443
* Closing connection 1

我试过你的代码,它在我的机器上运行良好。尽管如此,我发现我们的详细日志之间存在一些差异。 Try 1的日志是一样的,但是Try 2的日志有些不同,这是我的:

...
Try 2 ================================================
* Found bundle for host api.development.push.apple.com: 0x7fe1b380e730 [can multiplex]
* Re-using existing connection! (#0) with host api.development.push.apple.com
* Connected to api.development.push.apple.com (17.172.238.203) port 443 (#0)
* Using Stream ID: 3 (easy handle 0x7fe1b305da00)
> POST /3/device/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX HTTP/1.1
Host: api.development.push.apple.com
Accept: */*
apns-topic: it.XXX.XXXXX
Content-Length: 43
Content-Type: application/x-www-form-urlencoded

* We are completely uploaded and fine
< HTTP/2.0 200
< apns-id:XXXXXXXXXXXXXXXXX
< 
* Connection #0 to host api.development.push.apple.com left intact

我在你的日志中没有看到文本 "Re-using existing connection!" ...

编辑

解决方案似乎是将 curl 降级到 7.47.1

有同样的问题。在挖掘代码时在 github 包上找到了这个解决方案,对我有用。 https://github.com/nfilin/apns-http2.

将此添加到卷曲的选项中,然后重试。

curl_setopt($ch, CURLOPT_SSLKEY, $pem_file);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');

正如接受的答案所说,它通过降级到 cur-7.47.1 来工作...但前提是所有消息都发送 return HTTP 200 代码。

例如,如果第二条消息包含无效的有效负载或设备令牌(并且正如预期的那样,收到了 HTTP 4xx 响应),则在 第三条 消息上获取 "Unknown SSL protocol error in connection to api.development.push.apple.com:443".

我想预期的行为是在第二条消息上收到 4xx,但不会中断连接。

一个不合标准的解决方法是,如果没有收到 200,则断开连接并重新连接。但我想经过许多 disconnect/reconnect 个周期后,我们会被苹果服务器禁止。