New APNS Provider API and PHP
Asked Answered
C

7

22

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!

Catboat answered 8/1, 2016 at 18:53 Comment(0)
B
37

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

}
Bigg answered 16/1, 2016 at 20:37 Comment(13)
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?Catboat
I've had issues getting this working, more in my question here: #35416747Indignant
@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.Bigg
for c) can also use "brew install curl --with-nghttp2" (from simonecarletti.com/blog/2016/01/http2-curl-macosx) -- this helped mePostexilian
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?Exostosis
@Exostosis 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..Cardcarrying
@Cardcarrying 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): #38024309Exostosis
@Exostosis 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.Cardcarrying
Thanks for this code! To get a better response I needed to add the CURLOPT_RETURNTRANSFER option.Llanes
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.6Lesko
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.Bandmaster
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 scriptBandmaster
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)Enjambement
F
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);

Fredela answered 22/1, 2016 at 21:3 Comment(1)
I'm on openssl 1.0.2k and I'm still getting the string missing or corruptLesko
Z
4

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

Zygophyte answered 10/4, 2016 at 9:56 Comment(0)
C
4

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
Combings answered 4/5, 2016 at 16:53 Comment(1)
Next step is to build a PHP CURL module with this configurationCombings
R
1

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'
Rhombencephalon answered 9/3, 2016 at 23:13 Comment(0)
E
1

To resolve HTTP/2 client preface string missing or corrupt errors in PHP 5.6 I created an Apache and CLI PHP Docker image that you might want to rely on or just look up what I did in the Dockerfile to build your own. Same can probably apply to PHP 7.0 although I haven't tried.

Ewen answered 1/1, 2017 at 15:52 Comment(2)
Could you please add some install instructions for the rest of the mortals who have no idea how to implement your code? Thanks!Medford
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)Ewen
O
-2

yes, it's possible:

$errno=0;$errstr="";
$sock = stream_socket_client(
  "api.push.apple.com:443",
  $errno,
  $errstr,
  4,
  STREAM_CLIENT_CONNECT,
  stream_context_create(["ssl" => ["local_cert" => "/path/to/cert.pem","verify_peer"=>false,"verify_peer_name"=>false]])
);
stream_socket_enable_crypto($sock, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT);
Oxidimetry answered 14/3, 2017 at 16:9 Comment(1)
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; ?Women

© 2022 - 2024 — McMap. All rights reserved.