├── .gitignore ├── phpunit.xml ├── tests ├── encoding_tests.php ├── channel_tests.php ├── websocketevent_tests.php ├── response_tests.php ├── websocketmessageformat_tests.php ├── httpstreamformat_tests.php ├── httpresponseformat_tests.php ├── grippubcontrol_tests.php └── gripcontrol_tests.php ├── src ├── channel.php ├── websocketevent.php ├── encoding.php ├── response.php ├── websocketmessageformat.php ├── httpstreamformat.php ├── httpresponseformat.php ├── grippubcontrol.php └── gripcontrol.php ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # gedit 2 | *~ 3 | 4 | vendor 5 | composer.lock 6 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | ./tests/ 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/encoding_tests.php: -------------------------------------------------------------------------------- 1 | assertFalse(GripControl\Encoding::is_binary_data('text')); 8 | $this->assertTrue(GripControl\Encoding::is_binary_data("\x04\x00\xa0\x00")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/channel_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($ch->name, 'name'); 9 | $this->assertEquals($ch->prev_id, null); 10 | $ch = new GripControl\Channel('name', 'prev-id'); 11 | $this->assertEquals($ch->name, 'name'); 12 | $this->assertEquals($ch->prev_id, 'prev-id'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/websocketevent_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($we->type, 'type'); 9 | $this->assertEquals($we->content, null); 10 | $we = new GripControl\WebSocketEvent('type', 'content'); 11 | $this->assertEquals($we->type, 'type'); 12 | $this->assertEquals($we->content, 'content'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/response_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($re->code, null); 9 | $this->assertEquals($re->reason, null); 10 | $this->assertEquals($re->headers, null); 11 | $this->assertEquals($re->body, null); 12 | $re = new GripControl\Response('code', 'reason', 'headers', 'body'); 13 | $this->assertEquals($re->code, 'code'); 14 | $this->assertEquals($re->reason, 'reason'); 15 | $this->assertEquals($re->headers, 'headers'); 16 | $this->assertEquals($re->body, 'body'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/channel.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | $this->prev_id = $prev_id; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/websocketevent.php: -------------------------------------------------------------------------------- 1 | type = $type; 24 | $this->content = $content; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/websocketmessageformat_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($wm->content, 'content'); 9 | } 10 | 11 | public function testName() 12 | { 13 | $wm = new GripControl\WebSocketMessageFormat('content'); 14 | $this->assertEquals($wm->name(), 'ws-message'); 15 | } 16 | 17 | public function testExport() 18 | { 19 | $wm = new GripControl\WebSocketMessageFormat('content'); 20 | $this->assertEquals($wm->export(), array('content' => 'content')); 21 | $wm = new GripControl\WebSocketMessageFormat("\x04\x00\xa0\x00"); 22 | $this->assertEquals($wm->export(), array('content-bin' => base64_encode("\x04\x00\xa0\x00"))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/encoding.php: -------------------------------------------------------------------------------- 1 | = 0x7f) { 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/response.php: -------------------------------------------------------------------------------- 1 | code = $code; 28 | $this->reason = $reason; 29 | $this->headers = $headers; 30 | $this->body = $body; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Fanout, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/websocketmessageformat.php: -------------------------------------------------------------------------------- 1 | content = $content; 23 | } 24 | 25 | // The name used when publishing this format. 26 | public function name() 27 | { 28 | return 'ws-message'; 29 | } 30 | 31 | // Exports the message in the required format depending on whether the 32 | // message content is binary or not. 33 | public function export() 34 | { 35 | $out = array(); 36 | if (Encoding::is_binary_data($this->content)) { 37 | $out['content-bin'] = base64_encode($this->content); 38 | } else { 39 | $out['content'] = $this->content; 40 | } 41 | return $out; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/httpstreamformat_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($hf->content, 'content'); 9 | $this->assertEquals($hf->close, false); 10 | $hf = new GripControl\HttpStreamFormat('content', true); 11 | $this->assertEquals($hf->content, 'content'); 12 | $this->assertEquals($hf->close, true); 13 | } 14 | 15 | /** 16 | * @expectedException RuntimeException 17 | */ 18 | public function testIntializeException() 19 | { 20 | $hf = new GripControl\HttpStreamFormat(); 21 | } 22 | 23 | public function testName() 24 | { 25 | $hf = new GripControl\HttpStreamFormat('content'); 26 | $this->assertEquals($hf->name(), 'http-stream'); 27 | } 28 | 29 | public function testExport() 30 | { 31 | $hf = new GripControl\HttpStreamFormat('content'); 32 | $this->assertEquals($hf->export(), array('content' => 'content')); 33 | $hf = new GripControl\HttpStreamFormat("\x04\x00\xa0\x00"); 34 | $this->assertEquals($hf->export(), array('content-bin' => base64_encode("\x04\x00\xa0\x00"))); 35 | $hf = new GripControl\HttpStreamFormat('content', true); 36 | $this->assertEquals($hf->export(), array('action' => 'close')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fanout/gripcontrol", 3 | "description": "A GRIP library for PHP.", 4 | "homepage": "https://github.com/fanout/php-gripcontrol", 5 | "authors": [ 6 | { 7 | "name": "Konstantin Bokarius", 8 | "email": "kon@fanout.io", 9 | "role": "Developer" 10 | } 11 | ], 12 | "license": "MIT", 13 | "require": { 14 | "php": ">=5.3.0", 15 | "firebase/php-jwt": "~4.0", 16 | "fanout/pubcontrol": "^2.0.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "3.7.14" 20 | }, 21 | "autoload": { 22 | "files": ["src/encoding.php", 23 | "src/websocketmessageformat.php", 24 | "src/websocketevent.php", 25 | "src/httpresponseformat.php", 26 | "src/httpstreamformat.php", 27 | "src/response.php", 28 | "src/channel.php", 29 | "src/grippubcontrol.php", 30 | "src/gripcontrol.php"] 31 | }, 32 | "target-dir": "fanout/php-gripcontrol", 33 | "minimum-stability": "dev", 34 | "scripts": { 35 | "check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests", 36 | "fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src tests" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/httpresponseformat_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($hf->code, null); 9 | $this->assertEquals($hf->reason, null); 10 | $this->assertEquals($hf->headers, null); 11 | $this->assertEquals($hf->body, null); 12 | $hf = new GripControl\HttpResponseFormat('code', 'reason', 'headers', 'body'); 13 | $this->assertEquals($hf->code, 'code'); 14 | $this->assertEquals($hf->reason, 'reason'); 15 | $this->assertEquals($hf->headers, 'headers'); 16 | $this->assertEquals($hf->body, 'body'); 17 | } 18 | 19 | public function testName() 20 | { 21 | $hf = new GripControl\HttpResponseFormat(); 22 | $this->assertEquals($hf->name(), 'http-response'); 23 | } 24 | 25 | public function testExport() 26 | { 27 | $hf = new GripControl\HttpResponseFormat('code', 'reason', 'headers', 'body'); 28 | $this->assertEquals($hf->export(), array( 29 | 'code' => 'code', 30 | 'reason' => 'reason', 31 | 'headers' => 'headers', 32 | 'body' => 'body' 33 | )); 34 | $hf = new GripControl\HttpResponseFormat(null, null, null, "\x04\x00\xa0\x00"); 35 | $this->assertEquals($hf->export(), array('body-bin' => base64_encode("\x04\x00\xa0\x00"))); 36 | $hf = new GripControl\HttpResponseFormat(); 37 | $this->assertEquals($hf->export(), array()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/httpstreamformat.php: -------------------------------------------------------------------------------- 1 | content = $content; 25 | $this->close = $close; 26 | if (!$this->close && is_null($this->content)) { 27 | throw new \RuntimeException('Content not set'); 28 | } 29 | } 30 | 31 | // The name used when publishing this format. 32 | public function name() 33 | { 34 | return 'http-stream'; 35 | } 36 | 37 | // Exports the message in the required format depending on whether the 38 | // message content is binary or not, or whether the connection should 39 | // be closed. 40 | public function export() 41 | { 42 | $out = array(); 43 | if ($this->close) { 44 | $out['action'] = 'close'; 45 | } else { 46 | if (Encoding::is_binary_data($this->content)) { 47 | $out['content-bin'] = base64_encode($this->content); 48 | } else { 49 | $out['content'] = $this->content; 50 | } 51 | } 52 | return $out; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/httpresponseformat.php: -------------------------------------------------------------------------------- 1 | code = $code; 26 | $this->reason = $reason; 27 | $this->headers = $headers; 28 | $this->body = $body; 29 | } 30 | 31 | // The name used when publishing this format. 32 | public function name() 33 | { 34 | return 'http-response'; 35 | } 36 | 37 | // Export the message into the required format and include only the fields 38 | // that are set. The body is exported as base64 if the text is encoded as 39 | // binary. 40 | public function export() 41 | { 42 | $out = array(); 43 | if (!is_null($this->code)) { 44 | $out['code'] = $this->code; 45 | } 46 | if (!is_null($this->reason)) { 47 | $out['reason'] = $this->reason; 48 | } 49 | if (!is_null($this->headers)) { 50 | $out['headers'] = $this->headers; 51 | } 52 | if (!is_null($this->body)) { 53 | if (Encoding::is_binary_data($this->body)) { 54 | $out['body-bin'] = base64_encode($this->body); 55 | } else { 56 | $out['body'] = $this->body; 57 | } 58 | } 59 | return $out; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/grippubcontrol.php: -------------------------------------------------------------------------------- 1 | clients = array(); 25 | $this->pcccbhandlers = array(); 26 | if (!is_null($config)) { 27 | $this->apply_grip_config($config); 28 | } 29 | } 30 | 31 | // Apply the specified configuration to this GripPubControl instance. The 32 | // configuration object can either be a hash or an array of hashes where 33 | // each hash corresponds to a single PubControlClient instance. Each hash 34 | // will be parsed and a PubControlClient will be created either using just 35 | // a URI or a URI and JWT authentication information. 36 | public function apply_grip_config($config) 37 | { 38 | if (!is_array(reset($config))) { 39 | $config = array($config); 40 | } 41 | foreach ($config as $entry) { 42 | if (!array_key_exists('control_uri', $entry)) { 43 | continue; 44 | } 45 | $pub = new \PubControl\PubControlClient($entry['control_uri']); 46 | if (array_key_exists('control_iss', $entry)) { 47 | $pub->set_auth_jwt(array('iss' => $entry['control_iss']), $entry['key']); 48 | } 49 | $this->clients[] = $pub; 50 | } 51 | } 52 | 53 | // Synchronously publish an HTTP response format message to all of the 54 | // configured PubControlClients with a specified channel, message, and 55 | // optional ID and previous ID. Note that the 'http_response' parameter can 56 | // be provided as either an HttpResponseFormat instance or a string (in which 57 | // case an HttpResponseFormat instance will automatically be created and 58 | // have the 'body' field set to the specified string). 59 | public function publish_http_response($channel, $http_response, $id = null, $prev_id = null) 60 | { 61 | if (is_string($http_response)) { 62 | $http_response = new HttpResponseFormat(null, null, null, $http_response); 63 | } 64 | $item = new \PubControl\Item($http_response, $id, $prev_id); 65 | parent::publish($channel, $item); 66 | } 67 | 68 | // Asynchronously publish an HTTP response format message to all of the 69 | // configured PubControlClients with a specified channel, message, and 70 | // optional ID, previous ID, and callback. Note that the 'http_response' 71 | // parameter can be provided as either an HttpResponseFormat instance or 72 | // a string (in which case an HttpResponseFormat instance will automatically 73 | // be created and have the 'body' field set to the specified string). When 74 | // specified, the callback method will be called after publishing is complete 75 | // and passed a result and error message (if an error was encountered). 76 | public function publish_http_response_async($channel, $http_response, $id = null, $prev_id = null, $callback = null) 77 | { 78 | if (is_string($http_response)) { 79 | $http_response = new HttpResponseFormat(null, null, null, $http_response); 80 | } 81 | $item = new \PubControl\Item($http_response, $id, $prev_id); 82 | parent::publish_async($channel, $item, $callback); 83 | } 84 | 85 | // Synchronously publish an HTTP stream format message to all of the 86 | // configured PubControlClients with a specified channel, message, and 87 | // optional ID and previous ID. Note that the 'http_stream' parameter can 88 | // be provided as either an HttpStreamFormat instance or a string (in which 89 | // case an HttStreamFormat instance will automatically be created and 90 | // have the 'content' field set to the specified string). 91 | public function publish_http_stream($channel, $http_stream, $id = null, $prev_id = null) 92 | { 93 | if (is_string($http_stream)) { 94 | $http_stream = new HttpStreamFormat($http_stream); 95 | } 96 | $item = new \PubControl\Item($http_stream, $id, $prev_id); 97 | parent::publish($channel, $item); 98 | } 99 | 100 | // Asynchronously publish an HTTP stream format message to all of the 101 | // configured PubControlClients with a specified channel, message, and 102 | // optional ID, previous ID, and callback. Note that the 'http_stream' 103 | // parameter can be provided as either an HttpStreamFormat instance or 104 | // a string (in which case an HttpStreamFormat instance will automatically 105 | // be created and have the 'content' field set to the specified string). When 106 | // specified, the callback method will be called after publishing is complete 107 | // and passed a result and error message (if an error was encountered). 108 | public function publish_http_stream_async($channel, $http_stream, $id = null, $prev_id = null, $callback = null) 109 | { 110 | if (is_string($http_stream)) { 111 | $http_stream = new HttpStreamFormat($http_stream); 112 | } 113 | $item = new \PubControl\Item($http_stream, $id, $prev_id); 114 | parent::publish_async($channel, $item, $callback); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | php-gripcontrol 2 | ================ 3 | 4 | Author: Konstantin Bokarius 5 | 6 | A GRIP library for PHP. 7 | 8 | License 9 | ------- 10 | 11 | php-gripcontrol is offered under the MIT license. See the LICENSE file. 12 | 13 | Requirements 14 | ------------ 15 | 16 | * openssl 17 | * curl 18 | * pthreads (required for asynchronous publishing) 19 | * firebase/php-jwt >=1.0.0 (retreived automatically via Composer) 20 | * fanout/php-pubcontrol >=1.0.6 (retreived automatically via Composer) 21 | 22 | Installation 23 | ------------ 24 | 25 | Using Composer: 'composer require fanout/gripcontrol' 26 | 27 | Manual: ensure that php-jwt and php-pubcontrol have been included and require the following files in php-gripcontrol: 28 | 29 | ```PHP 30 | require 'php-gripcontrol/src/encoding.php'; 31 | require 'php-gripcontrol/src/channel.php'; 32 | require 'php-gripcontrol/src/response.php'; 33 | require 'php-gripcontrol/src/websocketevent.php'; 34 | require 'php-gripcontrol/src/websocketmessageformat.php'; 35 | require 'php-gripcontrol/src/httpstreamformat.php'; 36 | require 'php-gripcontrol/src/httpresponseformat.php'; 37 | require 'php-gripcontrol/src/grippubcontrol.php'; 38 | require 'php-gripcontrol/src/gripcontrol.php'; 39 | ``` 40 | 41 | Asynchronous Publishing 42 | ----------------------- 43 | 44 | In order to make asynchronous publish calls pthreads must be installed. If pthreads is not installed then only synchronous publish calls can be made. To install pthreads recompile PHP with the following flag: '--enable-maintainer-zts' 45 | 46 | Also note that since a callback passed to the publish_async methods is going to be executed in a separate thread, that callback and the class it belongs to are subject to the rules and limitations imposed by the pthreads extension. 47 | 48 | See more information about pthreads here: http://php.net/manual/en/book.pthreads.php 49 | 50 | Usage 51 | ----- 52 | 53 | Examples for how to publish HTTP response and HTTP stream messages to GRIP proxy endpoints via the GripPubControl class. 54 | 55 | ```PHP 56 | 'https://api.fanout.io/realm/', 72 | 'control_iss' => '', 73 | 'key' => Base64.decode64(''))); 74 | 75 | // Add new endpoints by applying an endpoint configuration: 76 | $grippub->apply_grip_config(array( 77 | array('control_uri' => ''), 78 | array('control_uri' => ''))); 79 | 80 | // Remove all configured endpoints: 81 | $grippub->remove_all_clients(); 82 | 83 | // Explicitly add an endpoint as a PubControlClient instance: 84 | $pubclient = new PubControl\PubControlClient(''); 85 | // Optionally set JWT auth: $pubclient->set_auth_jwt(, '') 86 | // Optionally set basic auth: $pubclient->set_auth_basic('', '') 87 | $grippub->add_client($pubclient); 88 | 89 | // Publish across all configured endpoints: 90 | $grippub->publish_http_response('', 'Test publish!'); 91 | $grippub->publish_http_stream('', 'Test publish!'); 92 | 93 | // Use publish_async for async publishing only if pthreads are installed: 94 | // $grippub->publish_http_response_async('', 'Test async publish!', 95 | // null, null, 'callback'); 96 | // $grippub->publish_http_stream_async('', 'Test async publish!', 97 | // null, null, 'callback'); 98 | // Wait for all async publish calls to complete: 99 | // $grippub->finish(); 100 | ?> 101 | ``` 102 | 103 | Validate the Grip-Sig request header from incoming GRIP messages. This ensures that the message was sent from a valid source and is not expired. Note that when using Fanout.io the key is the realm key, and when using Pushpin the key is configurable in Pushpin's settings. 104 | 105 | ```PHP 106 | '); 108 | ?> 109 | ``` 110 | 111 | Long polling example via response _headers_. The client connects to a GRIP proxy over HTTP and the proxy forwards the request to the origin. The origin subscribes the client to a channel and instructs it to long poll via the response _headers_. Note that with the recent versions of Apache it's not possible to send a 304 response containing custom headers, in which case the response body should be used instead (next usage example below). 112 | 113 | ```PHP 114 | ')) 118 | return; 119 | 120 | // Instruct the client to long poll via the response headers: 121 | http_response_code(200); 122 | header('Grip-Hold: response'); 123 | header('Grip-Channel: ' . 124 | GripControl\GripControl::create_grip_channel_header('')); 125 | // To optionally set a timeout value in seconds: 126 | // header('Grip-Timeout: '); 127 | ?> 128 | ``` 129 | 130 | Long polling example via response _body_. The client connects to a GRIP proxy over HTTP and the proxy forwards the request to the origin. The origin subscribes the client to a channel and instructs it to long poll via the response _body_. 131 | 132 | ```PHP 133 | ')) 137 | return; 138 | 139 | // Instruct the client to long poll via the response body: 140 | http_response_code(200); 141 | header('Content-Type: application/grip-instruct'); 142 | echo GripControl\GripControl::create_hold_response(''); 143 | // Or to optionally set a timeout value in seconds: 144 | // echo GripControl\GripControl::create_hold_response( 145 | // '', null, ); 146 | ?> 147 | ``` 148 | 149 | WebSocket over HTTP example. In this case, a client connects to a GRIP proxy via WebSockets and the GRIP proxy communicates with the origin via HTTP. 150 | 151 | ```PHP 152 | '')); 162 | $grippub->publish('', new PubControl\Item( 163 | new GripControl\WebSocketMessageFormat( 164 | 'Test WebSocket publish!!'))); 165 | } 166 | } 167 | 168 | // Validate the Grip-Sig header: 169 | $request_headers = getallheaders(); 170 | if (!GripControl\GripControl::validate_sig($request_headers['Grip-Sig'], '')) 171 | return; 172 | 173 | // Set the headers required by the GRIP proxy: 174 | header('Content-Type: application/websocket-events'); 175 | header('Sec-WebSocket-Extensions: grip; message-prefix=""'); 176 | http_response_code(200); 177 | $in_events = GripControl\GripControl::decode_websocket_events( 178 | file_get_contents("php://input")); 179 | if ($in_events[0]->type == 'OPEN') 180 | { 181 | // Open the WebSocket and subscribe it to a channel: 182 | $out_events = array(); 183 | $out_events[] = new GripControl\WebSocketEvent('OPEN'); 184 | $out_events[] = new GripControl\WebSocketEvent('TEXT', 'c:' . 185 | GripControl\GripControl::websocket_control_message('subscribe', 186 | array('channel' => ''))); 187 | $response = GripControl\GripControl::encode_websocket_events($out_events); 188 | ignore_user_abort(true); 189 | header("Connection: close"); 190 | header("Content-Length: " . strlen($response)); 191 | echo $response; 192 | ob_flush(); 193 | flush(); 194 | $publish_message = new PublishMessage(); 195 | $publish_message->start(); 196 | } 197 | ?> 198 | ``` 199 | 200 | Parse a GRIP URI to extract the URI, ISS, and key values. The values will be returned in a hash containing 'control_uri', 'control_iss', and 'key' keys. 201 | 202 | ```PHP 203 | ?iss=' . 206 | '&key=base64:'); 207 | ?> 208 | ``` 209 | -------------------------------------------------------------------------------- /src/gripcontrol.php: -------------------------------------------------------------------------------- 1 | $control_uri); 85 | if (!is_null($iss)) { 86 | $out['control_iss'] = $iss; 87 | } 88 | if (!is_null($key)) { 89 | $out['key'] = $key; 90 | } 91 | return $out; 92 | } 93 | 94 | // Validate the specified JWT token and key. This method is used to validate 95 | // the GRIP-SIG header coming from GRIP proxies such as Pushpin or Fanout.io. 96 | // Note that the token expiration is also verified. 97 | public static function validate_sig($token, $key) 98 | { 99 | try { 100 | \Firebase\JWT\JWT::decode($token, $key, array('HS256', 'HS384', 'HS512', 'RS256')); 101 | return true; 102 | } catch (\Exception $e) { 103 | return false; 104 | } 105 | } 106 | 107 | // Create a GRIP channel header for the specified channels. The channels 108 | // parameter can be specified as a string representing the channel name, 109 | // a Channel instance, or an array of Channel instances. The returned GRIP 110 | // channel header is used when sending instructions to GRIP proxies via 111 | // HTTP headers. 112 | public static function create_grip_channel_header($channels) 113 | { 114 | $channels = self::parse_channels($channels); 115 | $parts = array(); 116 | foreach ($channels as $channel) { 117 | $s = $channel->name; 118 | if (!is_null($channel->prev_id)) { 119 | $s .= "; prev-id={$channel->prev_id}"; 120 | } 121 | $parts[] = $s; 122 | } 123 | return implode(', ', $parts); 124 | } 125 | 126 | // A convenience method for creating GRIP hold response instructions for HTTP 127 | // long-polling. This method simply passes the specified parameters to the 128 | // create_hold method with 'response' as the hold mode. 129 | public static function create_hold_response($channels, $response = null, $timeout = null) 130 | { 131 | return self::create_hold('response', $channels, $response, $timeout); 132 | } 133 | 134 | // A convenience method for creating GRIP hold stream instructions for HTTP 135 | // streaming. This method simply passes the specified parameters to the 136 | // create_hold method with 'stream' as the hold mode. 137 | public static function create_hold_stream($channels, $response = null) 138 | { 139 | return self::create_hold('stream', $channels, $response); 140 | } 141 | 142 | // Decode the specified HTTP request body into an array of WebSocketEvent 143 | // instances when using the WebSocket-over-HTTP protocol. A RuntimeError 144 | // is raised if the format is invalid. 145 | public static function decode_websocket_events($body) 146 | { 147 | $out = array(); 148 | $start = 0; 149 | while ($start < strlen($body)) { 150 | $at = strpos($body, "\r\n", $start); 151 | if ($at === false) { 152 | throw new \RuntimeException('bad format'); 153 | } 154 | $typeline = substr($body, $start, $at - $start); 155 | $start = $at + 2; 156 | $at = strpos($typeline, ' '); 157 | $e = null; 158 | if (!($at === false)) { 159 | $etype = substr($typeline, 0, $at); 160 | $clen = intval('0x' . substr($typeline, $at + 1), 16); 161 | $content = substr($body, $start, $clen); 162 | $start += $clen + 2; 163 | $e = new WebSocketEvent($etype, $content); 164 | } else { 165 | $e = new WebSocketEvent($typeline); 166 | } 167 | $out[] = $e; 168 | } 169 | return $out; 170 | } 171 | 172 | // Encode the specified array of WebSocketEvent instances. The returned string 173 | // value should then be passed to a GRIP proxy in the body of an HTTP response 174 | // when using the WebSocket-over-HTTP protocol. 175 | public static function encode_websocket_events($events) 176 | { 177 | $out = ''; 178 | foreach ($events as $event) { 179 | if (!is_null($event->content)) { 180 | $content_length = dechex(strlen($event->content)); 181 | $out .= "{$event->type} {$content_length}\r\n" . 182 | "{$event->content}\r\n"; 183 | } else { 184 | $out .= "{$event->type}\r\n"; 185 | } 186 | } 187 | return $out; 188 | } 189 | 190 | // Generate a WebSocket control message with the specified type and optional 191 | // arguments. WebSocket control messages are passed to GRIP proxies and 192 | // example usage includes subscribing/unsubscribing a WebSocket connection 193 | // to/from a channel. 194 | public static function websocket_control_message($type, $args = null) 195 | { 196 | $out = array(); 197 | if (!is_null($args)) { 198 | $out = $args; 199 | } 200 | $out['type'] = $type; 201 | return json_encode($out); 202 | } 203 | 204 | // An internal method used to parse the specified parameter into an array 205 | // of Channel instances. The specified parameter can either be a string, a 206 | // Channel instance, or an array of Channel instances. 207 | protected static function parse_channels($channels) 208 | { 209 | if ($channels instanceof Channel) { 210 | $channels = array($channels); 211 | } elseif (is_string($channels)) { 212 | $channels = array(new Channel($channels)); 213 | } 214 | if (count($channels) == 0) { 215 | throw new \RuntimeException('channels length is 0'); 216 | } 217 | return $channels; 218 | } 219 | 220 | // An internal method for getting an array of hashes representing the 221 | // specified channels parameter. The resulting array is used for creating 222 | // GRIP proxy hold instructions. 223 | protected static function get_hold_channels($channels) 224 | { 225 | $ichannels = array(); 226 | foreach ($channels as $channel) { 227 | if (is_string($channel)) { 228 | $channel = new Channel($channel); 229 | } 230 | $ichannel = array(); 231 | $ichannel['name'] = $channel->name; 232 | if (!is_null($channel->prev_id)) { 233 | $ichannel['prev-id'] = $channel->prev_id; 234 | } 235 | $ichannels[] = $ichannel; 236 | } 237 | return $ichannels; 238 | } 239 | 240 | // An internal method for getting a hash representing the specified 241 | // response parameter. The resulting hash is used for creating GRIP 242 | // proxy hold instructions. 243 | protected static function get_hold_response($response) 244 | { 245 | $iresponse = null; 246 | if (!is_null($response)) { 247 | if (is_string($response)) { 248 | $response = new Response(null, null, null, $response); 249 | } 250 | $iresponse = array(); 251 | if (!is_null($response->code)) { 252 | $iresponse['code'] = $response->code; 253 | } 254 | if (!is_null($response->reason)) { 255 | $iresponse['reason'] = $response->reason; 256 | } 257 | if (!is_null($response->headers) && count($response->headers) > 0) { 258 | $iresponse['headers'] = $response->headers; 259 | } 260 | if (!is_null($response->body)) { 261 | if (Encoding::is_binary_data($response->body)) { 262 | $iresponse['body-bin'] = base64_encode($response->body); 263 | } else { 264 | $iresponse['body'] = $response->body; 265 | } 266 | } 267 | } 268 | return $iresponse; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /tests/grippubcontrol_tests.php: -------------------------------------------------------------------------------- 1 | clients; 8 | } 9 | 10 | public function getPcccbHandlers() 11 | { 12 | return $this->pcccbhandlers; 13 | } 14 | } 15 | 16 | class PubControlClientTestClass 17 | { 18 | public $was_finish_called = false; 19 | public $was_publish_called = false; 20 | public $publish_channel = false; 21 | public $publish_item = false; 22 | 23 | public function finish() 24 | { 25 | $this->was_finish_called = true; 26 | } 27 | 28 | public function publish($channel, $item) 29 | { 30 | $this->was_publish_called = true; 31 | $this->publish_channel = $channel; 32 | $this->publish_item = $item; 33 | } 34 | } 35 | 36 | class PubControlClientAsyncTestClass 37 | { 38 | public $publish_channel = null; 39 | public $publish_item = null; 40 | public $publish_cb = null; 41 | 42 | public function publish_async($channel, $item, $callback = null) 43 | { 44 | $this->publish_channel = $channel; 45 | $this->publish_item = $item; 46 | $this->publish_cb = $callback; 47 | } 48 | } 49 | 50 | class GripPubControlTestClassNoAsync extends GripControl\GripPubControl 51 | { 52 | public function is_async_supported() 53 | { 54 | return false; 55 | } 56 | } 57 | 58 | class CallbackTestClass extends Stackable 59 | { 60 | public $was_callback_called = false; 61 | public $result = null; 62 | public $message = null; 63 | 64 | public function callback($result, $message) 65 | { 66 | $this->result = $result; 67 | $this->message = $message; 68 | $this->was_callback_called = true; 69 | } 70 | 71 | public function run() 72 | { 73 | } 74 | } 75 | 76 | class TestGripPubControl extends PHPUnit_Framework_TestCase 77 | { 78 | public function testInitialize() 79 | { 80 | $pc = new GripPubControlTestClass(); 81 | $this->assertEquals(count($pc->getClients()), 0); 82 | $this->assertEquals(count($pc->getPcccbHandlers()), 0); 83 | $pc = new GripPubControlTestClass(array( 84 | array( 85 | 'control_uri' => 'uri', 86 | 'control_iss' => 'iss', 87 | 'key' => 'key==' 88 | ), 89 | array( 90 | 'control_uri' => 'uri2', 91 | 'control_iss' => 'iss2', 92 | 'key' => 'key==2' 93 | ) 94 | )); 95 | $this->assertEquals(count($pc->getClients()), 2); 96 | $this->assertEquals(count($pc->getPcccbHandlers()), 0); 97 | $this->assertEquals($pc->getClients()[0]->uri, 'uri'); 98 | $this->assertEquals($pc->getClients()[0]->auth_jwt_claim, array('iss' => 'iss')); 99 | $this->assertEquals($pc->getClients()[0]->auth_jwt_key, 'key=='); 100 | $this->assertEquals($pc->getClients()[1]->uri, 'uri2'); 101 | $this->assertEquals($pc->getClients()[1]->auth_jwt_claim, array('iss' => 'iss2')); 102 | $this->assertEquals($pc->getClients()[1]->auth_jwt_key, 'key==2'); 103 | } 104 | 105 | public function testGripApplyConfig() 106 | { 107 | $pc = new GripPubControlTestClass(); 108 | $pc->apply_grip_config(array( 109 | array( 110 | 'control_uri' => 'uri', 111 | 'control_iss' => 'iss', 112 | 'key' => 'key==' 113 | ), 114 | array( 115 | 'control_uri' => 'uri2', 116 | 'control_iss' => 'iss2', 117 | 'key' => 'key==2' 118 | ) 119 | )); 120 | $this->assertEquals(count($pc->getClients()), 2); 121 | $this->assertEquals($pc->getClients()[0]->uri, 'uri'); 122 | $this->assertEquals($pc->getClients()[0]->auth_jwt_claim, array('iss' => 'iss')); 123 | $this->assertEquals($pc->getClients()[0]->auth_jwt_key, 'key=='); 124 | $this->assertEquals($pc->getClients()[1]->uri, 'uri2'); 125 | $this->assertEquals($pc->getClients()[1]->auth_jwt_claim, array('iss' => 'iss2')); 126 | $this->assertEquals($pc->getClients()[1]->auth_jwt_key, 'key==2'); 127 | $pc->apply_grip_config(array( 128 | 'control_uri' => 'uri3', 129 | 'control_iss' => 'iss3', 130 | 'key' => 'key==3' 131 | )); 132 | $this->assertEquals(count($pc->getClients()), 3); 133 | $this->assertEquals($pc->getClients()[2]->uri, 'uri3'); 134 | $this->assertEquals($pc->getClients()[2]->auth_jwt_claim, array('iss' => 'iss3')); 135 | $this->assertEquals($pc->getClients()[2]->auth_jwt_key, 'key==3'); 136 | } 137 | 138 | public function testPublishHttpResponse1() 139 | { 140 | $pc = new GripControl\GripPubControl(); 141 | $pcc1 = new PubControlClientTestClass(); 142 | $pcc2 = new PubControlClientTestClass(); 143 | $pc->add_client($pcc1); 144 | $pc->add_client($pcc2); 145 | $pc->publish_http_response('chan', 'data'); 146 | $this->assertTrue($pcc1->was_publish_called); 147 | $this->assertEquals($pcc1->publish_channel, 'chan'); 148 | $this->assertEquals( 149 | $pcc1->publish_item->export(), 150 | (new PubControl\Item(new GripControl\HttpResponseFormat( 151 | null, 152 | null, 153 | null, 154 | 'data' 155 | )))->export() 156 | ); 157 | } 158 | 159 | public function testPublishHttpResponse2() 160 | { 161 | $pc = new GripControl\GripPubControl(); 162 | $pcc1 = new PubControlClientTestClass(); 163 | $pcc2 = new PubControlClientTestClass(); 164 | $pc->add_client($pcc1); 165 | $pc->add_client($pcc2); 166 | $response = new GripControl\HttpResponseFormat('code', 'reason', 'headers', 'data'); 167 | $pc->publish_http_response('chan', $response, 'id', 'prev-id'); 168 | $this->assertTrue($pcc1->was_publish_called); 169 | $this->assertEquals($pcc1->publish_channel, 'chan'); 170 | $this->assertEquals($pcc1->publish_item->export(), (new PubControl\Item($response, 'id', 'prev-id'))->export()); 171 | } 172 | 173 | /** 174 | * @expectedException RuntimeException 175 | */ 176 | public function testPublishAsyncException() 177 | { 178 | $pc = new GripPubControlTestClassNoAsync(); 179 | $pc->publish_async('chan', 'item', 'callback'); 180 | } 181 | 182 | public function testPublishHttpResponsehAsync() 183 | { 184 | $pc = new GripControl\GripPubControl(); 185 | $callback = new CallbackTestClass(); 186 | $pcc1 = new PubControlClientAsyncTestClass('uri'); 187 | $pcc2 = new PubControlClientAsyncTestClass('uri'); 188 | $pcc3 = new PubControlClientAsyncTestClass('uri'); 189 | $pc->add_client($pcc1); 190 | $pc->add_client($pcc2); 191 | $pc->add_client($pcc3); 192 | $pc->publish_http_response_async('chan', 'item', null, null, array($callback, "callback")); 193 | $this->assertEquals($pcc1->publish_channel, 'chan'); 194 | $this->assertEquals( 195 | $pcc1->publish_item->export(), 196 | (new PubControl\Item(new GripControl\HttpResponseFormat( 197 | null, 198 | null, 199 | null, 200 | 'item' 201 | )))->export() 202 | ); 203 | $this->assertEquals($pcc2->publish_channel, 'chan'); 204 | $this->assertEquals( 205 | $pcc2->publish_item->export(), 206 | (new PubControl\Item(new GripControl\HttpResponseFormat( 207 | null, 208 | null, 209 | null, 210 | 'item' 211 | )))->export() 212 | ); 213 | $this->assertEquals($pcc3->publish_channel, 'chan'); 214 | $this->assertEquals( 215 | $pcc3->publish_item->export(), 216 | (new PubControl\Item(new GripControl\HttpResponseFormat( 217 | null, 218 | null, 219 | null, 220 | 'item' 221 | )))->export() 222 | ); 223 | call_user_func($pcc1->publish_cb, false, 'message'); 224 | call_user_func($pcc2->publish_cb, false, 'message'); 225 | $this->assertTrue(is_null($callback->result)); 226 | call_user_func($pcc3->publish_cb, false, 'message'); 227 | $this->assertFalse($callback->result); 228 | $this->assertEquals($callback->message, 'message'); 229 | } 230 | 231 | public function testPublishHttpStream1() 232 | { 233 | $pc = new GripControl\GripPubControl(); 234 | $pcc1 = new PubControlClientTestClass(); 235 | $pcc2 = new PubControlClientTestClass(); 236 | $pc->add_client($pcc1); 237 | $pc->add_client($pcc2); 238 | $pc->publish_http_stream('chan', 'content'); 239 | $this->assertTrue($pcc1->was_publish_called); 240 | $this->assertEquals($pcc1->publish_channel, 'chan'); 241 | $this->assertEquals( 242 | $pcc1->publish_item->export(), 243 | (new PubControl\Item(new GripControl\HttpStreamFormat('content')))->export() 244 | ); 245 | } 246 | 247 | public function testPublishHttpStream2() 248 | { 249 | $pc = new GripControl\GripPubControl(); 250 | $pcc1 = new PubControlClientTestClass(); 251 | $pcc2 = new PubControlClientTestClass(); 252 | $pc->add_client($pcc1); 253 | $pc->add_client($pcc2); 254 | $stream = new GripControl\HttpStreamFormat('content', true); 255 | $pc->publish_http_stream('chan', $stream, 'id', 'prev-id'); 256 | $this->assertTrue($pcc1->was_publish_called); 257 | $this->assertEquals($pcc1->publish_channel, 'chan'); 258 | $this->assertEquals($pcc1->publish_item->export(), (new PubControl\Item($stream, 'id', 'prev-id'))->export()); 259 | } 260 | 261 | public function testPublishHttpStreamhAsync() 262 | { 263 | $pc = new GripControl\GripPubControl(); 264 | $callback = new CallbackTestClass(); 265 | $pcc1 = new PubControlClientAsyncTestClass('uri'); 266 | $pcc2 = new PubControlClientAsyncTestClass('uri'); 267 | $pcc3 = new PubControlClientAsyncTestClass('uri'); 268 | $pc->add_client($pcc1); 269 | $pc->add_client($pcc2); 270 | $pc->add_client($pcc3); 271 | $pc->publish_http_stream_async('chan', 'item', null, null, array($callback, "callback")); 272 | $this->assertEquals($pcc1->publish_channel, 'chan'); 273 | $this->assertEquals( 274 | $pcc1->publish_item->export(), 275 | (new PubControl\Item(new GripControl\HttpStreamFormat('item')))->export() 276 | ); 277 | $this->assertEquals($pcc2->publish_channel, 'chan'); 278 | $this->assertEquals( 279 | $pcc2->publish_item->export(), 280 | (new PubControl\Item(new GripControl\HttpStreamFormat('item')))->export() 281 | ); 282 | $this->assertEquals($pcc3->publish_channel, 'chan'); 283 | $this->assertEquals( 284 | $pcc3->publish_item->export(), 285 | (new PubControl\Item(new GripControl\HttpStreamFormat('item')))->export() 286 | ); 287 | call_user_func($pcc1->publish_cb, false, 'message'); 288 | call_user_func($pcc2->publish_cb, false, 'message'); 289 | $this->assertTrue(is_null($callback->result)); 290 | call_user_func($pcc3->publish_cb, false, 'message'); 291 | $this->assertFalse($callback->result); 292 | $this->assertEquals($callback->message, 'message'); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /tests/gripcontrol_tests.php: -------------------------------------------------------------------------------- 1 | assertEquals($hold->hold->mode, 'mode'); 39 | $this->assertEquals($hold->hold->channels[0]->name, 'channel'); 40 | $this->assertFalse(array_key_exists('timeout', $hold->hold)); 41 | $this->assertEquals($hold->response->code, 'code'); 42 | $this->assertEquals($hold->response->reason, 'reason'); 43 | $this->assertEquals($hold->response->headers, 'headers'); 44 | $this->assertEquals($hold->response->body, 'body'); 45 | $hold = json_decode(GripControl\GripControl::create_hold('mode', 'channel', 'body', 'timeout')); 46 | $this->assertEquals($hold->hold->mode, 'mode'); 47 | $this->assertEquals($hold->hold->timeout, 'timeout'); 48 | $this->assertEquals($hold->hold->channels[0]->name, 'channel'); 49 | $this->assertFalse(array_key_exists('code', $hold->response)); 50 | $this->assertFalse(array_key_exists('reason', $hold->response)); 51 | $this->assertFalse(array_key_exists('headers', $hold->response)); 52 | $this->assertEquals($hold->response->body, 'body'); 53 | } 54 | 55 | public function testParseGripUri() 56 | { 57 | $uri = 'http://api.fanout.io/realm/realm?iss=realm&key=base64:geag121321='; 58 | $config = GripControl\GripControl::parse_grip_uri($uri); 59 | $this->assertEquals($config['control_uri'], 'http://api.fanout.io/realm/realm'); 60 | $this->assertEquals($config['control_iss'], 'realm'); 61 | $this->assertEquals($config['key'], base64_decode('geag121321=')); 62 | $uri = 'https://api.fanout.io/realm/realm?iss=realm&key=base64:geag121321='; 63 | $config = GripControl\GripControl::parse_grip_uri($uri); 64 | $this->assertEquals($config['control_uri'], 'https://api.fanout.io/realm/realm'); 65 | $config = GripControl\GripControl::parse_grip_uri('http://api.fanout.io/realm/realm'); 66 | $this->assertEquals($config['control_uri'], 'http://api.fanout.io/realm/realm'); 67 | $this->assertEquals(array_key_exists('control_iss', $config), false); 68 | $this->assertEquals(array_key_exists('key', $config), false); 69 | $uri = 'http://api.fanout.io/realm/realm?iss=realm&key=base64:geag121321=¶m1=value1¶m2=value2'; 70 | $config = GripControl\GripControl::parse_grip_uri($uri); 71 | $this->assertEquals($config['control_uri'], 'http://api.fanout.io/realm/realm?param1=value1¶m2=value2'); 72 | $this->assertEquals($config['control_iss'], 'realm'); 73 | $this->assertEquals($config['key'], base64_decode('geag121321=')); 74 | $config = GripControl\GripControl::parse_grip_uri('http://api.fanout.io:8080/realm/realm/'); 75 | $this->assertEquals($config['control_uri'], 'http://api.fanout.io:8080/realm/realm'); 76 | $uri = 'http://api.fanout.io/realm/realm?iss=realm&key=geag121321='; 77 | $config = GripControl\GripControl::parse_grip_uri($uri); 78 | $this->assertEquals($config['key'], 'geag121321='); 79 | } 80 | 81 | public function testValidateSig() 82 | { 83 | $token = \Firebase\JWT\JWT::encode(array('iss' => 'realm', 'exp' => time() + 3600), 'key'); 84 | assert(GripControl\GripControl::validate_sig($token, 'key')); 85 | $token = \Firebase\JWT\JWT::encode(array('iss' => 'realm', 'exp' => time() - 3600), 'key'); 86 | $this->assertEquals(GripControl\GripControl::validate_sig($token, 'key'), false); 87 | $token = \Firebase\JWT\JWT::encode(array('iss' => 'realm', 'exp' => time() + 3600), 'key'); 88 | $this->assertEquals(GripControl\GripControl::validate_sig($token, 'wrong_key'), false); 89 | } 90 | 91 | /** 92 | * @expectedException RuntimeException 93 | */ 94 | public function testCreateGripChannelHeaderException() 95 | { 96 | GripControl\GripControl::create_grip_channel_header(array()); 97 | } 98 | 99 | public function testCreateGripChannelHeader() 100 | { 101 | $header = GripControl\GripControl::create_grip_channel_header('channel'); 102 | $this->assertEquals($header, 'channel'); 103 | $header = GripControl\GripControl::create_grip_channel_header(new GripControl\Channel('channel')); 104 | $this->assertEquals($header, 'channel'); 105 | $header = GripControl\GripControl::create_grip_channel_header(new GripControl\Channel('channel', 'prev-id')); 106 | $this->assertEquals($header, 'channel; prev-id=prev-id'); 107 | $header = GripControl\GripControl::create_grip_channel_header(array( 108 | new GripControl\Channel('channel1', 'prev-id1'), new GripControl\Channel('channel2', 'prev-id2') 109 | )); 110 | $this->assertEquals($header, 'channel1; prev-id=prev-id1, channel2; prev-id=prev-id2'); 111 | } 112 | 113 | public function testCreateHoldResponse() 114 | { 115 | $hold = json_decode(GripControl\GripControl::create_hold_response( 116 | 'channel', 117 | new GripControl\Response('code', 'reason', 'headers', 'body') 118 | ), true); 119 | $this->assertEquals($hold['hold']['mode'], 'response'); 120 | $this->assertEquals(array_key_exists('timeout', $hold['hold']), false); 121 | $this->assertEquals( 122 | $hold['hold']['channels'], 123 | array(array('name' => 'channel')) 124 | ); 125 | $this->assertEquals($hold['response'], array( 126 | 'code' => 'code', 127 | 'reason' => 'reason', 128 | 'headers' => 'headers', 129 | 'body' => 'body' 130 | )); 131 | $hold = json_decode(GripControl\GripControl::create_hold_response('channel', null, 'timeout'), true); 132 | $this->assertFalse(array_key_exists('response', $hold)); 133 | $this->assertEquals($hold['hold']['mode'], 'response'); 134 | $this->assertEquals($hold['hold']['timeout'], 'timeout'); 135 | } 136 | 137 | public function testCreateHoldStream() 138 | { 139 | $hold = json_decode(GripControl\GripControl::create_hold_stream('channel', new GripControl\Response( 140 | 'code', 141 | 'reason', 142 | 'headers', 143 | 'body' 144 | )), true); 145 | $this->assertEquals($hold['hold']['mode'], 'stream'); 146 | $this->assertEquals(array_key_exists('timeout', $hold['hold']), false); 147 | $this->assertEquals($hold['hold']['channels'], array(array('name' => 'channel'))); 148 | $this->assertEquals($hold['response'], array( 149 | 'code' => 'code', 150 | 'reason' => 'reason', 151 | 'headers' => 'headers', 152 | 'body' => 'body' 153 | )); 154 | $hold = json_decode(GripControl\GripControl::create_hold_stream('channel', null), true); 155 | $this->assertFalse(array_key_exists('response', $hold)); 156 | $this->assertEquals($hold['hold']['mode'], 'stream'); 157 | } 158 | 159 | /** 160 | * @expectedException RuntimeException 161 | */ 162 | public function testDecodeWebSocketEventsException1() 163 | { 164 | GripControl\GripControl::decode_websocket_events("TEXT 5"); 165 | } 166 | 167 | /** 168 | * @expectedException RuntimeException 169 | */ 170 | public function testDecodeWebSocketEventsException2() 171 | { 172 | GripControl\GripControl::decode_websocket_events("OPEN\r\nTEXT"); 173 | } 174 | 175 | public function testDecodeWebSocketEvents() 176 | { 177 | $events = GripControl\GripControl::decode_websocket_events( 178 | "OPEN\r\nTEXT 5\r\nHello\r\nTEXT 0\r\n\r\nCLOSE\r\nTEXT\r\nCLOSE\r\n" 179 | ); 180 | $this->assertEquals(count($events), 6); 181 | $this->assertEquals($events[0]->type, 'OPEN'); 182 | $this->assertEquals($events[0]->content, null); 183 | $this->assertEquals($events[1]->type, 'TEXT'); 184 | $this->assertEquals($events[1]->content, 'Hello'); 185 | $this->assertEquals($events[2]->type, 'TEXT'); 186 | $this->assertEquals($events[2]->content, ''); 187 | $this->assertEquals($events[3]->type, 'CLOSE'); 188 | $this->assertEquals($events[3]->content, null); 189 | $this->assertEquals($events[4]->type, 'TEXT'); 190 | $this->assertEquals($events[4]->content, null); 191 | $this->assertEquals($events[5]->type, 'CLOSE'); 192 | $this->assertEquals($events[5]->content, null); 193 | $events = GripControl\GripControl::decode_websocket_events("OPEN\r\n"); 194 | $this->assertEquals(count($events), 1); 195 | $this->assertEquals($events[0]->type, 'OPEN'); 196 | $this->assertEquals($events[0]->content, null); 197 | $events = GripControl\GripControl::decode_websocket_events("TEXT 5\r\nHello\r\n"); 198 | $this->assertEquals(count($events), 1); 199 | $this->assertEquals($events[0]->type, 'TEXT'); 200 | $this->assertEquals($events[0]->content, 'Hello'); 201 | } 202 | 203 | public function testEncodeWebSocketEvents() 204 | { 205 | $events = GripControl\GripControl::encode_websocket_events(array( 206 | new GripControl\WebSocketEvent("TEXT", "Hello"), 207 | new GripControl\WebSocketEvent("TEXT", ""), 208 | new GripControl\WebSocketEvent("TEXT", null) 209 | )); 210 | $this->assertEquals($events, "TEXT 5\r\nHello\r\nTEXT 0\r\n\r\nTEXT\r\n"); 211 | $events = GripControl\GripControl::encode_websocket_events(array(new GripControl\WebSocketEvent("OPEN"))); 212 | $this->assertEquals($events, "OPEN\r\n"); 213 | } 214 | 215 | public function testWebSocketControlMessage() 216 | { 217 | $message = GripControl\GripControl::websocket_control_message('type'); 218 | $this->assertEquals($message, '{"type":"type"}'); 219 | $message = json_decode(GripControl\GripControl::websocket_control_message('type', array( 220 | 'arg1' => 'val1', 221 | 'arg2' => 'val2' 222 | )), true); 223 | $this->assertEquals($message['type'], 'type'); 224 | $this->assertEquals($message['arg1'], 'val1'); 225 | $this->assertEquals($message['arg2'], 'val2'); 226 | } 227 | 228 | /** 229 | * @expectedException RuntimeException 230 | */ 231 | public function testParseChannelsException() 232 | { 233 | GripControlTestClass::callParsechannels(array()); 234 | } 235 | 236 | public function testParseChannels() 237 | { 238 | $channels = GripControlTestClass::callParsechannels('channel'); 239 | $this->assertEquals($channels[0]->name, 'channel'); 240 | $this->assertEquals($channels[0]->prev_id, null); 241 | $channels = GripControlTestClass::callParsechannels(new GripControl\Channel('channel')); 242 | $this->assertEquals($channels[0]->name, 'channel'); 243 | $this->assertEquals($channels[0]->prev_id, null); 244 | $channels = GripControlTestClass::callParsechannels(new GripControl\Channel('channel', 'prev-id')); 245 | $this->assertEquals($channels[0]->name, 'channel'); 246 | $this->assertEquals($channels[0]->prev_id, 'prev-id'); 247 | $channels = GripControlTestClass::callParsechannels(array( 248 | new GripControl\Channel('channel1', 'prev-id'), 249 | new GripControl\Channel('channel2') 250 | )); 251 | $this->assertEquals($channels[0]->name, 'channel1'); 252 | $this->assertEquals($channels[0]->prev_id, 'prev-id'); 253 | $this->assertEquals($channels[1]->name, 'channel2'); 254 | $this->assertEquals($channels[1]->prev_id, null); 255 | } 256 | 257 | public function testGetHoldChannels() 258 | { 259 | $hold_channels = GripControlTestClass::callGetHoldChannels(array(new GripControl\Channel('channel'))); 260 | $this->assertEquals($hold_channels[0], array('name' => 'channel')); 261 | $hold_channels = GripControlTestClass::callGetHoldChannels(array( 262 | new GripControl\Channel('channel', 'prev-id') 263 | )); 264 | $this->assertEquals($hold_channels[0], array('name' => 'channel', 'prev-id' => 'prev-id')); 265 | $hold_channels = GripControlTestClass::callGetHoldChannels(array( 266 | new GripControl\Channel('channel1', 'prev-id1'), 267 | new GripControl\Channel('channel2', 'prev-id2') 268 | )); 269 | $this->assertEquals($hold_channels[0], array('name' => 'channel1', 'prev-id' => 'prev-id1')); 270 | $this->assertEquals($hold_channels[1], array('name' => 'channel2', 'prev-id' => 'prev-id2')); 271 | } 272 | 273 | public function testGetHoldResponse() 274 | { 275 | $response = GripControlTestClass::callGetHoldResponse(null); 276 | $this->assertEquals($response, null); 277 | $response = GripControlTestClass::callGetHoldResponse('body'); 278 | $this->assertEquals($response['body'], 'body'); 279 | $this->assertEquals(array_key_exists('code', $response), false); 280 | $this->assertEquals(array_key_exists('reason', $response), false); 281 | $this->assertEquals(array_key_exists('headers', $response), false); 282 | // Verify non-UTF8 data passed as the body is exported as content-bin 283 | $response = GripControlTestClass::callGetHoldResponse("\x04\x00\xa0\x00"); 284 | $this->assertEquals($response['body-bin'], base64_encode("\x04\x00\xa0\x00")); 285 | $response = GripControlTestClass::callGetHoldResponse(new GripControl\Response( 286 | 'code', 287 | 'reason', 288 | array('header1' => 'val1'), 289 | "body\u2713" 290 | )); 291 | $this->assertEquals($response['code'], 'code'); 292 | $this->assertEquals($response['reason'], 'reason'); 293 | $this->assertEquals($response['headers'], array('header1' => 'val1')); 294 | $this->assertEquals($response['body'], "body\u2713"); 295 | $response = GripControlTestClass::callGetHoldResponse(new GripControl\Response( 296 | null, 297 | null, 298 | array(), 299 | null 300 | )); 301 | $this->assertEquals(array_key_exists('headers', $response), false); 302 | $this->assertEquals(array_key_exists('body', $response), false); 303 | $this->assertEquals(array_key_exists('reason', $response), false); 304 | $this->assertEquals(array_key_exists('headers', $response), false); 305 | } 306 | } 307 | --------------------------------------------------------------------------------