Ben Holness
Ben Holness

Reputation: 2707

New APNS Provider API and PHP

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!

Upvotes: 22

Views: 32707

Answers (7)

Roman Laptev
Roman Laptev

Reputation: 1

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

Upvotes: -2

Norbert
Norbert

Reputation: 21

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.

Upvotes: 1

Samo
Samo

Reputation: 2181

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

Upvotes: 4

valfer
valfer

Reputation: 3545

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

Upvotes: 4

kmarin
kmarin

Reputation: 342

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'

Upvotes: 1

tiempor3al
tiempor3al

Reputation: 439

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

}

Upvotes: 37

Madox
Madox

Reputation: 156

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

Upvotes: 4

Related Questions