New APNS Provider API and PHP
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:
-
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
-
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
make and install OpenSSL
-
Configure cURL with the following flag:
./configure --with-ssl=/usr/local/ssl/
Make and install cURL
-
set LD_LIBRARY_PATH to /usr/local/ssl/lib/
export LD_LIBRARY_PATH=/usr/local/ssl/lib/
-
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'
Related videos on Youtube
Ben Holness
Updated on January 06, 2020Comments
-
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 fromfwrite($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 onThanks!
-
Ben Holness over 8 yearsThanks :) ... 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 about 8 yearsI've had issues getting this working, more in my question here: stackoverflow.com/questions/35416247/…
-
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 about 8 yearsfor c) can also use "brew install curl --with-nghttp2" (from simonecarletti.com/blog/2016/01/http2-curl-macosx) -- this helped me
-
Samo about 8 yearsNext step is to build a PHP CURL module with this configuration
-
Nickkk almost 8 yearsThanks 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 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 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 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 over 7 yearsThanks for this code! To get a better response I needed to add the CURLOPT_RETURNTRANSFER option.
-
Jacksonkr about 7 yearsI'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 about 7 yearsI'm on
openssl 1.0.2k
and I'm still getting thestring missing or corrupt
-
Dmytro Shvetsov about 7 yearsHello! 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 almost 7 yearsThis 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 almost 7 yearsBurning 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 almost 7 yearsCould you please add some install instructions for the rest of the mortals who have no idea how to implement your code? Thanks!
-
Norbert almost 7 yearsIf 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 over 6 yearsI 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)