├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── examples ├── index.html ├── sse-swoole.php └── sse.php ├── src ├── Event.php ├── SSE.php ├── SSESwoole.php └── StopSSEException.php └── sse.png /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea/ 3 | composer.lock 4 | examples/*.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP SSE: Server-sent Events 2 | ====== 3 | 4 | A simple and efficient library implemented HTML5's server-sent events by PHP, is used to real-time push events from server to client, and easier than 5 | Websocket, instead of AJAX request. 6 | 7 | ## Requirements 8 | 9 | * PHP 5.4 or later 10 | 11 | ## Installation via Composer([packagist](https://packagist.org/packages/hhxsv5/php-sse)) 12 | 13 | ```BASH 14 | composer require "hhxsv5/php-sse:~2.0" -vvv 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Run demo 20 | 21 | - Run PHP webserver 22 | 23 | ```Bash 24 | cd examples 25 | php -S 127.0.0.1:9001 -t . 26 | ``` 27 | 28 | - Open url `http://127.0.0.1:9001/index.html` 29 | 30 | ![Demo](https://raw.githubusercontent.com/hhxsv5/php-sse/master/sse.png) 31 | 32 | ### Javascript demo 33 | 34 | > Client: receiving events from the server. 35 | 36 | ```Javascript 37 | // withCredentials=true: pass the cross-domain cookies to server-side 38 | const source = new EventSource('http://127.0.0.1:9001/sse.php', {withCredentials: true}); 39 | source.addEventListener('news', function (event) { 40 | console.log(event.data); 41 | // source.close(); // disconnect stream 42 | }, false); 43 | ``` 44 | 45 | ### PHP demo 46 | 47 | > Server: Sending events by pure php. 48 | 49 | ```PHP 50 | use Hhxsv5\SSE\Event; 51 | use Hhxsv5\SSE\SSE; 52 | use Hhxsv5\SSE\StopSSEException; 53 | 54 | // PHP-FPM SSE Example: push messages to client 55 | 56 | header('Content-Type: text/event-stream'); 57 | header('Cache-Control: no-cache'); 58 | header('Connection: keep-alive'); 59 | header('X-Accel-Buffering: no'); // Nginx: unbuffered responses suitable for Comet and HTTP streaming applications 60 | 61 | $callback = function () { 62 | $id = mt_rand(1, 1000); 63 | $news = [['id' => $id, 'title' => 'title ' . $id, 'content' => 'content ' . $id]]; // Get news from database or service. 64 | if (empty($news)) { 65 | return false; // Return false if no new messages 66 | } 67 | $shouldStop = false; // Stop if something happens or to clear connection, browser will retry 68 | if ($shouldStop) { 69 | throw new StopSSEException(); 70 | } 71 | return json_encode(compact('news')); 72 | // return ['event' => 'ping', 'data' => 'ping data']; // Custom event temporarily: send ping event 73 | // return ['id' => uniqid(), 'data' => json_encode(compact('news'))]; // Custom event Id 74 | }; 75 | (new SSE(new Event($callback, 'news')))->start(); 76 | ``` 77 | 78 | ### Symfony and Laravel demo 79 | 80 | > Server: Sending events by Laravel or Symfony. 81 | 82 | ```PHP 83 | use Hhxsv5\SSE\SSE; 84 | use Hhxsv5\SSE\Event; 85 | use Hhxsv5\SSE\StopSSEException; 86 | 87 | // Action method in controller 88 | public function getNewsStream() 89 | { 90 | $response = new \Symfony\Component\HttpFoundation\StreamedResponse(); 91 | $response->headers->set('Content-Type', 'text/event-stream'); 92 | $response->headers->set('Cache-Control', 'no-cache'); 93 | $response->headers->set('Connection', 'keep-alive'); 94 | $response->headers->set('X-Accel-Buffering', 'no'); // Nginx: unbuffered responses suitable for Comet and HTTP streaming applications 95 | $response->setCallback(function () { 96 | $callback = function () { 97 | $id = mt_rand(1, 1000); 98 | $news = [['id' => $id, 'title' => 'title ' . $id, 'content' => 'content ' . $id]]; // Get news from database or service. 99 | if (empty($news)) { 100 | return false; // Return false if no new messages 101 | } 102 | $shouldStop = false; // Stop if something happens or to clear connection, browser will retry 103 | if ($shouldStop) { 104 | throw new StopSSEException(); 105 | } 106 | return json_encode(compact('news')); 107 | // return ['event' => 'ping', 'data' => 'ping data']; // Custom event temporarily: send ping event 108 | // return ['id' => uniqid(), 'data' => json_encode(compact('news'))]; // Custom event Id 109 | }; 110 | (new SSE(new Event($callback, 'news')))->start(); 111 | }); 112 | return $response; 113 | } 114 | ``` 115 | 116 | ### Swoole demo 117 | 118 | > Server: Sending events by Swoole Coroutine Http Server. 119 | > Install [Swoole](https://github.com/swoole/swoole-src) 4.5.x: `pecl install swoole`. 120 | 121 | ```php 122 | use Hhxsv5\SSE\Event; 123 | use Hhxsv5\SSE\SSESwoole; 124 | use Swoole\Http\Request; 125 | use Swoole\Http\Response; 126 | use Swoole\Http\Server; 127 | use Hhxsv5\SSE\StopSSEException; 128 | 129 | // Swoole SSE Example: push messages to client 130 | 131 | $server = new Server('0.0.0.0', 5200); 132 | $server->set([ 133 | 'enable_coroutine' => true, 134 | 'max_coroutine' => 10000, // worker_num*10000 135 | 'reactor_num' => swoole_cpu_num() * 2, 136 | 'worker_num' => swoole_cpu_num() * 2, 137 | 'max_request' => 100000, 138 | 'buffer_output_size' => 4 * 1024 * 1024, // 4MB 139 | 'log_level' => SWOOLE_LOG_WARNING, 140 | 'log_file' => __DIR__ . '/swoole.log', 141 | ]); 142 | 143 | $server->on('Request', function (Request $request, Response $response) use ($server) { 144 | $response->header('Access-Control-Allow-Origin', '*'); 145 | $response->header('Content-Type', 'text/event-stream'); 146 | $response->header('Cache-Control', 'no-cache'); 147 | $response->header('Connection', 'keep-alive'); 148 | $response->header('X-Accel-Buffering', 'no'); 149 | 150 | $event = new Event(function () { 151 | $id = mt_rand(1, 1000); 152 | $news = [['id' => $id, 'title' => 'title ' . $id, 'content' => 'content ' . $id]]; // Get news from database or service. 153 | if (empty($news)) { 154 | return false; // Return false if no new messages 155 | } 156 | $shouldStop = false; // Stop if something happens or to clear connection, browser will retry 157 | if ($shouldStop) { 158 | throw new StopSSEException(); 159 | } 160 | return json_encode(compact('news')); 161 | // return ['event' => 'ping', 'data' => 'ping data']; // Custom event temporarily: send ping event 162 | // return ['id' => uniqid(), 'data' => json_encode(compact('news'))]; // Custom event Id 163 | }, 'news'); 164 | (new SSESwoole($event, $request, $response))->start(); 165 | }); 166 | $server->start(); 167 | ``` 168 | 169 | ## License 170 | 171 | [MIT](https://github.com/hhxsv5/php-sse/blob/master/LICENSE) 172 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hhxsv5/php-sse", 3 | "type": "library", 4 | "license": "MIT", 5 | "description": "A simple and efficient library implemented HTML5's server-sent events by PHP, is used to real-time push events from server to client, and easier than Websocket, instead of AJAX request.", 6 | "keywords": [ 7 | "sse", 8 | "server-sent events", 9 | "eventsource", 10 | "events", 11 | "sever-events", 12 | "event-stream" 13 | ], 14 | "homepage": "https://github.com/hhxsv5/php-sse", 15 | "authors": [ 16 | { 17 | "name": "Xie Biao", 18 | "email": "hhxsv5@sina.com" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=5.4" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "~6.0", 26 | "swoole/ide-helper": "@dev" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Hhxsv5\\SSE\\": "src" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SSE example 5 | 6 | 7 |

SSE example

8 |
9 | 31 | 32 | -------------------------------------------------------------------------------- /examples/sse-swoole.php: -------------------------------------------------------------------------------- 1 | set([ 16 | 'enable_coroutine' => true, 17 | 'max_coroutine' => 10000, // worker_num*10000 18 | 'reactor_num' => swoole_cpu_num() * 2, 19 | 'worker_num' => swoole_cpu_num() * 2, 20 | 'max_request' => 100000, 21 | 'buffer_output_size' => 4 * 1024 * 1024, // 4MB 22 | 'log_level' => SWOOLE_LOG_WARNING, 23 | 'log_file' => __DIR__ . '/swoole.log', 24 | ]); 25 | 26 | $server->on('Request', function (Request $request, Response $response) use ($server) { 27 | $response->header('Access-Control-Allow-Origin', '*'); 28 | $response->header('Content-Type', 'text/event-stream'); 29 | $response->header('Cache-Control', 'no-cache'); 30 | $response->header('Connection', 'keep-alive'); 31 | $response->header('X-Accel-Buffering', 'no'); 32 | 33 | $event = new Event(function () { 34 | $id = mt_rand(1, 1000); 35 | $news = [['id' => $id, 'title' => 'title ' . $id, 'content' => 'content ' . $id]]; // Get news from database or service. 36 | if (empty($news)) { 37 | return false; // Return false if no new messages 38 | } 39 | $shouldStop = false; // Stop if something happens or to clear connection, browser will retry 40 | if ($shouldStop) { 41 | throw new StopSSEException(); 42 | } 43 | return json_encode(compact('news')); 44 | // return ['event' => 'ping', 'data' => 'ping data']; // Custom event temporarily: send ping event 45 | // return ['id' => uniqid(), 'data' => json_encode(compact('news'))]; // Custom event Id 46 | }, 'news'); 47 | (new SSESwoole($event, $request, $response))->start(); 48 | }); 49 | $server->start(); 50 | -------------------------------------------------------------------------------- /examples/sse.php: -------------------------------------------------------------------------------- 1 | $id, 'title' => 'title ' . $id, 'content' => 'content ' . $id]]; // Get news from database or service. 19 | if (empty($news)) { 20 | return false; // Return false if no new messages 21 | } 22 | $shouldStop = false; // Stop if something happens or to clear connection, browser will retry 23 | if ($shouldStop) { 24 | throw new StopSSEException(); 25 | } 26 | return json_encode(compact('news')); 27 | // return ['event' => 'ping', 'data' => 'ping data']; // Custom event temporarily: send ping event 28 | // return ['id' => uniqid(), 'data' => json_encode(compact('news'))]; // Custom event Id 29 | }; 30 | (new SSE(new Event($callback, 'news')))->start(); 31 | -------------------------------------------------------------------------------- /src/Event.php: -------------------------------------------------------------------------------- 1 | callback = $callback; 51 | $this->id = ''; 52 | $this->data = ''; 53 | $this->initialEvent = $this->event = $event; 54 | $this->retry = $retry; 55 | $this->comment = ''; 56 | } 57 | 58 | /** 59 | * Fill the event data & id 60 | * @return $this 61 | * @throws StopSSEException 62 | */ 63 | public function fill() 64 | { 65 | $this->event = $this->initialEvent; 66 | $result = call_user_func($this->callback); 67 | if ($result === false) { 68 | $this->id = ''; 69 | $this->data = ''; 70 | $this->comment = 'no data'; 71 | } else { 72 | if (isset($result['event'])) { 73 | $this->event = $result['event']; 74 | } 75 | $this->id = isset($result['id']) ? $result['id'] : str_replace('.', '', uniqid('', true)); 76 | $this->data = isset($result['data']) ? $result['data'] : $result; 77 | $this->comment = isset($result['comment']) ? $result['comment'] : ''; 78 | } 79 | return $this; 80 | } 81 | 82 | public function __toString() 83 | { 84 | $event = []; 85 | if ($this->comment !== '') { 86 | $event[] = sprintf(': %s', $this->comment); 87 | } 88 | if ($this->id !== '') { 89 | $event[] = sprintf('id: %s', $this->id); 90 | } 91 | if ($this->retry > 0) { 92 | $event[] = sprintf('retry: %s', $this->retry); 93 | } 94 | if ($this->event !== '') { 95 | $event[] = sprintf('event: %s', $this->event); 96 | } 97 | if ($this->data !== '') { 98 | $event[] = sprintf('data: %s', $this->data); 99 | } 100 | return implode("\n", $event) . "\n\n"; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/SSE.php: -------------------------------------------------------------------------------- 1 | event = $event; 12 | } 13 | 14 | /** 15 | * Start SSE Server 16 | * @param int $interval in seconds 17 | */ 18 | public function start($interval = 3) 19 | { 20 | while (true) { 21 | try { 22 | echo $this->event->fill(); 23 | } catch (StopSSEException $e) { 24 | return; 25 | } 26 | 27 | if (ob_get_level() > 0) { 28 | ob_flush(); 29 | } 30 | 31 | flush(); 32 | 33 | // if the connection has been closed by the client we better exit the loop 34 | if (connection_aborted()) { 35 | return; 36 | } 37 | sleep($interval); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/SSESwoole.php: -------------------------------------------------------------------------------- 1 | request = $request; 17 | $this->response = $response; 18 | parent::__construct($event); 19 | } 20 | 21 | /** 22 | * Start SSE Server 23 | * @param int $interval 24 | */ 25 | public function start($interval = 3) 26 | { 27 | while (true) { 28 | try { 29 | $success = $this->response->write($this->event->fill()); 30 | } catch (StopSSEException $e) { 31 | $this->response->end(); 32 | return; 33 | } 34 | if (!$success) { 35 | $this->response->end(); 36 | return; 37 | } 38 | Coroutine::sleep($interval); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/StopSSEException.php: -------------------------------------------------------------------------------- 1 |