0
votes

I'm trying to implement GCM server using PHP and Zend Framework on Google App Engine. So far it works fine locally, but fails with this message when uploaded to App Engine:

Here is the code:

$ids = '....my device id....';
$apiKey = '...my api key...';

$data = array( 'message' => 'Hello World!' );
$gcm_client = new Client();
$gcm_client->setApiKey($apiKey);
$gcm_message = new Message();
$gcm_message->setTimeToLive(86400);
$gcm_message->setData($data);
$gcm_message->setRegistrationIds($ids);

$response = $gcm_client->send($gcm_message);
var_dump($response);

And it fails with this error message:

PHP Fatal error: Uncaught exception 'ErrorException' with message 'stream_socket_client(): unable to connect to android.googleapis.com:443 (Unknown error 4294967295)' in /base/data/home/..../backend:v1.375711862873219029/vendor/zendframework/zend-http/Zend/Http/Client/Adapter/Socket.php:253

I know App Engine doesn't allow socket connections and offers urlFetch wrapper for http and https, but how do I tell Zend Framework to use this transport?

3
I ended up making my own class implementing Zend\Http\Client\Adapter\AdapterInterface that uses URLFetch by opening a URL using the usual fopen with stream context to send POST request. Although this works I'm not sure it's the best way. Would prefer to use the framework capabilities, if possible.astax
I assume you have problem because of your firewall see this answer stackoverflow.com/a/39590519/2652524Gujarat Santana
@GujaratSantana I asked this question over two years ago and have found a workaround as described in my earlier comment. But this is definitely not a firewall problem - note the error message is different and also my custom implementation of Zend\Http\Client\Adapter\AdapterInterface worked. Additionally, the code was deployed in Google AppEngine, where a developer has no control over firewall.astax
okay good to knowGujarat Santana

3 Answers

0
votes

Try enabling Billing. As far as I remember sockets are enabled only for paid apps.

This won't charge you anything (unless you exceed free quota) but should get rid of the error.

0
votes

Promoted this from a comment - I ended up making my own class implementing Zend\Http\Client\Adapter\AdapterInterface that uses URLFetch by opening a URL using the usual fopen with stream context to send POST request. Although this works I'm not sure it's the best way. Would prefer to use the framework capabilities, if possible.

I'm not sure if this is going to help anyone, as both ZendFramework and AppEngine have evolved since I asked the question, but here is the adapter I've implemented:

use Zend\Http\Client\Adapter\AdapterInterface;
use Zend\Http\Client\Adapter\Exception\RuntimeException;
use Zend\Http\Client\Adapter\Exception\TimeoutException;
use Zend\Stdlib\ErrorHandler;

class URLFetchHttpAdapter implements AdapterInterface
{
    protected $stream;
    protected $options;

    /**
     * Set the configuration array for the adapter
     *
     * @param array $options
     */
    public function setOptions($options = array())
    {
        $this->options = $options;
    }

    /**
     * Connect to the remote server
     *
     * @param string $host
     * @param int $port
     * @param  bool $secure
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // no connection yet - it's performed in "write" method
    }

    /**
     * Send request to the remote server
     *
     * @param string $method
     * @param \Zend\Uri\Uri $url
     * @param string $httpVer
     * @param array $headers
     * @param string $body
     *
     * @throws \Zend\Loader\Exception\RuntimeException
     * @return string Request as text
     */
    public function write($method, $url, $httpVer = '1.1', $headers = array(), $body = '')
    {
        $headers_str = '';
        foreach ($headers as $k => $v) {
            if (is_string($k))
                $v = ucfirst($k) . ": $v";
            $headers_str .= "$v\r\n";
        }

        if (!is_array($this->options))
            $this->options = array();

        $context_arr = array("http" =>
                                 array( "method" => $method,
                                        "content" => $body,
                                        "header" => $headers_str,
                                        "protocol_version" => $httpVer,
                                        'ignore_errors' => true,
                                        'follow_location' => false,
                                 ) + $this->options
        );
        $context = stream_context_create($context_arr);

        ErrorHandler::start();
        $this->stream = fopen((string)$url, 'r', null, $context);
        $error = ErrorHandler::stop();
        if (!$this->stream) {
            throw new \Zend\Loader\Exception\RuntimeException('', 0, $error);
        }
    }


    /**
     * Read response from server
     *
     * @throws \Zend\Http\Client\Adapter\Exception\RuntimeException
     * @return string
     */
    public function read()
    {
        if ($this->stream) {
            ErrorHandler::start();
            $metadata = stream_get_meta_data($this->stream);
            $headers = join("\r\n", $metadata['wrapper_data']);

            $contents = stream_get_contents($this->stream);
            $error = ErrorHandler::stop();
            if ($error)
                throw $error;

            $this->close();

            //echo $headers."\r\n\r\n".$contents;
            return $headers."\r\n\r\n".$contents;
        } else {
            throw new RuntimeException("No connection exists");
        }
    }

    /**
     * Close the connection to the server
     *
     */
    public function close()
    {
        if (is_resource($this->stream)) {
            ErrorHandler::start();
            fclose($this->stream);
            ErrorHandler::stop();
            $this->stream = null;
        }
    }

    /**
     * Check if the socket has timed out - if so close connection and throw
     * an exception
     *
     * @throws TimeoutException with READ_TIMEOUT code
     */
    protected function _checkSocketReadTimeout()
    {
        if ($this->stream) {
            $info = stream_get_meta_data($this->stream);
            $timedout = $info['timed_out'];
            if ($timedout) {
                $this->close();
                throw new TimeoutException(
                    "Read timed out after {$this->options['timeout']} seconds",
                    TimeoutException::READ_TIMEOUT
                );
            }
        }
    }

}
0
votes
public function sendAndroidPushNotification($registration_ids, $message) 
{

    $registrationIds = array($registration_ids);
    $msg = array(
        'message' => $message,
        'title' => 'notification center',
        'vibrate' => 1,
        'sound' => 1
    );

    $fields = array(
        'registration_ids' => $registrationIds,
        'data' => $msg
    );

    $fields = json_encode($fields);
    $arrContextOptions=array(
        "http" => array(
            "method" => "POST",
            "header" =>
            "Authorization: key = <YOUR_APP_KEY>". "\r\n" .
            "Content-Type: application/json". "\r\n",
            "content" => $fields,
         ),
         "ssl"=>array(
             "allow_self_signed"=>true,
             "verify_peer"=>false,
         ),
    );

    $arrContextOptions = stream_context_create($arrContextOptions);
    $result = file_get_contents('https://android.googleapis.com/gcm/send', false, $arrContextOptions);

    return $result;
}