├── .gitignore
├── README.md
├── composer.json
├── examples
├── chat.html
├── chat.php
├── democert.pem
├── echo_client.php
├── headers.html
├── headers.php
├── remoteEvents.html
├── remoteEvents.php
├── rooms.html
├── rooms.php
├── ssl_echo.html
├── ssl_echo.php
├── tcp_proxy_example.html
├── tcp_proxy_example.php
├── time.html
└── time.php
├── js
├── bower.json
└── phpws.js
├── src
└── Devristo
│ └── Phpws
│ ├── Client
│ ├── Connector.php
│ └── WebSocket.php
│ ├── Exceptions
│ ├── WebSocketFrameSizeMismatch.php
│ ├── WebSocketInvalidChallengeResponse.php
│ ├── WebSocketInvalidKeyException.php
│ ├── WebSocketInvalidUrlScheme.php
│ └── WebSocketMessageNotFinalised.php
│ ├── Framing
│ ├── WebSocketFrame.php
│ ├── WebSocketFrame76.php
│ ├── WebSocketFrameInterface.php
│ └── WebSocketOpcode.php
│ ├── Messaging
│ ├── MessageInterface.php
│ ├── RemoteEventMessage.php
│ ├── WebSocketMessage.php
│ ├── WebSocketMessage76.php
│ └── WebSocketMessageInterface.php
│ ├── Protocol
│ ├── Handshake.php
│ ├── StackTransport.php
│ ├── TransportInterface.php
│ ├── WebSocketConnection.php
│ ├── WebSocketTransport.php
│ ├── WebSocketTransportFactory.php
│ ├── WebSocketTransportFlash.php
│ ├── WebSocketTransportHixie.php
│ ├── WebSocketTransportHybi.php
│ ├── WebSocketTransportInterface.php
│ └── WebsocketTransportRole.php
│ ├── Reflection
│ └── FullAccessWrapper.php
│ ├── RemoteEvent
│ ├── RemoteEventTransport.php
│ ├── RemoteEvents.php
│ └── Room.php
│ └── Server
│ ├── OriginEnforcer.php
│ ├── UriHandler
│ ├── ClientRouter.php
│ ├── WebSocketUriHandler.php
│ └── WebSocketUriHandlerInterface.php
│ └── WebSocketServer.php
└── tests
├── framing.test.php
└── server.test.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /phpunit.phar
3 | /vendor
4 | /composer.lock
5 | /composer.phar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebSocket Server and Client library for PHP. Works with the latest HyBi specifications, as well the older Hixie #76 specification used by older Chrome versions and some Flash fallback solutions.
2 |
3 | This project was started to bring more interactive features to http://www.u2start.com/
4 |
5 | Features
6 | ============
7 | Server
8 | * Hixie #76 and Hybi #12 protocol versions
9 | * Flash client support (also serves XML policy file on the same port)
10 | * See https://github.com/gimite/web-socket-js for a compatible Flash Client
11 | * Native Firefox, Safari (iPod / iPhone as well), Chrome and IE10 support. With Flash Client every browser supporting Flash works as well (including IE6-9, Opera, Android and other older desktop browsers).
12 | * Opera (Mobile) supports WebSockets natively but support has been disabled by default. Can be enabled in opera:config.
13 |
14 | Client
15 | * Hybi / Hixie76 support.
16 | * Event-based Async I/O
17 |
18 |
19 | Getting started
20 | =================
21 | The easiest way to set up PHPWS is by using it as Composer dependency. Add the following to your composer.json
22 |
23 | ```json
24 | {
25 | "repositories": [
26 | {
27 | "type": "vcs",
28 | "url": "https://github.com/Devristo/phpws"
29 | }
30 | ],
31 | "require": {
32 | "devristo/phpws": "dev-master"
33 | }
34 | }
35 | ```
36 |
37 | And run ```php composer.phar install```
38 |
39 | To verify it is working create a time.php in your project root
40 | ```php
41 | require_once("vendor/autoload.php");
42 | use Devristo\Phpws\Server\WebSocketServer;
43 |
44 | $loop = \React\EventLoop\Factory::create();
45 |
46 | // Create a logger which writes everything to the STDOUT
47 | $logger = new \Zend\Log\Logger();
48 | $writer = new Zend\Log\Writer\Stream("php://output");
49 | $logger->addWriter($writer);
50 |
51 | // Create a WebSocket server using SSL
52 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
53 |
54 | $loop->addPeriodicTimer(0.5, function() use($server, $logger){
55 | $time = new DateTime();
56 | $string = $time->format("Y-m-d H:i:s");
57 | $logger->notice("Broadcasting time to all clients: $string");
58 | foreach($server->getConnections() as $client)
59 | $client->sendString($string);
60 | });
61 |
62 |
63 | // Bind the server
64 | $server->bind();
65 |
66 | // Start the event loop
67 | $loop->run();
68 | ```
69 |
70 | And a client time.html as follows
71 | ```html
72 |
73 |
74 | WebSocket TEST
75 |
76 |
77 |
Server Time
78 |
79 |
80 |
86 |
87 |
88 | ```
89 | Now run the time.php from the command line and open time.html in your browser. You should see the current time, broadcasted
90 | by phpws at regular intervals. If this works you might be interested in more complicated servers in the examples folder.
91 |
92 | Getting started with the Phpws Client
93 | =======================================
94 | The following is a client for the websocket server hosted at http://echo.websocket.org
95 |
96 | ```php
97 | require_once("vendor/autoload.php"); // Composer autoloader
98 |
99 | $loop = \React\EventLoop\Factory::create();
100 |
101 | $logger = new \Zend\Log\Logger();
102 | $writer = new Zend\Log\Writer\Stream("php://output");
103 | $logger->addWriter($writer);
104 |
105 | $client = new \Devristo\Phpws\Client\WebSocket("ws://echo.websocket.org/?encoding=text", $loop, $logger);
106 |
107 | $client->on("request", function($headers) use ($logger){
108 | $logger->notice("Request object created!");
109 | });
110 |
111 | $client->on("handshake", function() use ($logger) {
112 | $logger->notice("Handshake received!");
113 | });
114 |
115 | $client->on("connect", function($headers) use ($logger, $client){
116 | $logger->notice("Connected!");
117 | $client->send("Hello world!");
118 | });
119 |
120 | $client->on("message", function($message) use ($client, $logger){
121 | $logger->notice("Got message: ".$message->getData());
122 | $client->close();
123 | });
124 |
125 | $client->open();
126 | $loop->run();
127 | ```
128 |
129 |
130 | Known Issues
131 | ==================
132 | * Lacks ORIGIN checking (can be implemented manually in onConnect using getHeaders(), just disconnect the user when you dont like the Origin header)
133 | * No support for extension data from the HyBi specs.
134 |
135 | Requirements
136 | =================
137 | *Server*
138 | * PHP 5.4
139 | * Open port for the server
140 | * PHP OpenSSL module to run a server over a encrypted connection
141 | * http://pecl.php.net/package/pecl_http as its a dependency of Zend\Uri
142 |
143 | * Composer dependencies *
144 | These will be installed automatically when using phpws as a composer package.
145 |
146 | * Reactphp
147 | * ZF2 Logger
148 |
149 | *Client*
150 | * PHP 5.4
151 | * Server that implements the HyBi (#8-#12) draft version
152 | * PHP OpenSSL module to connect using SSL (wss:// uris)
153 |
154 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mpociot/phpws",
3 | "description": "WebSocket Server and Client library for PHP",
4 | "minimum-stability": "stable",
5 | "authors": [
6 | {
7 | "name": "Devristo",
8 | "email": "chris@devristo.com"
9 | }
10 | ],
11 | "autoload": {
12 | "psr-0": {
13 | "Devristo\\Phpws\\": "src/"
14 | }
15 | },
16 | "require": {
17 | "zendframework/zend-log": "2.*",
18 | "react/socket": "^1.0 || ^0.8.6",
19 | "react/stream": "^1.0 || ^0.7.5",
20 | "zendframework/zend-http": "2.*"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Simple Chat
4 |
5 |
10 |
11 |
70 |
71 |
72 |
73 |
WebSocket Test
74 |
75 |
76 |
77 |
78 |
Server will echo your response!
79 |
80 |
--------------------------------------------------------------------------------
/examples/chat.php:
--------------------------------------------------------------------------------
1 | #!/php -q
2 | php chat.php
11 | use Devristo\Phpws\Framing\WebSocketFrame;
12 | use Devristo\Phpws\Framing\WebSocketOpcode;
13 | use Devristo\Phpws\Messaging\WebSocketMessageInterface;
14 | use Devristo\Phpws\Protocol\WebSocketTransportInterface;
15 | use Devristo\Phpws\Server\IWebSocketServerObserver;
16 | use Devristo\Phpws\Server\UriHandler\WebSocketUriHandler;
17 | use Devristo\Phpws\Server\WebSocketServer;
18 |
19 | /**
20 | * This ChatHandler handler below will respond to all messages sent to /chat (e.g. ws://localhost:12345/chat)
21 | */
22 | class ChatHandler extends WebSocketUriHandler {
23 |
24 | /**
25 | * Notify everyone when a user has joined the chat
26 | *
27 | * @param WebSocketTransportInterface $user
28 | */
29 | public function onConnect(WebSocketTransportInterface $user){
30 | foreach($this->getConnections() as $client){
31 | $client->sendString("User {$user->getId()} joined the chat: ");
32 | }
33 | }
34 |
35 | /**
36 | * Broadcast messages sent by a user to everyone in the room
37 | *
38 | * @param WebSocketTransportInterface $user
39 | * @param WebSocketMessageInterface $msg
40 | */
41 | public function onMessage(WebSocketTransportInterface $user, WebSocketMessageInterface $msg) {
42 | $this->logger->notice("Broadcasting " . strlen($msg->getData()) . " bytes");
43 |
44 | foreach($this->getConnections() as $client){
45 | $client->sendString("User {$user->getId()} said: ".$msg->getData());
46 | }
47 | }
48 | }
49 | class ChatHandlerForUnroutedUrls extends WebSocketUriHandler {
50 | /**
51 | * This class deals with users who are not routed
52 | */
53 | public function onConnect(WebSocketTransportInterface $user){
54 | //do nothing
55 | $this->logger->notice("User {$user->getId()} did not join any room");
56 | }
57 | public function onMessage(WebSocketTransportInterface $user, WebSocketMessageInterface $msg) {
58 | //do nothing
59 | $this->logger->notice("User {$user->getId()} is not in a room but tried to say: {$msg->getData()}");
60 | }
61 | }
62 |
63 |
64 | $loop = \React\EventLoop\Factory::create();
65 |
66 | // Create a logger which writes everything to the STDOUT
67 | $logger = new \Zend\Log\Logger();
68 | $writer = new Zend\Log\Writer\Stream("php://output");
69 | $logger->addWriter($writer);
70 |
71 | // Create a WebSocket server
72 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
73 |
74 | // Create a router which transfers all /chat connections to the ChatHandler class
75 | $router = new \Devristo\Phpws\Server\UriHandler\ClientRouter($server, $logger);
76 | // route /chat url
77 | $router->addRoute('#^/chat$#i', new ChatHandler($logger));
78 | // route unmatched urls durring this demo to avoid errors
79 | $router->addRoute('#^(.*)$#i', new ChatHandlerForUnroutedUrls($logger));
80 |
81 | // Bind the server
82 | $server->bind();
83 |
84 | // Start the event loop
85 | $loop->run();
86 |
87 | ?>
88 |
--------------------------------------------------------------------------------
/examples/democert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDwDCCAymgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBojELMAkGA1UEBhMCVVMx
3 | DjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xHzAdBgNVBAoTFlBIUFdT
4 | IERlbW8gQ2VydGlmaWNhdGUxHzAdBgNVBAsTFlBIUFdTIERlbW8gQ2VydGlmaWNh
5 | dGUxDjAMBgNVBAMTBXBocHdzMSAwHgYJKoZIhvcNAQkBFhFwaHB3c0BleGFtcGxl
6 | LmNvbTAeFw0xMTEyMTgxNzI3MjlaFw0xMjEyMTcxNzI3MjlaMIGiMQswCQYDVQQG
7 | EwJVUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBkF1c3RpbjEfMB0GA1UEChMW
8 | UEhQV1MgRGVtbyBDZXJ0aWZpY2F0ZTEfMB0GA1UECxMWUEhQV1MgRGVtbyBDZXJ0
9 | aWZpY2F0ZTEOMAwGA1UEAxMFcGhwd3MxIDAeBgkqhkiG9w0BCQEWEXBocHdzQGV4
10 | YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeht+8x3R4eCft
11 | yf5pHFJwCUB1zYuNrKULIS7XAFPzNHkTQZxU2BEBHjKdcSXJqXUMyByHH1bu70dw
12 | wjnUcXiMfJuTvtTkqyq2GbQu47SujqkcbguBUajo4+OgypmV7BDmatq/JJOH1wMQ
13 | NKmVE7in6v2fEp5DS9Q0DIeb0X0oZQIDAQABo4IBAjCB/zAdBgNVHQ4EFgQUS0a7
14 | Agqixx6D0ExIBwT2oSygZBswgc8GA1UdIwSBxzCBxIAUS0a7Agqixx6D0ExIBwT2
15 | oSygZBuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0G
16 | A1UEBxMGQXVzdGluMR8wHQYDVQQKExZQSFBXUyBEZW1vIENlcnRpZmljYXRlMR8w
17 | HQYDVQQLExZQSFBXUyBEZW1vIENlcnRpZmljYXRlMQ4wDAYDVQQDEwVwaHB3czEg
18 | MB4GCSqGSIb3DQEJARYRcGhwd3NAZXhhbXBsZS5jb22CAQAwDAYDVR0TBAUwAwEB
19 | /zANBgkqhkiG9w0BAQQFAAOBgQAOuG4cfsbLq1CSxtLuhLCtx4XmEbGNIq6PRFbA
20 | +mnVEAs0J4rfeCDxNhQeSeZGduqE5jdq8kNFSBfu3vVYeFbNj91uObAc4ZUQeVgT
21 | N5zrGKKATHAwnftCoMloS07D82z0+HpBbPB5kJ4I6+Z8Dt65cS8aWzw2LE7Thcjg
22 | NXluZw==
23 | -----END CERTIFICATE-----
24 | -----BEGIN RSA PRIVATE KEY-----
25 | MIICXwIBAAKBgQDeht+8x3R4eCftyf5pHFJwCUB1zYuNrKULIS7XAFPzNHkTQZxU
26 | 2BEBHjKdcSXJqXUMyByHH1bu70dwwjnUcXiMfJuTvtTkqyq2GbQu47SujqkcbguB
27 | Uajo4+OgypmV7BDmatq/JJOH1wMQNKmVE7in6v2fEp5DS9Q0DIeb0X0oZQIDAQAB
28 | AoGBANcNsZxXhhAGz0/XLq+WV3U++7TdeEjq2HXxE7tk7bzUsU4S0mqMhaJ29KOD
29 | felug1he7HMJrpIrXPd0PT86iiwtfdzFKRjhDmKLWn8iSXp04tJOOY51BTmN/wO/
30 | NCKnj60lbjwCjbGxGLq7VyYQjpnIVZ5Y/RGlc155eMBz0jkBAkEA94UfBWNxkNJA
31 | GQdXtrfF/ShbHK5HCgrh+oSRUQ7oqigTjod/1cXUmLWDX03oxELeKOiJx3nOi1YA
32 | 8L1BuBOLeQJBAOYmjMUPapcIvUubweEw6sYXaM+3Msie+HHTI0VzIQkvAUYUtGQz
33 | 5T0pUaSe9SGOpQeCUViczE5ko4T7GyfrnU0CQQCFl8gCdIXbEF+gIqJo8A9gb+Od
34 | O0MEXJNTTzHPeiiBjlff2apZiwkP0wgw7C/xndWiZr/WdhvQgH7JcJyD6aihAkEA
35 | xfnNR8pOH2PWKc7vRT4mBoamk198oNUW1BsSkTBK77JufxFaZ4O4oxcC8wAFz3r7
36 | /Oyd+wLOQHUTsFWs83cbVQJBAIX03j/CO2NuoEESTXWmE3MjMdghT45CKpfqHrFY
37 | T8kZ6oQtxtNibxC5dwWnl4pCGTDGuCkE7/tfLchs0lID16M=
38 | -----END RSA PRIVATE KEY-----
39 |
--------------------------------------------------------------------------------
/examples/echo_client.php:
--------------------------------------------------------------------------------
1 | addWriter($writer);
17 |
18 | $client = new \Devristo\Phpws\Client\WebSocket("ws://echo.websocket.org/?encoding=text", $loop, $logger);
19 | //$client = new \Devristo\Phpws\Client\WebSocket("ws://google.com", $loop, $logger);
20 | $client->on("connect", function() use ($logger, $client){
21 | $logger->notice("Or we can use the connect event!");
22 | $client->send("Hello world!");
23 | });
24 |
25 | $client->on("message", function($message) use ($client, $logger){
26 | $logger->notice("Got message: ".$message->getData());
27 | $client->close();
28 | });
29 |
30 | $client->open()->then(function() use($logger, $client){
31 | $logger->notice("We can use a promise to determine when the socket has been connected!");
32 | });
33 |
34 | $loop->run();
--------------------------------------------------------------------------------
/examples/headers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Header Manipulation
5 |
6 |
7 |
8 |
9 |
PHPWS handshake response
10 |
11 |
12 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/headers.php:
--------------------------------------------------------------------------------
1 | #!/php -q
2 | addWriter($writer);
15 |
16 | // Create a WebSocket server using SSL
17 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
18 | $server->on("handshake", function(WebSocketTransport $client, Handshake $handshake){
19 | // Here we can alter or abort PHPWS's response to the user
20 | $handshake->getResponse()->getHeaders()->addHeaderLine("X-WebSocket-Server", "phpws");
21 |
22 | // We can also see which headers the client sent in its handshake. Lets proof it
23 | $userAgent = $handshake->getRequest()->getHeader('User-Agent')->getFieldValue();
24 | $handshake->getResponse()->getHeaders()->addHeaderLine("X-User-Agent",$userAgent);
25 |
26 | // Since we cannot see in the browser what headers were sent by the server, we will send them again as a message
27 | $client->on("connect", function() use ($client){
28 |
29 | // The request and the response is available on the transport object as well.
30 | $client->sendString($client->getHandshakeResponse()->toString());
31 | });
32 | });
33 |
34 | // Bind the server
35 | $server->bind();
36 |
37 | // Start the event loop
38 | $loop->run();
--------------------------------------------------------------------------------
/examples/remoteEvents.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Protocol stacking
5 |
6 |
7 |
8 |
9 |
10 |
Remote Events
11 |
12 |
13 |
38 |
39 |
--------------------------------------------------------------------------------
/examples/remoteEvents.php:
--------------------------------------------------------------------------------
1 | php demo.php
6 | use Devristo\Phpws\Messaging\RemoteEventMessage;
7 | use Devristo\Phpws\Protocol\StackTransport;
8 | use Devristo\Phpws\RemoteEvent\RemoteEventTransport;
9 | use Devristo\Phpws\Protocol\ServerProtocolStack;
10 | use Devristo\Phpws\Protocol\TransportInterface;
11 | use Devristo\Phpws\Protocol\WebSocketTransportInterface;
12 | use Devristo\Phpws\Server\WebSocketServer;
13 |
14 |
15 | class StackHandler extends \Devristo\Phpws\Server\UriHandler\WebSocketUriHandler{
16 | protected $loop;
17 |
18 | public function __construct(\React\EventLoop\LoopInterface $loop, $logger){
19 | parent::__construct($logger);
20 | $this->loop = $loop;
21 | }
22 |
23 | /**
24 | * Notify everyone when a user has joined the chat
25 | *
26 | * @param StackTransport $stackTransport
27 | */
28 | public function onConnect(WebSocketTransportInterface $transport){
29 | /**
30 | * @var $stackTransport StackTransport
31 | * @var $jsonTransport RemoteEventTransport
32 | */
33 | $logger = $this->logger;
34 | $loop = $this->loop;
35 | $stackTransport = StackTransport::create($transport, array(function(TransportInterface $carrier) use($loop, $logger){
36 | return new RemoteEventTransport($carrier, $loop, $logger);
37 | }));
38 |
39 | $jsonTransport = $stackTransport->getTopTransport();
40 |
41 | $server = $transport->getHandshakeResponse()->getHeaders()->get('X-WebSocket-Server')->getFieldValue();
42 |
43 | $greetingMessage = RemoteEventMessage::create(null, "greeting", "hello world from $server!");
44 |
45 | $jsonTransport->whenResponseTo($greetingMessage, 0.1)->then(function(RemoteEventMessage $result) use ($logger, $server){
46 | $logger->notice(sprintf("Got '%s' in response to 'hello world from $server!'", $result->getData()));
47 | });
48 |
49 | $jsonTransport->remoteEvent()->on("greeting", function(RemoteEventMessage $message) use ($transport, $logger){
50 | $logger->notice(sprintf("We got a greeting event from {$transport->getId()}"));
51 | });
52 | }
53 | }
54 |
55 | $loop = \React\EventLoop\Factory::create();
56 |
57 | // Create a logger which writes everything to the STDOUT
58 | $logger = new \Zend\Log\Logger();
59 | $writer = new \Zend\Log\Writer\Stream("php://output");
60 | $logger->addWriter($writer);
61 |
62 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
63 | $server->bind();
64 |
65 | $server->on("handshake", function(\Devristo\Phpws\Protocol\WebSocketTransportInterface $transport, \Devristo\Phpws\Protocol\Handshake $handshake){
66 | $handshake->getResponse()->getHeaders()->addHeaderLine("X-WebSocket-Server", "phpws");
67 | });
68 |
69 | $router = new \Devristo\Phpws\Server\UriHandler\ClientRouter($server, $logger);
70 | $router->addRoute('#^/stack#i', new StackHandler($loop, $logger));
71 |
72 | // Start the event loop
73 | $loop->run();
--------------------------------------------------------------------------------
/examples/rooms.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Rooms
5 |
6 |
7 |
8 |
9 |
10 |
Server Time
11 |
12 |
13 |
14 |
15 |
16 |
17 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/rooms.php:
--------------------------------------------------------------------------------
1 | php demo.php
11 | use Devristo\Phpws\RemoteEvent\RemoteEventTransport;
12 | use Devristo\Phpws\Protocol\StackTransport;
13 | use Devristo\Phpws\Protocol\TransportInterface;
14 | use Devristo\Phpws\Server\WebSocketServer;
15 |
16 | $loop = \React\EventLoop\Factory::create();
17 |
18 | // Create a logger which writes everything to the STDOUT
19 | $logger = new \Zend\Log\Logger();
20 | $writer = new Zend\Log\Writer\Stream("php://output");
21 | $logger->addWriter($writer);
22 |
23 | // Create a WebSocket server and create a router which sends all user requesting /echo to the DemoEchoHandler above
24 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
25 |
26 | $handler = new \Devristo\Phpws\RemoteEvent\RemoteEvents($logger);
27 |
28 | $server->on("connect", function(TransportInterface $transport) use($loop, $logger, $handler){
29 |
30 | $stack = StackTransport::create($transport, array(
31 | function (TransportInterface $transport) use ($loop, $logger) {
32 | return new RemoteEventTransport($transport, $loop, $logger);
33 | }
34 | ));
35 |
36 | $handler->listenTo($stack);
37 | });
38 |
39 | $handler->room("time")->on("subscribe", function (StackTransport $transport) use ($logger, $handler){
40 | $logger->notice("Someone joined our room full of time enthousiasts!!");
41 | });
42 |
43 | // Each 0.5 seconds sent the time to all connected clients
44 | $loop->addPeriodicTimer(0.5, function() use($server, $handler, $logger){
45 | $time = new DateTime();
46 | $string = $time->format("Y-m-d H:i:s");
47 |
48 | if(count($handler->room("time")->getMembers()))
49 | $logger->notice("Broadcasting time to time room: $string");
50 |
51 | $handler->room("time")->remoteEmit("time", $string);
52 | });
53 |
54 |
55 | // Bind the server
56 | $server->bind();
57 |
58 | // Start the event loop
59 | $loop->run();
--------------------------------------------------------------------------------
/examples/ssl_echo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | SSL Echo
4 |
5 |
10 |
11 |
71 |
72 |
73 |
74 |
WebSocket Test
75 |
76 |
77 |
78 |
79 |
Server will echo your response!
80 |
81 |
--------------------------------------------------------------------------------
/examples/ssl_echo.php:
--------------------------------------------------------------------------------
1 | #!/php -q
2 | addWriter($writer);
15 |
16 | // Create a WebSocket server using SSL
17 | $server = new WebSocketServer("ssl://0.0.0.0:12345", $loop, $logger);
18 | $context = stream_context_create();
19 | stream_context_set_option($context, 'ssl', 'local_cert', "democert.pem");
20 | stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
21 | stream_context_set_option($context, 'ssl', 'verify_peer', false);
22 | $server->setStreamContext($context);
23 |
24 | // Sent a welcome message when a client connects
25 | $server->on("connect", function(WebSocketTransportInterface $user){
26 | $user->sendString("Hey! I am the echo robot. I will repeat all your input!");
27 | });
28 |
29 | // Echo back any message the user sends
30 | $server->on("message", function(WebSocketTransportInterface $user, WebSocketMessageInterface $message) use($logger){
31 | $logger->notice(sprintf("We have got '%s' from client %s", $message->getData(), $user->getId()));
32 | $user->sendString($message->getData());
33 | });
34 |
35 | // Bind the server
36 | $server->bind();
37 |
38 | // Start the event loop
39 | $loop->run();
--------------------------------------------------------------------------------
/examples/tcp_proxy_example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TCP Proxy
5 |
6 |
7 |
8 |
9 |
Tcp proxy
10 |
Check JS Console for details
11 |
12 |
Request
13 |
14 |
15 |
16 |
17 |
Response
18 |
19 |
20 |
21 |
22 |
23 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/examples/tcp_proxy_example.php:
--------------------------------------------------------------------------------
1 | #!/php -q
2 | php demo.php
7 | use Devristo\Phpws\Messaging\WebSocketMessageInterface;
8 | use Devristo\Phpws\Protocol\WebSocketTransportInterface;
9 | use Devristo\Phpws\Server\UriHandler\WebSocketUriHandler;
10 | use Devristo\Phpws\Server\WebSocketServer;
11 |
12 | /**
13 | * This URI handler will allow clients to open TCP connections through the outside world. Phpws is then acting as proxy.
14 | *
15 | * @author Chris
16 | *
17 | */
18 | class ProxyHandler extends WebSocketUriHandler
19 | {
20 | /**
21 | * A multi-dimensional dictionary. First key is user id and second key is the id of the TCP stream. The value is the
22 | * TCP stream itself
23 | *
24 | * @var \React\Stream\Stream[][]
25 | */
26 | protected $streams = array();
27 | protected $server;
28 |
29 | /**
30 | * @param \React\EventLoop\LoopInterface $loop The React Loop, it is used to listen to events on newly created TCP
31 | * streams
32 | * @param $logger
33 | */
34 | public function __construct(\React\EventLoop\LoopInterface $loop, $logger)
35 | {
36 | parent::__construct($logger);
37 | $this->loop = $loop;
38 | }
39 |
40 | public function onDisconnect(WebSocketTransportInterface $user)
41 | {
42 | foreach ($this->getStreamsByUser($user) as $stream) {
43 | $stream->close();
44 | }
45 | unset($this->streams[$user->getId()]);
46 | }
47 |
48 | /**
49 | * Entry point for all messages received from clients in this proxy 'room'
50 | *
51 | * @param WebSocketTransportInterface $user
52 | * @param WebSocketMessageInterface $msg
53 | */
54 | public function onMessage(WebSocketTransportInterface $user, WebSocketMessageInterface $msg)
55 | {
56 | try {
57 | $message = json_decode($msg->getData());
58 |
59 | if ($message->command == 'connect')
60 | $this->requestConnect($user, $message);
61 | elseif ($message->command == 'write')
62 | $this->requestWrite($user, $message);
63 | elseif ($message->command == 'close')
64 | $this->requestClose($user, $message);
65 |
66 | } catch (Exception $e) {
67 | $this->logger->err($e->getMessage());
68 | }
69 | }
70 |
71 | /**
72 | * Handler called when a CONNECT message is sent by a client
73 | *
74 | * A React SocketClient will be created, Google DNS is used to resolve host names. When the connection is made
75 | * several event listeners are attached. When data is received on the stream, it is forwarded to the client requesting
76 | * the proxied TCP connection
77 | *
78 | * Other events forwarded are connect and close
79 | *
80 | * @param WebSocketTransportInterface $user
81 | * @param $message
82 | */
83 | protected function requestConnect(WebSocketTransportInterface $user, $message)
84 | {
85 | $address = $message->address;
86 | $this->logger->notice(sprintf("User %s requests connection to %s", $user->getId(), $address));
87 |
88 | try {
89 | $dnsResolverFactory = new React\Dns\Resolver\Factory();
90 | $dns = $dnsResolverFactory->createCached('8.8.8.8', $this->loop);
91 | $stream = new \React\SocketClient\Connector($this->loop, $dns);
92 |
93 | list($host, $port) = explode(":", $address);
94 |
95 | $logger = $this->logger;
96 | $that = $this;
97 |
98 | $stream->create($host, $port)->then(function (\React\Stream\Stream $stream) use($user, $logger, $message, $address, $that){
99 | $id = uniqid("stream-$address-");
100 | $that->addStream($user, $id, $stream);
101 |
102 | // Notify the user when the connection has been made
103 | $user->sendString(json_encode(array(
104 | 'connection' => $id,
105 | 'event' => 'connected',
106 | 'tag' => property_exists($message, 'tag') ? $message->tag : null
107 | )));
108 |
109 | // Forward data back to the user
110 | $stream->on("data", function ($data) use ($stream, $id, $user, $logger){
111 | $logger->notice("Forwarding ".strlen($data). " bytes from stream $id to {$user->getId()}");
112 | $message = array(
113 | 'connection' => $id,
114 | 'event' => 'data',
115 | 'data' => $data
116 | );
117 |
118 | $user->sendString(json_encode($message));
119 | });
120 |
121 | // When the stream closes, notify the user
122 | $stream->on("close", function() use($user, $id, $logger, $address){
123 | $logger->notice(sprintf("Connection %s of user %s to %s has been closed", $id, $user->getId(), $address));
124 |
125 | $message =
126 | array(
127 | 'connection' => $id,
128 | 'event' => 'close'
129 | );
130 |
131 | $user->sendString(json_encode($message));
132 | });
133 | });
134 | } catch (Exception $e) {
135 | $user->sendString(json_encode(array(
136 | 'event' => 'error',
137 | 'tag' => property_exists($message, 'tag') ? $message->tag : null,
138 | 'message' => $e->getMessage()
139 | )));
140 | }
141 | }
142 |
143 | /**
144 | * Forward data send by the user over the specified TCP stream
145 | *
146 | * @param WebSocketTransportInterface $user
147 | * @param $message
148 | */
149 | protected function requestWrite(WebSocketTransportInterface $user, $message)
150 | {
151 | $stream = $this->getStream($user, $message->connection);
152 |
153 | if($stream){
154 | $this->logger->notice(sprintf("User %s writes %d bytes to connection %s", $user->getId(), strlen($message->data), $message->connection));
155 | $stream->write($message->data);
156 | }
157 | }
158 |
159 | /**
160 | * Close the stream specified by the user
161 | *
162 | * @param WebSocketTransportInterface $user
163 | * @param $message
164 | */
165 | protected function requestClose(WebSocketTransportInterface $user, $message)
166 | {
167 | $stream = $this->getStream($user, $message->connection);
168 |
169 | if($stream){
170 | $this->logger->notice(sprintf("User %s closes connection %s", $user->getId(), $message->connection));
171 | $stream->close();
172 | $this->removeStream($user, $message->connection);
173 |
174 | $user->sendString(json_encode(array(
175 | 'event' => 'close',
176 | 'connection' => $message->connection,
177 | 'tag' => property_exists($message, 'tag') ? $message->tag : null
178 | )));
179 | } else {
180 | $user->sendString(json_encode(array(
181 | 'event' => 'error',
182 | 'tag' => property_exists($message, 'tag') ? $message->tag : null,
183 | 'message' => 'Connection was already closed'
184 | )));
185 | }
186 | }
187 |
188 | /**
189 | * @param WebSocketTransportInterface $user
190 | * @param $id
191 | * @return \React\Stream\Stream
192 | */
193 | protected function getStream(WebSocketTransportInterface $user, $id)
194 | {
195 | $userStreams = $this->getStreamsByUser($user);
196 |
197 | return array_key_exists($id, $userStreams) ? $userStreams[$id] : null;
198 | }
199 |
200 | /**
201 | * @param WebSocketTransportInterface $user
202 | * @return \React\Stream\Stream[]
203 | */
204 | protected function getStreamsByUser(WebSocketTransportInterface $user)
205 | {
206 | return array_key_exists($user->getId(), $this->streams) ? $this->streams[$user->getId()] : array();
207 | }
208 |
209 | protected function removeStream(WebSocketTransportInterface $user, $id)
210 | {
211 | unset($this->streams[$user->getId()][$id]);
212 | }
213 |
214 | protected function addStream(WebSocketTransportInterface $user, $id, \React\Stream\Stream $stream){
215 | $this->streams[$user->getId()][$id] = $stream;
216 | }
217 | }
218 |
219 | $loop = \React\EventLoop\Factory::create();
220 | $logger = new \Zend\Log\Logger();
221 | $writer = new Zend\Log\Writer\Stream("php://output");
222 | $logger->addWriter($writer);
223 |
224 | $server = new WebSocketServer("tcp://0.0.0.0:12345", $loop, $logger);
225 | $router = new \Devristo\Phpws\Server\UriHandler\ClientRouter($server, $logger);
226 | $router->addRoute("#^/proxy$#i", new ProxyHandler($loop, $logger));
227 |
228 | $server->bind();
229 | $loop->run();
--------------------------------------------------------------------------------
/examples/time.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Timers
4 |
5 |
6 |