Question

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?

Was it helpful?

Solution 2

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

}

OTHER TIPS

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.

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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top