New APNS Provider API and PHP

31,023

Solution 1

With the new HTTP/2 APNS provider API, you can use curl to send push notifications.

EDIT

Before proceeding (as noted by @Madox), openssl >= 1.0.2e of should be installed (from package preferably). Verify with the command

openssl version

a) Your version of PHP should be >= 5.5.24 so that the constant CURL_HTTP_VERSION_2_0 is defined.

b) Make sure that you have curl version 7.46+ installed in your system with

curl --version

c) Curl should have http/2 support enabled. In the output when typing the previous command, you should see a line like this one:

Features: IDN IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets 

if HTTP2 doesn't show up, you can follow this excellent tutorial to install http/2 for curl https://serversforhackers.com/video/curl-with-http2-support

Verify that curl detected openssl >= 1.0.2e, doing curl --version should output something like this:

curl 7.47.1 (x86_64-pc-linux-gnu) libcurl/7.47.1 OpenSSL/1.0.2f zlib/1.2.8 libidn/1.28 nghttp2/1.8.0-DEV librtmp/2.3

e) Once you everything installed, you can test it in the command line:

curl -d '{"aps":{"alert":"hi","sound":"default"}}' \ 
--cert <your-certificate.pem>:<certificate-password> \ 
-H "apns-topic: <your-app-bundle-id>" \ 
--http2  \ 
https://api.development.push.apple.com/3/device/<device-token>

f) Here is a sample code in PHP that I have successfully tried:

if(defined('CURL_HTTP_VERSION_2_0')){

    $device_token   = '...';
    $pem_file       = 'path to your pem file';
    $pem_secret     = 'your pem secret';
    $apns_topic     = 'your apns topic. Can be your app bundle ID';


    $sample_alert = '{"aps":{"alert":"hi","sound":"default"}}';
    $url = "https://api.development.push.apple.com/3/device/$device_token";

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $sample_alert);
    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);
    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    //On successful response you should get true in the response and a status code of 200
    //A list of responses and status codes is available at 
    //https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/TheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH107-SW1

    var_dump($response);
    var_dump($httpcode);

}

Solution 2

I'm using CentOS 6 and the way to solve was to install from source cURL and OpenSSL.

It may look very simple but it took me 3 days to figure out the right configuration for the packages so I think I can help someone by posting what I did here.

It proved a little tricky for me since I'm not used to install packages from source, but I now can connect to APNs using HTTPS over HTTP/2.

The details of what I did is here:

  1. Download and uncompress cURL and OpenSSL:

    wget https://curl.haxx.se/download/curl-7.47.1.tar.gz
    wget https://www.openssl.org/source/openssl-1.0.2h.tar.gz
    
  2. Configure OpenSSL with the following flags (I'm not sure what they do, but is what worked for me):

    export CXXFLAGS="$CXXFLAGS -fPIC"
    ./config zlib enable-ssl3 enable-shared
    
  3. make and install OpenSSL

  4. Configure cURL with the following flag:

    ./configure --with-ssl=/usr/local/ssl/
    
  5. Make and install cURL

  6. set LD_LIBRARY_PATH to /usr/local/ssl/lib/

    export LD_LIBRARY_PATH=/usr/local/ssl/lib/                  
    
  7. Test

    /usr/local/bin/curl -v -d '{"aps":{"alert":"hi","sound":"default"}}' --cert cert.crt --key cert.key -H "apns-topic: topics" --http2 https://api.development.push.apple.com:443/3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
    

The result:

*   Trying 17.172.238.203...
* Connected to api.development.push.apple.com (17.172.238.203) 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/pki/tls/certs/ca-bundle.crt
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* 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 0x1091110)
> POST /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0 HTTP/1.1
> Host: api.development.push.apple.com
> User-Agent: curl/7.48.0
> Accept: */*
> apns-topic: topics
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* We are completely uploaded and fine
< HTTP/2.0 400
<
* Connection #0 to host api.development.push.apple.com left intact
{"reason":"BadDeviceToken"}

As you can see, I got rid of the ugly

▒@@▒HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 504f5354202f332f6465766963652f746573742048545450

Solution 3

I could successfully send push via HTTP2 using php CURL and read the feedback directly in the response body (here I wrote a short tutorial on how to do this: Sending Push Notification with HTTP2 (and PHP)). I think that you can check the response body reading from the socket (I don't remember exactly the php function, perhaps "fgets").

Solution 4

I want to add some information to tiempor3al answer.

1) curl must be compiled with openssl version >=1.0.2 to fully support http/2. I receive "?@@?HTTP/2 client preface string missing or corrupt..." error when I compiled it with CentOS stock openssl-1.0.1e.

2) if your version of php module mod_curl.so compiled without CURL_HTTP_VERSION_2_0 constant, you could replace it with integer number 3:

curl_setopt($ch, CURLOPT_HTTP_VERSION, 3);

Solution 5

Follow this guide [http://cloudfields.net/blog/ios-push-notifications-encryption/][1] to generate and merge your certificate and private key. Once you merge and your sslcert and pkey as described in the guide with same file names, just try the curl command below.

curl -X POST -H 'apns-topic: com.mycompany.ios.BadassApp' -d '{"aps":{"content-available":1,"alert":"hi","sound":"default"}}' --cert apns_cert.pem:yourCertPassword --http2 'https://api.development.push.apple.com:443/3/device/b8de1sf067effefc398d792205146fc67dn0e96b0ff21ds81cabe384bbe71353'
Share:
31,023

Related videos on Youtube

Ben Holness
Author by

Ben Holness

Updated on January 06, 2020

Comments

  • Ben Holness
    Ben Holness over 4 years

    I started creating some code based upon this for sending push notifications from PHP.

    However now that I have understood there is a new API which utilizes HTTP/2 and provides feedback in the response, I am trying to figure out what I need to do to get that feedback.

    I haven't been able to find any tutorials or sample code to give me direction (I guess because it is so new).

    Is it possible to use the stream_socket_client() method of connecting to APNS with the new provider API? How do I get the feedback? All I get back from fwrite($fp, $msg, strlen($msg)) right now is a number. For all intents and purposes, you can consider my code the same as the code from the SO question I based my code on

    Thanks!

  • Ben Holness
    Ben Holness over 8 years
    Thanks :) ... PHP is 5.6.17, curl is 7.46.0 and shows HTTP2 support. When I try step d or e, I get a response "?@@?HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: [hex dump]". Any thoughts?
  • Mark Locker
    Mark Locker about 8 years
    I've had issues getting this working, more in my question here: stackoverflow.com/questions/35416247/…
  • tiempor3al
    tiempor3al about 8 years
    @BenHolness In my experience, this error has to do with the version of openssl being used. You should have curl compiled with version >= 1.0.2e of openssl. Verify with the command curl --version.
  • dragonflyesque
    dragonflyesque about 8 years
    for c) can also use "brew install curl --with-nghttp2" (from simonecarletti.com/blog/2016/01/http2-curl-macosx) -- this helped me
  • Samo
    Samo about 8 years
    Next step is to build a PHP CURL module with this configuration
  • Nickkk
    Nickkk almost 8 years
    Thanks for this very clear answer. I can successfully send push notifications from the command line but when using your PHP code I keep getting this error: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes .... Any idea what the problem might be?
  • YumeYume
    YumeYume over 7 years
    @Nickkk I am currently experiencing the same issue.. Have you found anything to solve this ? I just checked, all my packages are recent enough to handle this, it's working on command line, but not in the script. I dumped the version number of openssl in the script, and it seems that it uses an older version that what I installed (using 1.0.1t though 1.1.0e is installed). A colleague just told me that PHP got its own openssl package, which is problematic..
  • Nickkk
    Nickkk over 7 years
    @YumeYume I couldn't find a solution yet. I tried everything I could think of, but without success. I opened another question here (maybe you want to try to follow the approach described in the first answer and see if it works): stackoverflow.com/questions/38023809/…
  • YumeYume
    YumeYume over 7 years
    @Nickkk I was able to get everything to work ! I used apt-get autoremove php5 to clean packages, reinstalled it, and it detected my openssl package in the libs, and used it ! I guess I could have configured that instead of reinstalling, but I'm still a beginner with debian, so I went for the easy mode.
  • Castles
    Castles over 7 years
    Thanks for this code! To get a better response I needed to add the CURLOPT_RETURNTRANSFER option.
  • Jacksonkr
    Jacksonkr about 7 years
    I'm getting {"reason":"DeviceTokenNotForTopic"} which is odd because this works with an apsn python script I found here. Any ideas? #openssl1.0.2k #curl7.53.1 #osx10.11.6
  • Jacksonkr
    Jacksonkr about 7 years
    I'm on openssl 1.0.2k and I'm still getting the string missing or corrupt
  • Dmytro Shvetsov
    Dmytro Shvetsov about 7 years
    Hello! Can you share all code for sending notifications ? You "Build the binary notification" like: $payload = json_encode($body); chr(0) . pack('n', 32) . pack('H*', $device_token) . pack('n', strlen($payload)) . $payload; ?
  • Tom Pace
    Tom Pace almost 7 years
    This is crazy, I had curl 7.35 and OpenSSL 1.0.1f. Upgrade to curl 7.46and OpenSSL 1.0.2l (May 25 2017). Built OpenSSL 1.0.2l and type openssl version and it shows 1.0.2l, and then after building curl, directly using ./configure with the OpenSSL 1.0.2 path, I still get OpenSSL 1.0.1f from curl --version. I'm at my wits end.
  • Tom Pace
    Tom Pace almost 7 years
    Burning out... Going through the raw 10s of thousands of lines of code of ./configure in curl, it attempts to build a test to confirm the availability of OpenSSL at location --with-ssl=____ and while the installation is there, and the particular HMAC_Update method exists within lcrypto.a, somehow the test fails and thus kicks the lib include folder out of the path and I get zero OpenSSL. So basically, curl 7.46 doesn't build on Ubuntu 14.04 with OpenSSL 1.0.2l and my brain is bleeding from reformatting thousands of lines of awkward-indented bash script
  • andreszs
    andreszs almost 7 years
    Could you please add some install instructions for the rest of the mortals who have no idea how to implement your code? Thanks!
  • Norbert
    Norbert almost 7 years
    If you're using Docker, you just need to use norbertm/php-5.6-apache-http2:5.6.31 or norbertm/php-5.6-cli-http2:5.6.31. If not using Docker, what you need to do is build curl with nghttp2 (here is how I did it) and then build PHP using that curl (here is how the official PHP Docker image does that)
  • tharkay
    tharkay over 6 years
    I was able to install curl with http/2 and use it on the command line but got errors when using it in Php with the code from above. Simply restarting apache with sudo apachectl restart fixed my problem. (maybe this helps someone)