├── .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 |
--------------------------------------------------------------------------------