Search code examples
apple-push-notificationshttp2

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


I have set up a linux server running Ubuntu 15.10 x64. I have set up php/openssl/curl to work together to send using HTTP/2. The PHP script I am testing with is below. Basically, I'm sending two push messages, both using the same curl handle in order to keep the connection open as recommended by Apple. The first message goes through and shows up on my device just fine, but when it tries to send the second, I get an error "Unknown SSL protocol error in connection to api.development.push.apple.com:443" after "SSL re-using session ID". Does anyone have any suggestions about what could be wrong? Could someone try the script and let me know if they are experiencing the same thing?

Below are the version printouts from my server:

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

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 CODE:

<?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);

Output from running the script above using curl verbose (personal items replaced with 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

Solution

  • I tried your code and it works right on my machine. Nevertheless I see some differences between our verbose logs. The log of Try 1 is identical, but the log in Try 2 has some differences, this is mine:

    ...
    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
    

    I don't see in your log the text "Re-using existing connection!" ...

    EDIT

    The solution seems to be to downgrade curl to 7.47.1