├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bugreport.yml
│ └── config.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── composer.json
├── phpunit.xml.dist
├── phpunit.xml.legacy
├── src
└── Ratchet
│ ├── AbstractConnectionDecorator.php
│ ├── App.php
│ ├── ComponentInterface.php
│ ├── ConnectionInterface.php
│ ├── Http
│ ├── CloseResponseTrait.php
│ ├── HttpRequestParser.php
│ ├── HttpServer.php
│ ├── HttpServerInterface.php
│ ├── NoOpHttpServerController.php
│ ├── OriginCheck.php
│ └── Router.php
│ ├── MessageComponentInterface.php
│ ├── MessageInterface.php
│ ├── Server
│ ├── EchoServer.php
│ ├── FlashPolicy.php
│ ├── IoConnection.php
│ ├── IoServer.php
│ └── IpBlackList.php
│ ├── Session
│ ├── Serialize
│ │ ├── HandlerInterface.php
│ │ ├── PhpBinaryHandler.php
│ │ └── PhpHandler.php
│ ├── SessionProvider.php
│ └── Storage
│ │ ├── Proxy
│ │ ├── VirtualProxy.php
│ │ ├── VirtualProxyForSymfony6.php
│ │ └── VirtualProxyForSymfony7.php
│ │ ├── VirtualSessionStorage.php
│ │ ├── VirtualSessionStorageForSymfony6.php
│ │ └── VirtualSessionStorageForSymfony7.php
│ ├── Wamp
│ ├── Exception.php
│ ├── JsonException.php
│ ├── ServerProtocol.php
│ ├── Topic.php
│ ├── TopicManager.php
│ ├── WampConnection.php
│ ├── WampServer.php
│ └── WampServerInterface.php
│ └── WebSocket
│ ├── ConnContext.php
│ ├── MessageCallableInterface.php
│ ├── MessageComponentInterface.php
│ ├── WsConnection.php
│ ├── WsServer.php
│ └── WsServerInterface.php
└── tests
├── autobahn
├── bin
│ └── fuzzingserver.php
├── fuzzingclient-all.json
├── fuzzingclient-profile.json
└── fuzzingclient-quick.json
├── bootstrap.php
├── helpers
└── Ratchet
│ ├── AbstractMessageComponentTestCase.php
│ ├── Mock
│ ├── Connection.php
│ ├── ConnectionDecorator.php
│ └── WampComponent.php
│ ├── NullComponent.php
│ ├── Wamp
│ └── Stub
│ │ └── WsWampServerInterface.php
│ └── WebSocket
│ └── Stub
│ └── WsMessageComponentInterface.php
└── unit
├── AbstractConnectionDecoratorTest.php
├── AppTest.php
├── Http
├── HttpRequestParserTest.php
├── HttpServerTest.php
├── OriginCheckTest.php
└── RouterTest.php
├── Server
├── EchoServerTest.php
├── FlashPolicyComponentTest.php
├── IoConnectionTest.php
├── IoServerTest.php
└── IpBlackListComponentTest.php
├── Session
├── Serialize
│ ├── PhpBinaryHandlerTest.php
│ └── PhpHandlerTest.php
├── SessionProviderTest.php
└── Storage
│ └── VirtualSessionStoragePDOTest.php
└── Wamp
├── ServerProtocolTest.php
├── TopicManagerTest.php
├── TopicTest.php
├── WampConnectionTest.php
└── WampServerTest.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github:
2 | - ReactPHP
3 | - clue
4 | - cboden
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bugreport.yml:
--------------------------------------------------------------------------------
1 | name: "🐛 Bug Report"
2 | description: "Found a bug in our project? Create a report to help us improve."
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: "Thanks for helping improve Ratchet by reporting a bug!
7 | Please check [existing Issues](https://github.com/ratchetphp/Ratchet/issues) before submitting."
8 |
9 | - type: textarea
10 | id: reproduction
11 | attributes:
12 | label: "Reproduction Steps"
13 | description: "Exact steps to reproduce (include code samples)"
14 | validations:
15 | required: true
16 |
17 | - type: textarea
18 | id: environment
19 | attributes:
20 | label: "Runtime Environment"
21 | placeholder: "PHP version, Ratchet version, etc."
22 | validations:
23 | required: true
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: "Ratchet Discussions"
4 | url: https://github.com/ratchetphp/Ratchet/discussions
5 | about: 'We are happy to answer your questions! Start a new discussion in our "Q&A" category.'
6 | - name: "Feature Requests"
7 | url: https://github.com/ratchetphp/Ratchet/discussions/categories/ideas
8 | about: 'You have ideas to improve our project? Start a new discussion in our "Ideas" category.'
9 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: "CI"
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | phpunit:
9 | name: "PHPUnit"
10 | runs-on: "ubuntu-24.04"
11 |
12 | strategy:
13 | matrix:
14 | php-version:
15 | - "5.4"
16 | - "5.5"
17 | - "5.6"
18 | - "7.0"
19 | - "7.1"
20 | - "7.2"
21 | - "7.3"
22 | - "7.4"
23 | - "8.0"
24 | - "8.1"
25 | - "8.2"
26 | - "8.3"
27 | - "8.4"
28 | dependencies:
29 | - "highest"
30 | include:
31 | - dependencies: "lowest"
32 | php-version: "5.4"
33 |
34 | steps:
35 | - name: "Checkout"
36 | uses: "actions/checkout@v4"
37 | with:
38 | fetch-depth: 2
39 |
40 | - name: "Install PHP"
41 | uses: "shivammathur/setup-php@v2"
42 | with:
43 | php-version: "${{ matrix.php-version }}"
44 | coverage: "none"
45 | ini-values: "zend.assertions=1"
46 |
47 | - name: "Install dependencies with Composer"
48 | uses: "ramsey/composer-install@v3"
49 | with:
50 | dependency-versions: "${{ matrix.dependencies }}"
51 |
52 | - name: "Run PHPUnit"
53 | run: "vendor/bin/phpunit ${{ matrix.php-version < 7.3 && ' -c phpunit.xml.legacy' || '' }}"
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | phpunit.xml
2 | reports
3 | sandbox
4 | vendor
5 | composer.lock
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | ### Legend
5 |
6 | * "BC": Backwards compatibility break (from public component APIs)
7 | * "BF": Bug fix
8 |
9 | ---
10 |
11 | * 0.4.4 (2021-12-11)
12 | * Correct and update dependencies for forward compatibility
13 | * Added context for React Socket server to App
14 | * Use non-deprecated Guzzle API calls
15 |
16 | * 0.4.3 (2020-06-04)
17 | * BF: Fixed interface acceptable regression in `App`
18 | * Update RFC6455 library with latest fixes
19 |
20 | * 0.4.2 (2020-01-27)
21 | * Support Symfony 5
22 | * BF: Use phpunit from vendor directory
23 | * Allow disabling of xdebug warning by defining `RATCHET_DISABLE_XDEBUG_WARN`
24 | * Stop using `LoopInterface::tick()` for testing
25 |
26 | * 0.4.1 (2017-12-11)
27 | * Only enableKeepAlive in App if no WsServer passed allowing user to set their own timeout duration
28 | * Support Symfony 4
29 | * BF: Plug NOOP controller in connection from router in case of misbehaving client
30 | * BF: Raise error from invalid WAMP payload
31 |
32 | * 0.4 (2017-09-14)
33 | * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object
34 | * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface
35 | * Added heartbeat support via ping/pong in WsServer
36 | * BC: No longer support old (and insecure) Hixie76 and Hybi protocols
37 | * BC: No longer support disabling UTF-8 checks
38 | * BC: The Session component implements HttpServerInterface instead of WsServerInterface
39 | * BC: PHP 5.3 no longer supported
40 | * BC: Update to newer version of react/socket dependency
41 | * BC: WAMP topics reduced to 0 subscriptions are deleted, new subs to same name will result in new Topic instance
42 | * Significant performance enhancements
43 |
44 | * 0.3.6 (2017-01-06)
45 | * BF: Keep host and scheme in HTTP request object attatched to connection
46 | * BF: Return correct HTTP response (405) when non-GET request made
47 |
48 | * 0.3.5 (2016-05-25)
49 | * BF: Unmask responding close frame
50 | * Added write handler for PHP session serializer
51 |
52 | * 0.3.4 (2015-12-23)
53 | * BF: Edge case where version check wasn't run on message coalesce
54 | * BF: Session didn't start when using pdo_sqlite
55 | * BF: WAMP currie prefix check when using '#'
56 | * Compatibility with Symfony 3
57 |
58 | * 0.3.3 (2015-05-26)
59 | * BF: Framing bug on large messages upon TCP fragmentation
60 | * BF: Symfony Router query parameter defaults applied to Request
61 | * BF: WAMP CURIE on all URIs
62 | * OriginCheck rules applied to FlashPolicy
63 | * Switched from PSR-0 to PSR-4
64 |
65 | * 0.3.2 (2014-06-08)
66 | * BF: No messages after closing handshake (fixed rare race condition causing 100% CPU)
67 | * BF: Fixed accidental BC break from v0.3.1
68 | * Added autoDelete parameter to Topic to destroy when empty of connections
69 | * Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App)
70 | * Normalized Exceptions in WAMP
71 |
72 | * 0.3.1 (2014-05-26)
73 | * Added query parameter support to Router, set in HTTP request (ws://server?hello=world)
74 | * HHVM compatibility
75 | * BF: React/0.4 support; CPU starvation bug fixes
76 | * BF: Allow App::route to ignore Host header
77 | * Added expected filters to WAMP Topic broadcast method
78 | * Resource cleanup in WAMP TopicManager
79 |
80 | * 0.3.0 (2013-10-14)
81 | * Added the `App` class to help making Ratchet so easy to use it's silly
82 | * BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks
83 | * Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router
84 | * BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer
85 | * BF: Single sub-protocol selection to conform with RFC6455
86 | * BF: Sanity checks on WAMP protocol to prevent errors
87 |
88 | * 0.2.8 (2013-09-19)
89 | * React 0.3 support
90 |
91 | * 0.2.7 (2013-06-09)
92 | * BF: Sub-protocol negotation with Guzzle 3.6
93 |
94 | * 0.2.6 (2013-06-01)
95 | * Guzzle 3.6 support
96 |
97 | * 0.2.5 (2013-04-01)
98 | * Fixed Hixie-76 handshake bug
99 |
100 | * 0.2.4 (2013-03-09)
101 | * Support for Symfony 2.2 and Guzzle 2.3
102 | * Minor bug fixes when handling errors
103 |
104 | * 0.2.3 (2012-11-21)
105 | * Bumped dep: Guzzle to v3, React to v0.2.4
106 | * More tests
107 |
108 | * 0.2.2 (2012-10-20)
109 | * Bumped deps to use React v0.2
110 |
111 | * 0.2.1 (2012-10-13)
112 | * BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string)
113 | * Documentation corrections
114 | * Using new composer structure
115 |
116 | * 0.2 (2012-09-07)
117 | * Ratchet passes every non-binary-frame test from the Autobahn Testsuite
118 | * Major performance improvements
119 | * BC: Renamed "WampServer" to "ServerProtocol"
120 | * BC: New "WampServer" component passes Topic container objects of subscribed Connections
121 | * Option to turn off UTF-8 checks in order to increase performance
122 | * Switched dependency guzzle/guzzle to guzzle/http (no API changes)
123 | * mbstring no longer required
124 |
125 | * 0.1.5 (2012-07-12)
126 | * BF: Error where service wouldn't run on PHP <= 5.3.8
127 | * Dependency library updates
128 |
129 | * 0.1.4 (2012-06-17)
130 | * Fixed dozens of failing AB tests
131 | * BF: Proper socket buffer handling
132 |
133 | * 0.1.3 (2012-06-15)
134 | * Major refactor inside WebSocket protocol handling, more loosley coupled
135 | * BF: Proper error handling on failed WebSocket connections
136 | * BF: Handle TCP message concatenation
137 | * Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance
138 | * mb_string now a requirement
139 |
140 | * 0.1.2 (2012-05-19)
141 | * BC/BF: Updated WAMP API to coincide with the official spec
142 | * Tweaks to improve running as a long lived process
143 |
144 | * 0.1.1 (2012-05-14)
145 | * Separated interfaces allowing WebSockets to support multiple sub protocols
146 | * BF: remoteAddress variable on connections returns proper value
147 |
148 | * 0.1 (2012-05-11)
149 | * First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList
150 | * I/O now handled by React, making Ratchet fully asynchronous
151 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Chris Boden
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # This file is intended to ease the author's development and testing process
2 | # Users do not need to use `make`; Ratchet does not need to be compiled
3 |
4 | test:
5 | vendor/bin/phpunit
6 |
7 | cover:
8 | vendor/bin/phpunit --coverage-text --coverage-html=reports/coverage
9 |
10 | abtests:
11 | ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent &
12 | ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect &
13 | ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv &
14 | wstest -m testeeserver -w ws://localhost:8000 &
15 | sleep 1
16 | wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json
17 | killall php wstest
18 |
19 | abtest:
20 | ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect &
21 | sleep 1
22 | wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json
23 | killall php
24 |
25 | profile:
26 | php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent &
27 | sleep 1
28 | wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json
29 | killall php
30 |
31 | apidocs:
32 | apigen --title Ratchet -d reports/api \
33 | -s src/ \
34 | -s vendor/ratchet/rfc6455/src \
35 | -s vendor/react/event-loop/src \
36 | -s vendor/react/socket/src \
37 | -s vendor/react/stream/src \
38 | -s vendor/psr/http-message/src \
39 | -s vendor/symfony/http-foundation/Session \
40 | -s vendor/symfony/routing \
41 | -s vendor/evenement/evenement/src/Evenement \
42 | --exclude=vendor/symfony/routing/Tests \
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ratchet
2 |
3 | [](https://github.com/ratchetphp/Ratchet/actions)
4 | [](http://socketo.me/reports/ab/index.html)
5 | [](https://packagist.org/packages/cboden/ratchet)
6 |
7 | A PHP library for asynchronously serving WebSockets.
8 | Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components.
9 |
10 | ## Reviving Ratchet!
11 |
12 | We're currently aiming to revive Ratchet to get it up to date with the latest versions and use this as a starting point for bigger updates to come.
13 | We need your help to achieve this goal, see [ticket #1054](https://github.com/ratchetphp/Ratchet/issues/1054) for ways to help out. ❤️
14 |
15 | ## Requirements
16 |
17 | Shell access is required and root access is recommended.
18 | To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access.
19 | In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines.
20 | You can find more details in the [server conf docs](http://socketo.me/docs/deploy#server_configuration).
21 |
22 | ### Documentation
23 |
24 | User and API documentation is available on Ratchet's website: http://socketo.me
25 |
26 | See https://github.com/cboden/Ratchet-examples for some out-of-the-box working demos using Ratchet.
27 |
28 | Need help? Have a question? Want to provide feedback? Write a message on the [Google Groups Mailing List](https://groups.google.com/forum/#!forum/ratchet-php).
29 |
30 | ---
31 |
32 | ### A quick example
33 |
34 | ```php
35 | clients = new \SplObjectStorage;
51 | }
52 |
53 | public function onOpen(ConnectionInterface $conn) {
54 | $this->clients->attach($conn);
55 | }
56 |
57 | public function onMessage(ConnectionInterface $from, $msg) {
58 | foreach ($this->clients as $client) {
59 | if ($from != $client) {
60 | $client->send($msg);
61 | }
62 | }
63 | }
64 |
65 | public function onClose(ConnectionInterface $conn) {
66 | $this->clients->detach($conn);
67 | }
68 |
69 | public function onError(ConnectionInterface $conn, \Exception $e) {
70 | $conn->close();
71 | }
72 | }
73 |
74 | // Run the server application through the WebSocket protocol on port 8080
75 | $app = new Ratchet\App('localhost', 8080);
76 | $app->route('/chat', new MyChat, array('*'));
77 | $app->route('/echo', new Ratchet\Server\EchoServer, array('*'));
78 | $app->run();
79 | ```
80 |
81 | $ php chat.php
82 |
83 | ```javascript
84 | // Then some JavaScript in the browser:
85 | var conn = new WebSocket('ws://localhost:8080/echo');
86 | conn.onmessage = function(e) { console.log(e.data); };
87 | conn.onopen = function(e) { conn.send('Hello Me!'); };
88 | ```
89 |
90 | ## Install
91 |
92 | The recommended way to install this library is [through Composer](https://getcomposer.org/).
93 | [New to Composer?](https://getcomposer.org/doc/00-intro.md)
94 |
95 | This will install the latest supported version:
96 |
97 | ```bash
98 | composer require cboden/ratchet:^0.4.4
99 | ```
100 |
101 | See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
102 |
103 | This project aims to run on any platform and thus does not require any PHP
104 | extensions and supports running on legacy PHP 5.4 through current PHP 8+.
105 | It's *highly recommended to use the latest supported PHP version* for this project.
106 |
107 | See above note about [Reviving Ratchet](#reviving-ratchet) for newer PHP support.
108 |
109 | ## License
110 |
111 | MIT, see [LICENSE file](LICENSE).
112 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | Please report security issues to:
6 |
7 | * Chris Boden [cboden@gmail.com](cboden@gmail.com)
8 | * Matt Bonneau [matt@bonneau.net](matt@bonneau.net)
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cboden/ratchet"
3 | , "type": "library"
4 | , "description": "PHP WebSocket library"
5 | , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"]
6 | , "homepage": "http://socketo.me"
7 | , "license": "MIT"
8 | , "authors": [
9 | {
10 | "name": "Chris Boden"
11 | , "email": "cboden@gmail.com"
12 | , "role": "Developer"
13 | }
14 | , {
15 | "name": "Matt Bonneau"
16 | , "role": "Developer"
17 | }
18 | ]
19 | , "support": {
20 | "issues": "https://github.com/ratchetphp/Ratchet/issues"
21 | , "chat": "https://gitter.im/reactphp/reactphp"
22 | }
23 | , "autoload": {
24 | "psr-4": {
25 | "Ratchet\\": "src/Ratchet"
26 | }
27 | }
28 | , "require": {
29 | "php": ">=5.4.2"
30 | , "ratchet/rfc6455": "^0.3.1"
31 | , "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5"
32 | , "react/event-loop": "^1.0 || ^0.5 || ^0.4"
33 | , "guzzlehttp/psr7": "^1.7|^2.0"
34 | , "symfony/http-foundation": "^2.6|^3.0|^4.0|^5.0|^6.0|^7.0"
35 | , "symfony/routing": "^2.6|^3.0|^4.0|^5.0|^6.0|^7.0"
36 | }
37 | , "require-dev": {
38 | "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 | ./tests/unit/
13 |
14 |
15 |
16 |
17 | ./src/
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/phpunit.xml.legacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 | ./tests/unit/
11 |
12 |
13 |
14 |
15 | ./src/
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Ratchet/AbstractConnectionDecorator.php:
--------------------------------------------------------------------------------
1 | wrappedConn = $conn;
22 | }
23 |
24 | /**
25 | * @return ConnectionInterface
26 | */
27 | protected function getConnection() {
28 | return $this->wrappedConn;
29 | }
30 |
31 | public function __set($name, $value) {
32 | $this->wrappedConn->$name = $value;
33 | }
34 |
35 | public function __get($name) {
36 | return $this->wrappedConn->$name;
37 | }
38 |
39 | public function __isset($name) {
40 | return isset($this->wrappedConn->$name);
41 | }
42 |
43 | public function __unset($name) {
44 | unset($this->wrappedConn->$name);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Ratchet/App.php:
--------------------------------------------------------------------------------
1 | httpHost = $httpHost;
81 | $this->port = $port;
82 |
83 | // prefer SocketServer (reactphp/socket v1.9+) over legacy \React\Socket\Server
84 | $socket = class_exists('React\Socket\SocketServer') ? new SocketServer($address . ':' . $port, $context, $loop) : new LegacySocketServer($address . ':' . $port, $loop, $context);
85 |
86 | $this->routes = new RouteCollection;
87 | $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop);
88 |
89 | $policy = new FlashPolicy;
90 | $policy->addAllowedAccess($httpHost, 80);
91 | $policy->addAllowedAccess($httpHost, $port);
92 |
93 | if (80 == $port) {
94 | $flashUri = '0.0.0.0:843';
95 | } else {
96 | $flashUri = 8843;
97 | }
98 |
99 | // prefer SocketServer (reactphp/socket v1.9+) over legacy \React\Socket\Server
100 | $flashSock = class_exists('React\Socket\SocketServer') ? new SocketServer($flashUri, [], $loop) : new LegacySocketServer($flashUri, $loop);
101 | $this->flashServer = new IoServer($policy, $flashSock);
102 | }
103 |
104 | /**
105 | * Add an endpoint/application to the server
106 | * @param string $path The URI the client will connect to
107 | * @param ComponentInterface $controller Your application to server for the route. If not specified, assumed to be for a WebSocket
108 | * @param array $allowedOrigins An array of hosts allowed to connect (same host by default), ['*'] for any
109 | * @param string $httpHost Override the $httpHost variable provided in the __construct
110 | * @return ComponentInterface|WsServer
111 | */
112 | public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) {
113 | if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) {
114 | $decorated = $controller;
115 | } elseif ($controller instanceof WampServerInterface) {
116 | $decorated = new WsServer(new WampServer($controller));
117 | $decorated->enableKeepAlive($this->_server->loop);
118 | } elseif ($controller instanceof MessageComponentInterface || $controller instanceof WsMessageComponentInterface) {
119 | $decorated = new WsServer($controller);
120 | $decorated->enableKeepAlive($this->_server->loop);
121 | } else {
122 | $decorated = $controller;
123 | }
124 |
125 | if ($httpHost === null) {
126 | $httpHost = $this->httpHost;
127 | }
128 |
129 | $allowedOrigins = array_values($allowedOrigins);
130 | if (0 === count($allowedOrigins)) {
131 | $allowedOrigins[] = $httpHost;
132 | }
133 | if ('*' !== $allowedOrigins[0]) {
134 | $decorated = new OriginCheck($decorated, $allowedOrigins);
135 | }
136 |
137 | //allow origins in flash policy server
138 | if(empty($this->flashServer) === false) {
139 | foreach($allowedOrigins as $allowedOrgin) {
140 | $this->flashServer->app->addAllowedAccess($allowedOrgin, $this->port);
141 | }
142 | }
143 |
144 | $this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost, array(), array('GET')));
145 |
146 | return $decorated;
147 | }
148 |
149 | /**
150 | * Run the server by entering the event loop
151 | */
152 | public function run() {
153 | $this->_server->run();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Ratchet/ComponentInterface.php:
--------------------------------------------------------------------------------
1 | \Ratchet\VERSION
17 | ], $additional_headers));
18 |
19 | $conn->send(Message::toString($response));
20 | $conn->close();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Ratchet/Http/HttpRequestParser.php:
--------------------------------------------------------------------------------
1 | httpBuffer)) {
30 | $context->httpBuffer = '';
31 | }
32 |
33 | $context->httpBuffer .= $data;
34 |
35 | if (strlen($context->httpBuffer) > (int)$this->maxSize) {
36 | throw new \OverflowException("Maximum buffer size of {$this->maxSize} exceeded parsing HTTP header");
37 | }
38 |
39 | if ($this->isEom($context->httpBuffer)) {
40 | $request = $this->parse($context->httpBuffer);
41 |
42 | unset($context->httpBuffer);
43 |
44 | return $request;
45 | }
46 | }
47 |
48 | /**
49 | * Determine if the message has been buffered as per the HTTP specification
50 | * @param string $message
51 | * @return boolean
52 | */
53 | public function isEom($message) {
54 | return (boolean)strpos($message, static::EOM);
55 | }
56 |
57 | /**
58 | * @param string $headers
59 | * @return \Psr\Http\Message\RequestInterface
60 | */
61 | public function parse($headers) {
62 | return Message::parseRequest($headers);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Ratchet/Http/HttpServer.php:
--------------------------------------------------------------------------------
1 | _httpServer = $component;
26 | $this->_reqParser = new HttpRequestParser;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function onOpen(ConnectionInterface $conn) {
33 | $conn->httpHeadersReceived = false;
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function onMessage(ConnectionInterface $from, $msg) {
40 | if (true !== $from->httpHeadersReceived) {
41 | try {
42 | if (null === ($request = $this->_reqParser->onMessage($from, $msg))) {
43 | return;
44 | }
45 | } catch (\OverflowException $oe) {
46 | return $this->close($from, 413);
47 | }
48 |
49 | $from->httpHeadersReceived = true;
50 |
51 | return $this->_httpServer->onOpen($from, $request);
52 | }
53 |
54 | $this->_httpServer->onMessage($from, $msg);
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function onClose(ConnectionInterface $conn) {
61 | if ($conn->httpHeadersReceived) {
62 | $this->_httpServer->onClose($conn);
63 | }
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function onError(ConnectionInterface $conn, \Exception $e) {
70 | if ($conn->httpHeadersReceived) {
71 | $this->_httpServer->onError($conn, $e);
72 | } else {
73 | $this->close($conn, 500);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Ratchet/Http/HttpServerInterface.php:
--------------------------------------------------------------------------------
1 | _component = $component;
28 | $this->allowedOrigins += $allowed;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | #[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
35 | public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
36 | $header = (string)$request->getHeader('Origin')[0];
37 | $origin = parse_url($header, PHP_URL_HOST) ?: $header;
38 |
39 | if (!in_array($origin, $this->allowedOrigins)) {
40 | return $this->close($conn, 403);
41 | }
42 |
43 | return $this->_component->onOpen($conn, $request);
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | function onMessage(ConnectionInterface $from, $msg) {
50 | return $this->_component->onMessage($from, $msg);
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | function onClose(ConnectionInterface $conn) {
57 | return $this->_component->onClose($conn);
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | function onError(ConnectionInterface $conn, \Exception $e) {
64 | return $this->_component->onError($conn, $e);
65 | }
66 | }
--------------------------------------------------------------------------------
/src/Ratchet/Http/Router.php:
--------------------------------------------------------------------------------
1 | _matcher = $matcher;
22 | $this->_noopController = new NoOpHttpServerController;
23 | }
24 |
25 | /**
26 | * {@inheritdoc}
27 | * @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
28 | */
29 | #[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
30 | public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
31 | if (null === $request) {
32 | throw new \UnexpectedValueException('$request can not be null');
33 | }
34 |
35 | $conn->controller = $this->_noopController;
36 |
37 | $uri = $request->getUri();
38 |
39 | $context = $this->_matcher->getContext();
40 | $context->setMethod($request->getMethod());
41 | $context->setHost($uri->getHost());
42 |
43 | try {
44 | $route = $this->_matcher->match($uri->getPath());
45 | } catch (MethodNotAllowedException $nae) {
46 | return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods()));
47 | } catch (ResourceNotFoundException $nfe) {
48 | return $this->close($conn, 404);
49 | }
50 |
51 | if (is_string($route['_controller']) && class_exists($route['_controller'])) {
52 | $route['_controller'] = new $route['_controller'];
53 | }
54 |
55 | if (!($route['_controller'] instanceof HttpServerInterface)) {
56 | throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface');
57 | }
58 |
59 | $parameters = [];
60 | foreach($route as $key => $value) {
61 | if ((is_string($key)) && ('_' !== substr($key, 0, 1))) {
62 | $parameters[$key] = $value;
63 | }
64 | }
65 | $parameters = array_merge($parameters, Query::parse($uri->getQuery() ?: ''));
66 |
67 | $request = $request->withUri($uri->withQuery(Query::build($parameters)));
68 |
69 | $conn->controller = $route['_controller'];
70 | $conn->controller->onOpen($conn, $request);
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | public function onMessage(ConnectionInterface $from, $msg) {
77 | $from->controller->onMessage($from, $msg);
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function onClose(ConnectionInterface $conn) {
84 | if (isset($conn->controller)) {
85 | $conn->controller->onClose($conn);
86 | }
87 | }
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public function onError(ConnectionInterface $conn, \Exception $e) {
93 | if (isset($conn->controller)) {
94 | $conn->controller->onError($conn, $e);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Ratchet/MessageComponentInterface.php:
--------------------------------------------------------------------------------
1 | send($msg);
15 | }
16 |
17 | public function onClose(ConnectionInterface $conn) {
18 | }
19 |
20 | public function onError(ConnectionInterface $conn, \Exception $e) {
21 | $conn->close();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Ratchet/Server/FlashPolicy.php:
--------------------------------------------------------------------------------
1 | ';
23 |
24 | /**
25 | * Stores an array of allowed domains and their ports
26 | * @var array
27 | */
28 | protected $_access = array();
29 |
30 | /**
31 | * @var string
32 | */
33 | protected $_siteControl = '';
34 |
35 | /**
36 | * @var string
37 | */
38 | protected $_cache = '';
39 |
40 | /**
41 | * @var string
42 | */
43 | protected $_cacheValid = false;
44 |
45 | /**
46 | * Add a domain to an allowed access list.
47 | *
48 | * @param string $domain Specifies a requesting domain to be granted access. Both named domains and IP
49 | * addresses are acceptable values. Subdomains are considered different domains. A wildcard (*) can
50 | * be used to match all domains when used alone, or multiple domains (subdomains) when used as a
51 | * prefix for an explicit, second-level domain name separated with a dot (.)
52 | * @param string $ports A comma-separated list of ports or range of ports that a socket connection
53 | * is allowed to connect to. A range of ports is specified through a dash (-) between two port numbers.
54 | * Ranges can be used with individual ports when separated with a comma. A single wildcard (*) can
55 | * be used to allow all ports.
56 | * @param bool $secure
57 | * @throws \UnexpectedValueException
58 | * @return FlashPolicy
59 | */
60 | public function addAllowedAccess($domain, $ports = '*', $secure = false) {
61 | if (!$this->validateDomain($domain)) {
62 | throw new \UnexpectedValueException('Invalid domain');
63 | }
64 |
65 | if (!$this->validatePorts($ports)) {
66 | throw new \UnexpectedValueException('Invalid Port');
67 | }
68 |
69 | $this->_access[] = array($domain, $ports, (boolean)$secure);
70 | $this->_cacheValid = false;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * Removes all domains from the allowed access list.
77 | *
78 | * @return \Ratchet\Server\FlashPolicy
79 | */
80 | public function clearAllowedAccess() {
81 | $this->_access = array();
82 | $this->_cacheValid = false;
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable
89 | * domain policy files other than the master policy file located in the target domain's root and named
90 | * crossdomain.xml.
91 | *
92 | * @param string $permittedCrossDomainPolicies
93 | * @throws \UnexpectedValueException
94 | * @return FlashPolicy
95 | */
96 | public function setSiteControl($permittedCrossDomainPolicies = 'all') {
97 | if (!$this->validateSiteControl($permittedCrossDomainPolicies)) {
98 | throw new \UnexpectedValueException('Invalid site control set');
99 | }
100 |
101 | $this->_siteControl = $permittedCrossDomainPolicies;
102 | $this->_cacheValid = false;
103 |
104 | return $this;
105 | }
106 |
107 | /**
108 | * {@inheritdoc}
109 | */
110 | public function onOpen(ConnectionInterface $conn) {
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | public function onMessage(ConnectionInterface $from, $msg) {
117 | if (!$this->_cacheValid) {
118 | $this->_cache = $this->renderPolicy()->asXML();
119 | $this->_cacheValid = true;
120 | }
121 |
122 | $from->send($this->_cache . "\0");
123 | $from->close();
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | public function onClose(ConnectionInterface $conn) {
130 | }
131 |
132 | /**
133 | * {@inheritdoc}
134 | */
135 | public function onError(ConnectionInterface $conn, \Exception $e) {
136 | $conn->close();
137 | }
138 |
139 | /**
140 | * Builds the crossdomain file based on the template policy
141 | *
142 | * @throws \UnexpectedValueException
143 | * @return \SimpleXMLElement
144 | */
145 | public function renderPolicy() {
146 | $policy = new \SimpleXMLElement($this->_policy);
147 |
148 | $siteControl = $policy->addChild('site-control');
149 |
150 | if ($this->_siteControl == '') {
151 | $this->setSiteControl();
152 | }
153 |
154 | $siteControl->addAttribute('permitted-cross-domain-policies', $this->_siteControl);
155 |
156 | if (empty($this->_access)) {
157 | throw new \UnexpectedValueException('You must add a domain through addAllowedAccess()');
158 | }
159 |
160 | foreach ($this->_access as $access) {
161 | $tmp = $policy->addChild('allow-access-from');
162 | $tmp->addAttribute('domain', $access[0]);
163 | $tmp->addAttribute('to-ports', $access[1]);
164 | $tmp->addAttribute('secure', ($access[2] === true) ? 'true' : 'false');
165 | }
166 |
167 | return $policy;
168 | }
169 |
170 | /**
171 | * Make sure the proper site control was passed
172 | *
173 | * @param string $permittedCrossDomainPolicies
174 | * @return bool
175 | */
176 | public function validateSiteControl($permittedCrossDomainPolicies) {
177 | //'by-content-type' and 'by-ftp-filename' are not available for sockets
178 | return (bool)in_array($permittedCrossDomainPolicies, array('none', 'master-only', 'all'));
179 | }
180 |
181 | /**
182 | * Validate for proper domains (wildcards allowed)
183 | *
184 | * @param string $domain
185 | * @return bool
186 | */
187 | public function validateDomain($domain) {
188 | return (bool)preg_match("/^((http(s)?:\/\/)?([a-z0-9-_]+\.|\*\.)*([a-z0-9-_\.]+)|\*)$/i", $domain);
189 | }
190 |
191 | /**
192 | * Make sure valid ports were passed
193 | *
194 | * @param string $port
195 | * @return bool
196 | */
197 | public function validatePorts($port) {
198 | return (bool)preg_match('/^(\*|(\d+[,-]?)*\d+)$/', $port);
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/Ratchet/Server/IoConnection.php:
--------------------------------------------------------------------------------
1 | conn = $conn;
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function send($data) {
28 | $this->conn->write($data);
29 |
30 | return $this;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function close() {
37 | $this->conn->end();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Ratchet/Server/IoServer.php:
--------------------------------------------------------------------------------
1 | loop = $loop;
51 | $this->app = $app;
52 | $this->socket = $socket;
53 |
54 | $socket->on('connection', array($this, 'handleConnect'));
55 | }
56 |
57 | /**
58 | * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received
59 | * @param int $port The port to server sockets on
60 | * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any)
61 | * @return IoServer
62 | */
63 | public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') {
64 | // prefer default Loop (reactphp/event-loop v1.2+) over legacy \React\EventLoop\Factory
65 | $loop = class_exists('React\EventLoop\Loop') ? Loop::get() : LegacyLoopFactory::create();
66 |
67 | // prefer SocketServer (reactphp/socket v1.9+) over legacy \React\Socket\Server
68 | $socket = class_exists('React\Socket\SocketServer') ? new SocketServer($address . ':' . $port, [], $loop) : new LegacySocketServer($address . ':' . $port, $loop);
69 |
70 | return new static($component, $socket, $loop);
71 | }
72 |
73 | /**
74 | * Run the application by entering the event loop
75 | * @throws \RuntimeException If a loop was not previously specified
76 | */
77 | public function run() {
78 | if (null === $this->loop) {
79 | throw new \RuntimeException("A React Loop was not provided during instantiation");
80 | }
81 |
82 | // @codeCoverageIgnoreStart
83 | $this->loop->run();
84 | // @codeCoverageIgnoreEnd
85 | }
86 |
87 | /**
88 | * Triggered when a new connection is received from React
89 | * @param \React\Socket\ConnectionInterface $conn
90 | */
91 | public function handleConnect(SocketConnection $conn) {
92 | // assign dynamic `$decor` property used by Ratchet without raising notice on PHP 8.2+
93 | // need this hack because we can't use `#[\AllowDynamicProperties]` on vendor code
94 | set_error_handler(function () { }, E_DEPRECATED);
95 | $conn->decor = new IoConnection($conn);
96 | restore_error_handler();
97 |
98 | $conn->decor->resourceId = (int)$conn->stream;
99 |
100 | $uri = $conn->getRemoteAddress();
101 | $conn->decor->remoteAddress = trim(
102 | parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST),
103 | '[]'
104 | );
105 |
106 | $this->app->onOpen($conn->decor);
107 |
108 | $conn->on('data', function ($data) use ($conn) {
109 | $this->handleData($data, $conn);
110 | });
111 | $conn->on('close', function () use ($conn) {
112 | $this->handleEnd($conn);
113 | });
114 | $conn->on('error', function (\Exception $e) use ($conn) {
115 | $this->handleError($e, $conn);
116 | });
117 | }
118 |
119 | /**
120 | * Data has been received from React
121 | * @param string $data
122 | * @param \React\Socket\ConnectionInterface $conn
123 | */
124 | public function handleData($data, $conn) {
125 | try {
126 | $this->app->onMessage($conn->decor, $data);
127 | } catch (\Exception $e) {
128 | $this->handleError($e, $conn);
129 | }
130 | }
131 |
132 | /**
133 | * A connection has been closed by React
134 | * @param \React\Socket\ConnectionInterface $conn
135 | */
136 | public function handleEnd($conn) {
137 | try {
138 | $this->app->onClose($conn->decor);
139 | } catch (\Exception $e) {
140 | $this->handleError($e, $conn);
141 | }
142 |
143 | unset($conn->decor);
144 | }
145 |
146 | /**
147 | * An error has occurred, let the listening application know
148 | * @param \Exception $e
149 | * @param \React\Socket\ConnectionInterface $conn
150 | */
151 | public function handleError(\Exception $e, $conn) {
152 | $this->app->onError($conn->decor, $e);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Ratchet/Server/IpBlackList.php:
--------------------------------------------------------------------------------
1 | _decorating = $component;
22 | }
23 |
24 | /**
25 | * Add an address to the blacklist that will not be allowed to connect to your application
26 | * @param string $ip IP address to block from connecting to your application
27 | * @return IpBlackList
28 | */
29 | public function blockAddress($ip) {
30 | $this->_blacklist[$ip] = true;
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * Unblock an address so they can access your application again
37 | * @param string $ip IP address to unblock from connecting to your application
38 | * @return IpBlackList
39 | */
40 | public function unblockAddress($ip) {
41 | if (isset($this->_blacklist[$this->filterAddress($ip)])) {
42 | unset($this->_blacklist[$this->filterAddress($ip)]);
43 | }
44 |
45 | return $this;
46 | }
47 |
48 | /**
49 | * @param string $address
50 | * @return bool
51 | */
52 | public function isBlocked($address) {
53 | return (isset($this->_blacklist[$this->filterAddress($address)]));
54 | }
55 |
56 | /**
57 | * Get an array of all the addresses blocked
58 | * @return array
59 | */
60 | public function getBlockedAddresses() {
61 | return array_keys($this->_blacklist);
62 | }
63 |
64 | /**
65 | * @param string $address
66 | * @return string
67 | */
68 | public function filterAddress($address) {
69 | if (strstr($address, ':') && substr_count($address, '.') == 3) {
70 | list($address, $port) = explode(':', $address);
71 | }
72 |
73 | return $address;
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | function onOpen(ConnectionInterface $conn) {
80 | if ($this->isBlocked($conn->remoteAddress)) {
81 | return $conn->close();
82 | }
83 |
84 | return $this->_decorating->onOpen($conn);
85 | }
86 |
87 | /**
88 | * {@inheritdoc}
89 | */
90 | function onMessage(ConnectionInterface $from, $msg) {
91 | return $this->_decorating->onMessage($from, $msg);
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | function onClose(ConnectionInterface $conn) {
98 | if (!$this->isBlocked($conn->remoteAddress)) {
99 | $this->_decorating->onClose($conn);
100 | }
101 | }
102 |
103 | /**
104 | * {@inheritdoc}
105 | */
106 | function onError(ConnectionInterface $conn, \Exception $e) {
107 | if (!$this->isBlocked($conn->remoteAddress)) {
108 | $this->_decorating->onError($conn, $e);
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Serialize/HandlerInterface.php:
--------------------------------------------------------------------------------
1 | $bucketData) {
15 | $preSerialized[] = $bucket . '|' . serialize($bucketData);
16 | }
17 | $serialized = implode('', $preSerialized);
18 | }
19 |
20 | return $serialized;
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | * @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net
26 | * @throws \UnexpectedValueException If there is a problem parsing the data
27 | */
28 | public function unserialize($raw) {
29 | $returnData = array();
30 | $offset = 0;
31 |
32 | while ($offset < strlen($raw)) {
33 | if (!strstr(substr($raw, $offset), "|")) {
34 | throw new \UnexpectedValueException("invalid data, remaining: " . substr($raw, $offset));
35 | }
36 |
37 | $pos = strpos($raw, "|", $offset);
38 | $num = $pos - $offset;
39 | $varname = substr($raw, $offset, $num);
40 | $offset += $num + 1;
41 |
42 | // try to unserialize one piece of data from current offset, ignoring any warnings for trailing data on PHP 8.3+
43 | // @link https://wiki.php.net/rfc/unserialize_warn_on_trailing_data
44 | $data = @unserialize(substr($raw, $offset));
45 |
46 | $returnData[$varname] = $data;
47 | $offset += strlen(serialize($data));
48 | }
49 |
50 | return $returnData;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/SessionProvider.php:
--------------------------------------------------------------------------------
1 | _app = $app;
52 | $this->_handler = $handler;
53 | $this->_null = new NullSessionHandler;
54 |
55 | ini_set('session.auto_start', 0);
56 | ini_set('session.cache_limiter', '');
57 | ini_set('session.use_cookies', 0);
58 |
59 | $this->setOptions($options);
60 |
61 | if (null === $serializer) {
62 | $serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh?
63 | if (!class_exists($serialClass)) {
64 | throw new \RuntimeException('Unable to parse session serialize handler');
65 | }
66 |
67 | $serializer = new $serialClass;
68 | }
69 |
70 | $this->_serializer = $serializer;
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | #[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
77 | public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
78 | $sessionName = ini_get('session.name');
79 |
80 | $id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) {
81 | if ($accumulator) {
82 | return $accumulator;
83 | }
84 |
85 | $crumbs = $this->parseCookie($cookie);
86 |
87 | return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false;
88 | }, false);
89 |
90 | if (null === $request || false === $id) {
91 | $saveHandler = $this->_null;
92 | $id = '';
93 | } else {
94 | $saveHandler = $this->_handler;
95 | }
96 |
97 | $conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer));
98 |
99 | if (ini_get('session.auto_start')) {
100 | $conn->Session->start();
101 | }
102 |
103 | return $this->_app->onOpen($conn, $request);
104 | }
105 |
106 | /**
107 | * {@inheritdoc}
108 | */
109 | function onMessage(ConnectionInterface $from, $msg) {
110 | return $this->_app->onMessage($from, $msg);
111 | }
112 |
113 | /**
114 | * {@inheritdoc}
115 | */
116 | function onClose(ConnectionInterface $conn) {
117 | // "close" session for Connection
118 |
119 | return $this->_app->onClose($conn);
120 | }
121 |
122 | /**
123 | * {@inheritdoc}
124 | */
125 | function onError(ConnectionInterface $conn, \Exception $e) {
126 | return $this->_app->onError($conn, $e);
127 | }
128 |
129 | /**
130 | * Set all the php session. ini options
131 | * © Symfony
132 | * @param array $options
133 | * @return array
134 | */
135 | protected function setOptions(array $options) {
136 | $all = array(
137 | 'auto_start', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
138 | 'cookie_lifetime', 'cookie_path', 'cookie_secure',
139 | 'entropy_file', 'entropy_length', 'gc_divisor',
140 | 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
141 | 'hash_function', 'name', 'referer_check',
142 | 'serialize_handler', 'use_cookies',
143 | 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
144 | 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
145 | 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags'
146 | );
147 |
148 | foreach ($all as $key) {
149 | if (!array_key_exists($key, $options)) {
150 | $options[$key] = ini_get("session.{$key}");
151 | } else {
152 | ini_set("session.{$key}", $options[$key]);
153 | }
154 | }
155 |
156 | return $options;
157 | }
158 |
159 | /**
160 | * @param string $langDef Input to convert
161 | * @return string
162 | */
163 | protected function toClassCase($langDef) {
164 | return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef)));
165 | }
166 |
167 | /**
168 | * Taken from Guzzle3
169 | */
170 | private static $cookieParts = array(
171 | 'domain' => 'Domain',
172 | 'path' => 'Path',
173 | 'max_age' => 'Max-Age',
174 | 'expires' => 'Expires',
175 | 'version' => 'Version',
176 | 'secure' => 'Secure',
177 | 'port' => 'Port',
178 | 'discard' => 'Discard',
179 | 'comment' => 'Comment',
180 | 'comment_url' => 'Comment-Url',
181 | 'http_only' => 'HttpOnly'
182 | );
183 |
184 | /**
185 | * Taken from Guzzle3
186 | */
187 | private function parseCookie($cookie, $host = null, $path = null, $decode = false) {
188 | // Explode the cookie string using a series of semicolons
189 | $pieces = array_filter(array_map('trim', explode(';', $cookie)));
190 |
191 | // The name of the cookie (first kvp) must include an equal sign.
192 | if (empty($pieces) || !strpos($pieces[0], '=')) {
193 | return false;
194 | }
195 |
196 | // Create the default return array
197 | $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array(
198 | 'cookies' => array(),
199 | 'data' => array(),
200 | 'path' => $path ?: '/',
201 | 'http_only' => false,
202 | 'discard' => false,
203 | 'domain' => $host
204 | ));
205 | $foundNonCookies = 0;
206 |
207 | // Add the cookie pieces into the parsed data array
208 | foreach ($pieces as $part) {
209 |
210 | $cookieParts = explode('=', $part, 2);
211 | $key = trim($cookieParts[0]);
212 |
213 | if (count($cookieParts) == 1) {
214 | // Can be a single value (e.g. secure, httpOnly)
215 | $value = true;
216 | } else {
217 | // Be sure to strip wrapping quotes
218 | $value = trim($cookieParts[1], " \n\r\t\0\x0B\"");
219 | if ($decode) {
220 | $value = urldecode($value);
221 | }
222 | }
223 |
224 | // Only check for non-cookies when cookies have been found
225 | if (!empty($data['cookies'])) {
226 | foreach (self::$cookieParts as $mapValue => $search) {
227 | if (!strcasecmp($search, $key)) {
228 | $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value;
229 | $foundNonCookies++;
230 | continue 2;
231 | }
232 | }
233 | }
234 |
235 | // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a
236 | // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data.
237 | $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value;
238 | }
239 |
240 | // Calculate the expires date
241 | if (!$data['expires'] && $data['max_age']) {
242 | $data['expires'] = time() + (int) $data['max_age'];
243 | }
244 |
245 | return $data;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/Proxy/VirtualProxy.php:
--------------------------------------------------------------------------------
1 | 80200 && (new \ReflectionMethod('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy','setId'))->hasReturnType()) {
6 | // alias to class for Symfony 7 on PHP 8.2+ using native types like `setId(string $id): void`
7 | class_alias(__NAMESPACE__ . '\\VirtualProxyForSymfony7', __NAMESPACE__ . '\\VirtualProxy');
8 | } elseif (PHP_VERSION_ID > 80000 && (new \ReflectionMethod('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy','getId'))->hasReturnType()) {
9 | // alias to class for Symfony 6 on PHP 8+ using native types like `getId(): string`
10 | class_alias(__NAMESPACE__ . '\\VirtualProxyForSymfony6', __NAMESPACE__ . '\\VirtualProxy');
11 | } else {
12 | // fall back to class without native types
13 |
14 | class VirtualProxy extends SessionHandlerProxy {
15 | /**
16 | * @var string
17 | */
18 | protected $_sessionId;
19 |
20 | /**
21 | * @var string
22 | */
23 | protected $_sessionName;
24 |
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function __construct(\SessionHandlerInterface $handler) {
29 | parent::__construct($handler);
30 |
31 | $this->saveHandlerName = 'user';
32 | $this->_sessionName = ini_get('session.name');
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function getId() {
39 | return $this->_sessionId;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function setId($id) {
46 | $this->_sessionId = $id;
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | public function getName() {
53 | return $this->_sessionName;
54 | }
55 |
56 | /**
57 | * DO NOT CALL THIS METHOD
58 | * @internal
59 | */
60 | public function setName($name) {
61 | throw new \RuntimeException("Can not change session name in VirtualProxy");
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/Proxy/VirtualProxyForSymfony6.php:
--------------------------------------------------------------------------------
1 | saveHandlerName = 'user';
29 | $this->_sessionName = ini_get('session.name');
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function getId(): string {
36 | return $this->_sessionId;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function setId($id) {
43 | $this->_sessionId = $id;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function getName(): string {
50 | return $this->_sessionName;
51 | }
52 |
53 | /**
54 | * DO NOT CALL THIS METHOD
55 | * @internal
56 | */
57 | public function setName($name) {
58 | throw new \RuntimeException("Can not change session name in VirtualProxy");
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/Proxy/VirtualProxyForSymfony7.php:
--------------------------------------------------------------------------------
1 | saveHandlerName = 'user';
29 | $this->_sessionName = ini_get('session.name');
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function getId(): string {
36 | return $this->_sessionId;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function setId(string $id): void {
43 | $this->_sessionId = $id;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function getName(): string {
50 | return $this->_sessionName;
51 | }
52 |
53 | /**
54 | * DO NOT CALL THIS METHOD
55 | * @internal
56 | */
57 | public function setName(string $name): void {
58 | throw new \RuntimeException("Can not change session name in VirtualProxy");
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/VirtualSessionStorage.php:
--------------------------------------------------------------------------------
1 | 80200 && (new \ReflectionMethod('Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage','save'))->hasReturnType()) {
8 | // alias to class for Symfony 7 on PHP 8.2+ using native types like `save(): void`
9 | class_alias(__NAMESPACE__ . '\\VirtualSessionStorageForSymfony7', __NAMESPACE__ . '\\VirtualSessionStorage');
10 | } elseif (PHP_VERSION_ID > 80000 && (new \ReflectionMethod('Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage','start'))->hasReturnType()) {
11 | // alias to class for Symfony 6 on PHP 8+ using native types like `start(): bool`
12 | class_alias(__NAMESPACE__ . '\\VirtualSessionStorageForSymfony6', __NAMESPACE__ . '\\VirtualSessionStorage');
13 | } else {
14 | // fall back to class without native types
15 |
16 | class VirtualSessionStorage extends NativeSessionStorage {
17 | /**
18 | * @var \Ratchet\Session\Serialize\HandlerInterface
19 | */
20 | protected $_serializer;
21 |
22 | /**
23 | * @param \SessionHandlerInterface $handler
24 | * @param string $sessionId The ID of the session to retrieve
25 | * @param \Ratchet\Session\Serialize\HandlerInterface $serializer
26 | */
27 | public function __construct(\SessionHandlerInterface $handler, $sessionId, HandlerInterface $serializer) {
28 | $this->setSaveHandler($handler);
29 | $this->saveHandler->setId($sessionId);
30 | $this->_serializer = $serializer;
31 | $this->setMetadataBag(null);
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function start() {
38 | if ($this->started && !$this->closed) {
39 | return true;
40 | }
41 |
42 | // You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use
43 | // pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object
44 | // in the constructor. The method arguments are filled with the values, which are also used by the symfony
45 | // framework in this case. This must not be the best choice, but it works.
46 | $this->saveHandler->open(session_save_path(), session_name());
47 |
48 | $rawData = $this->saveHandler->read($this->saveHandler->getId());
49 | $sessionData = $this->_serializer->unserialize($rawData);
50 |
51 | $this->loadSession($sessionData);
52 |
53 | if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
54 | $this->saveHandler->setActive(false);
55 | }
56 |
57 | return true;
58 | }
59 |
60 | /**
61 | * {@inheritdoc}
62 | */
63 | public function regenerate($destroy = false, $lifetime = null) {
64 | // .. ?
65 | return false;
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | */
71 | public function save() {
72 | // get the data from the bags?
73 | // serialize the data
74 | // save the data using the saveHandler
75 | // $this->saveHandler->write($this->saveHandler->getId(),
76 |
77 | if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) {
78 | $this->saveHandler->setActive(false);
79 | }
80 |
81 | $this->closed = true;
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function setSaveHandler($saveHandler = null) {
88 | if (!($saveHandler instanceof \SessionHandlerInterface)) {
89 | throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface');
90 | }
91 |
92 | if (!($saveHandler instanceof VirtualProxy)) {
93 | $saveHandler = new VirtualProxy($saveHandler);
94 | }
95 |
96 | $this->saveHandler = $saveHandler;
97 | }
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/VirtualSessionStorageForSymfony6.php:
--------------------------------------------------------------------------------
1 | setSaveHandler($handler);
26 | $this->saveHandler->setId($sessionId);
27 | $this->_serializer = $serializer;
28 | $this->setMetadataBag(null);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function start(): bool {
35 | if ($this->started && !$this->closed) {
36 | return true;
37 | }
38 |
39 | // You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use
40 | // pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object
41 | // in the constructor. The method arguments are filled with the values, which are also used by the symfony
42 | // framework in this case. This must not be the best choice, but it works.
43 | $this->saveHandler->open(session_save_path(), session_name());
44 |
45 | $rawData = $this->saveHandler->read($this->saveHandler->getId());
46 | $sessionData = $this->_serializer->unserialize($rawData);
47 |
48 | $this->loadSession($sessionData);
49 |
50 | if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
51 | $this->saveHandler->setActive(false);
52 | }
53 |
54 | return true;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function regenerate(bool $destroy = false, ?int $lifetime = null): bool {
61 | // .. ?
62 | return false;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function save() {
69 | // get the data from the bags?
70 | // serialize the data
71 | // save the data using the saveHandler
72 | // $this->saveHandler->write($this->saveHandler->getId(),
73 |
74 | if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) {
75 | $this->saveHandler->setActive(false);
76 | }
77 |
78 | $this->closed = true;
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public function setSaveHandler($saveHandler = null) {
85 | if (!($saveHandler instanceof \SessionHandlerInterface)) {
86 | throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface');
87 | }
88 |
89 | if (!($saveHandler instanceof VirtualProxy)) {
90 | $saveHandler = new VirtualProxy($saveHandler);
91 | }
92 |
93 | $this->saveHandler = $saveHandler;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Ratchet/Session/Storage/VirtualSessionStorageForSymfony7.php:
--------------------------------------------------------------------------------
1 | setSaveHandler($handler);
27 | $this->saveHandler->setId($sessionId);
28 | $this->_serializer = $serializer;
29 | $this->setMetadataBag(null);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function start(): bool {
36 | if ($this->started && !$this->closed) {
37 | return true;
38 | }
39 |
40 | // You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use
41 | // pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object
42 | // in the constructor. The method arguments are filled with the values, which are also used by the symfony
43 | // framework in this case. This must not be the best choice, but it works.
44 | $this->saveHandler->open(session_save_path(), session_name());
45 |
46 | $rawData = $this->saveHandler->read($this->saveHandler->getId());
47 | $sessionData = $this->_serializer->unserialize($rawData);
48 |
49 | $this->loadSession($sessionData);
50 |
51 | if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) {
52 | $this->saveHandler->setActive(false);
53 | }
54 |
55 | return true;
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function regenerate(bool $destroy = false, ?int $lifetime = null): bool {
62 | // .. ?
63 | return false;
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function save(): void {
70 | // get the data from the bags?
71 | // serialize the data
72 | // save the data using the saveHandler
73 | // $this->saveHandler->write($this->saveHandler->getId(),
74 |
75 | if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) {
76 | $this->saveHandler->setActive(false);
77 | }
78 |
79 | $this->closed = true;
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function setSaveHandler(AbstractProxy|\SessionHandlerInterface|null $saveHandler): void {
86 | if (!($saveHandler instanceof \SessionHandlerInterface)) {
87 | throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface');
88 | }
89 |
90 | if (!($saveHandler instanceof VirtualProxy)) {
91 | $saveHandler = new VirtualProxy($saveHandler);
92 | }
93 |
94 | $this->saveHandler = $saveHandler;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/Exception.php:
--------------------------------------------------------------------------------
1 | _decorating = $serverComponent;
53 | $this->connections = new \SplObjectStorage;
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function getSubProtocols() {
60 | if ($this->_decorating instanceof WsServerInterface) {
61 | $subs = $this->_decorating->getSubProtocols();
62 | $subs[] = 'wamp';
63 |
64 | return $subs;
65 | }
66 |
67 | return ['wamp'];
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function onOpen(ConnectionInterface $conn) {
74 | $decor = new WampConnection($conn);
75 | $this->connections->attach($conn, $decor);
76 |
77 | $this->_decorating->onOpen($decor);
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | * @throws \Ratchet\Wamp\Exception
83 | * @throws \Ratchet\Wamp\JsonException
84 | */
85 | public function onMessage(ConnectionInterface $from, $msg) {
86 | $from = $this->connections[$from];
87 |
88 | if (null === ($json = @json_decode($msg, true))) {
89 | throw new JsonException;
90 | }
91 |
92 | if (!is_array($json) || $json !== array_values($json)) {
93 | throw new Exception("Invalid WAMP message format");
94 | }
95 |
96 | if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) {
97 | throw new Exception('Invalid Topic, must be a string');
98 | }
99 |
100 | switch ($json[0]) {
101 | case static::MSG_PREFIX:
102 | $from->WAMP->prefixes[$json[1]] = $json[2];
103 | break;
104 |
105 | case static::MSG_CALL:
106 | array_shift($json);
107 | $callID = array_shift($json);
108 | $procURI = array_shift($json);
109 |
110 | if (count($json) == 1 && is_array($json[0])) {
111 | $json = $json[0];
112 | }
113 |
114 | $this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json);
115 | break;
116 |
117 | case static::MSG_SUBSCRIBE:
118 | $this->_decorating->onSubscribe($from, $from->getUri($json[1]));
119 | break;
120 |
121 | case static::MSG_UNSUBSCRIBE:
122 | $this->_decorating->onUnSubscribe($from, $from->getUri($json[1]));
123 | break;
124 |
125 | case static::MSG_PUBLISH:
126 | $exclude = (array_key_exists(3, $json) ? $json[3] : null);
127 | if (!is_array($exclude)) {
128 | if (true === (boolean)$exclude) {
129 | $exclude = [$from->WAMP->sessionId];
130 | } else {
131 | $exclude = [];
132 | }
133 | }
134 |
135 | $eligible = (array_key_exists(4, $json) ? $json[4] : []);
136 |
137 | $this->_decorating->onPublish($from, $from->getUri($json[1]), $json[2], $exclude, $eligible);
138 | break;
139 |
140 | default:
141 | throw new Exception('Invalid WAMP message type');
142 | }
143 | }
144 |
145 | /**
146 | * {@inheritdoc}
147 | */
148 | public function onClose(ConnectionInterface $conn) {
149 | $decor = $this->connections[$conn];
150 | $this->connections->detach($conn);
151 |
152 | $this->_decorating->onClose($decor);
153 | }
154 |
155 | /**
156 | * {@inheritdoc}
157 | */
158 | public function onError(ConnectionInterface $conn, \Exception $e) {
159 | return $this->_decorating->onError($this->connections[$conn], $e);
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/Topic.php:
--------------------------------------------------------------------------------
1 | id = $topicId;
18 | $this->subscribers = new \SplObjectStorage;
19 | }
20 |
21 | /**
22 | * @return string
23 | */
24 | public function getId() {
25 | return $this->id;
26 | }
27 |
28 | public function __toString() {
29 | return $this->getId();
30 | }
31 |
32 | /**
33 | * Send a message to all the connections in this topic
34 | * @param string|array $msg Payload to publish
35 | * @param array $exclude A list of session IDs the message should be excluded from (blacklist)
36 | * @param array $eligible A list of session Ids the message should be send to (whitelist)
37 | * @return Topic The same Topic object to chain
38 | */
39 | public function broadcast($msg, array $exclude = array(), array $eligible = array()) {
40 | $useEligible = (bool)count($eligible);
41 | foreach ($this->subscribers as $client) {
42 | if (in_array($client->WAMP->sessionId, $exclude)) {
43 | continue;
44 | }
45 |
46 | if ($useEligible && !in_array($client->WAMP->sessionId, $eligible)) {
47 | continue;
48 | }
49 |
50 | $client->event($this->id, $msg);
51 | }
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * @param WampConnection $conn
58 | * @return boolean
59 | */
60 | public function has(ConnectionInterface $conn) {
61 | return $this->subscribers->contains($conn);
62 | }
63 |
64 | /**
65 | * @param WampConnection $conn
66 | * @return Topic
67 | */
68 | public function add(ConnectionInterface $conn) {
69 | $this->subscribers->attach($conn);
70 |
71 | return $this;
72 | }
73 |
74 | /**
75 | * @param WampConnection $conn
76 | * @return Topic
77 | */
78 | public function remove(ConnectionInterface $conn) {
79 | if ($this->subscribers->contains($conn)) {
80 | $this->subscribers->detach($conn);
81 | }
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * {@inheritdoc}
88 | */
89 | #[\ReturnTypeWillChange]
90 | public function getIterator() {
91 | return $this->subscribers;
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | #[\ReturnTypeWillChange]
98 | public function count() {
99 | return $this->subscribers->count();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/TopicManager.php:
--------------------------------------------------------------------------------
1 | app = $app;
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function onOpen(ConnectionInterface $conn) {
25 | $conn->WAMP->subscriptions = new \SplObjectStorage;
26 | $this->app->onOpen($conn);
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
33 | $this->app->onCall($conn, $id, $this->getTopic($topic), $params);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function onSubscribe(ConnectionInterface $conn, $topic) {
40 | $topicObj = $this->getTopic($topic);
41 |
42 | if ($conn->WAMP->subscriptions->contains($topicObj)) {
43 | return;
44 | }
45 |
46 | $this->topicLookup[$topic]->add($conn);
47 | $conn->WAMP->subscriptions->attach($topicObj);
48 | $this->app->onSubscribe($conn, $topicObj);
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function onUnsubscribe(ConnectionInterface $conn, $topic) {
55 | $topicObj = $this->getTopic($topic);
56 |
57 | if (!$conn->WAMP->subscriptions->contains($topicObj)) {
58 | return;
59 | }
60 |
61 | $this->cleanTopic($topicObj, $conn);
62 |
63 | $this->app->onUnsubscribe($conn, $topicObj);
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
70 | $this->app->onPublish($conn, $this->getTopic($topic), $event, $exclude, $eligible);
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | public function onClose(ConnectionInterface $conn) {
77 | $this->app->onClose($conn);
78 |
79 | foreach ($this->topicLookup as $topic) {
80 | $this->cleanTopic($topic, $conn);
81 | }
82 | }
83 |
84 | /**
85 | * {@inheritdoc}
86 | */
87 | public function onError(ConnectionInterface $conn, \Exception $e) {
88 | $this->app->onError($conn, $e);
89 | }
90 |
91 | /**
92 | * {@inheritdoc}
93 | */
94 | public function getSubProtocols() {
95 | if ($this->app instanceof WsServerInterface) {
96 | return $this->app->getSubProtocols();
97 | }
98 |
99 | return array();
100 | }
101 |
102 | /**
103 | * @param string
104 | * @return Topic
105 | */
106 | protected function getTopic($topic) {
107 | if (!array_key_exists($topic, $this->topicLookup)) {
108 | $this->topicLookup[$topic] = new Topic($topic);
109 | }
110 |
111 | return $this->topicLookup[$topic];
112 | }
113 |
114 | protected function cleanTopic(Topic $topic, ConnectionInterface $conn) {
115 | if ($conn->WAMP->subscriptions->contains($topic)) {
116 | $conn->WAMP->subscriptions->detach($topic);
117 | }
118 |
119 | $this->topicLookup[$topic->getId()]->remove($conn);
120 |
121 | if (0 === $topic->count()) {
122 | unset($this->topicLookup[$topic->getId()]);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/WampConnection.php:
--------------------------------------------------------------------------------
1 | WAMP = new \StdClass;
20 | $this->WAMP->sessionId = str_replace('.', '', uniqid(mt_rand(), true));
21 | $this->WAMP->prefixes = array();
22 |
23 | $this->send(json_encode(array(WAMP::MSG_WELCOME, $this->WAMP->sessionId, 1, \Ratchet\VERSION)));
24 | }
25 |
26 | /**
27 | * Successfully respond to a call made by the client
28 | * @param string $id The unique ID given by the client to respond to
29 | * @param array $data an object or array
30 | * @return WampConnection
31 | */
32 | public function callResult($id, $data = array()) {
33 | return $this->send(json_encode(array(WAMP::MSG_CALL_RESULT, $id, $data)));
34 | }
35 |
36 | /**
37 | * Respond with an error to a client call
38 | * @param string $id The unique ID given by the client to respond to
39 | * @param string $errorUri The URI given to identify the specific error
40 | * @param string $desc A developer-oriented description of the error
41 | * @param string $details An optional human readable detail message to send back
42 | * @return WampConnection
43 | */
44 | public function callError($id, $errorUri, $desc = '', $details = null) {
45 | if ($errorUri instanceof Topic) {
46 | $errorUri = (string)$errorUri;
47 | }
48 |
49 | $data = array(WAMP::MSG_CALL_ERROR, $id, $errorUri, $desc);
50 |
51 | if (null !== $details) {
52 | $data[] = $details;
53 | }
54 |
55 | return $this->send(json_encode($data));
56 | }
57 |
58 | /**
59 | * @param string $topic The topic to broadcast to
60 | * @param mixed $msg Data to send with the event. Anything that is json'able
61 | * @return WampConnection
62 | */
63 | public function event($topic, $msg) {
64 | return $this->send(json_encode(array(WAMP::MSG_EVENT, (string)$topic, $msg)));
65 | }
66 |
67 | /**
68 | * @param string $curie
69 | * @param string $uri
70 | * @return WampConnection
71 | */
72 | public function prefix($curie, $uri) {
73 | $this->WAMP->prefixes[$curie] = (string)$uri;
74 |
75 | return $this->send(json_encode(array(WAMP::MSG_PREFIX, $curie, (string)$uri)));
76 | }
77 |
78 | /**
79 | * Get the full request URI from the connection object if a prefix has been established for it
80 | * @param string $uri
81 | * @return string
82 | */
83 | public function getUri($uri) {
84 | $curieSeperator = ':';
85 |
86 | if (preg_match('/http(s*)\:\/\//', $uri) == false) {
87 | if (strpos($uri, $curieSeperator) !== false) {
88 | list($prefix, $action) = explode($curieSeperator, $uri);
89 |
90 | if(isset($this->WAMP->prefixes[$prefix]) === true){
91 | return $this->WAMP->prefixes[$prefix] . '#' . $action;
92 | }
93 | }
94 | }
95 |
96 | return $uri;
97 | }
98 |
99 | /**
100 | * @internal
101 | */
102 | public function send($data) {
103 | $this->getConnection()->send($data);
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | */
111 | public function close($opt = null) {
112 | $this->getConnection()->close($opt);
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/WampServer.php:
--------------------------------------------------------------------------------
1 | wampProtocol = new ServerProtocol(new TopicManager($app));
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function onOpen(ConnectionInterface $conn) {
33 | $this->wampProtocol->onOpen($conn);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | public function onMessage(ConnectionInterface $conn, $msg) {
40 | try {
41 | $this->wampProtocol->onMessage($conn, $msg);
42 | } catch (Exception $we) {
43 | $conn->close(1007);
44 | }
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function onClose(ConnectionInterface $conn) {
51 | $this->wampProtocol->onClose($conn);
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function onError(ConnectionInterface $conn, \Exception $e) {
58 | $this->wampProtocol->onError($conn, $e);
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function getSubProtocols() {
65 | return $this->wampProtocol->getSubProtocols();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Ratchet/Wamp/WampServerInterface.php:
--------------------------------------------------------------------------------
1 | connection = $conn;
18 | $this->buffer = $buffer;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Ratchet/WebSocket/MessageCallableInterface.php:
--------------------------------------------------------------------------------
1 | WebSocket->closing) {
17 | if (!($msg instanceof DataInterface)) {
18 | $msg = new Frame($msg);
19 | }
20 |
21 | $this->getConnection()->send($msg->getContents());
22 | }
23 |
24 | return $this;
25 | }
26 |
27 | /**
28 | * @param int|\Ratchet\RFC6455\Messaging\DataInterface
29 | */
30 | public function close($code = 1000) {
31 | if ($this->WebSocket->closing) {
32 | return;
33 | }
34 |
35 | if ($code instanceof DataInterface) {
36 | $this->send($code);
37 | } else {
38 | $this->send(new Frame(pack('n', $code), true, Frame::OP_CLOSE));
39 | }
40 |
41 | $this->getConnection()->close();
42 |
43 | $this->WebSocket->closing = true;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Ratchet/WebSocket/WsServer.php:
--------------------------------------------------------------------------------
1 | msgCb = function(ConnectionInterface $conn, MessageInterface $msg) {
71 | $this->delegate->onMessage($conn, $msg);
72 | };
73 | } elseif ($component instanceof DataComponentInterface) {
74 | $this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) {
75 | $this->delegate->onMessage($conn, $msg->getPayload());
76 | };
77 | } else {
78 | throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface');
79 | }
80 |
81 | if (bin2hex('✓') !== 'e29c93') {
82 | throw new \DomainException('Bad encoding, unicode character ✓ did not match expected value. Ensure charset UTF-8 and check ini val mbstring.func_autoload');
83 | }
84 |
85 | $this->delegate = $component;
86 | $this->connections = new \SplObjectStorage;
87 |
88 | $this->closeFrameChecker = new CloseFrameChecker;
89 | $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier);
90 | $this->handshakeNegotiator->setStrictSubProtocolCheck(true);
91 |
92 | if ($component instanceof WsServerInterface) {
93 | $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols());
94 | }
95 |
96 | $this->pongReceiver = function() {};
97 |
98 | $reusableUnderflowException = new \UnderflowException;
99 | $this->ueFlowFactory = function() use ($reusableUnderflowException) {
100 | return $reusableUnderflowException;
101 | };
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | #[HackSupportForPHP8] public function onOpen(ConnectionInterface $conn, ?RequestInterface $request = null) { /*
108 | public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { /**/
109 | if (null === $request) {
110 | throw new \UnexpectedValueException('$request can not be null');
111 | }
112 |
113 | $conn->httpRequest = $request;
114 |
115 | $conn->WebSocket = new \StdClass;
116 | $conn->WebSocket->closing = false;
117 |
118 | $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION);
119 |
120 | $conn->send(Message::toString($response));
121 |
122 | if (101 !== $response->getStatusCode()) {
123 | return $conn->close();
124 | }
125 |
126 | $wsConn = new WsConnection($conn);
127 |
128 | $streamer = new MessageBuffer(
129 | $this->closeFrameChecker,
130 | function(MessageInterface $msg) use ($wsConn) {
131 | $cb = $this->msgCb;
132 | $cb($wsConn, $msg);
133 | },
134 | function(FrameInterface $frame) use ($wsConn) {
135 | $this->onControlFrame($frame, $wsConn);
136 | },
137 | true,
138 | $this->ueFlowFactory
139 | );
140 |
141 | $this->connections->attach($conn, new ConnContext($wsConn, $streamer));
142 |
143 | return $this->delegate->onOpen($wsConn);
144 | }
145 |
146 | /**
147 | * {@inheritdoc}
148 | */
149 | public function onMessage(ConnectionInterface $from, $msg) {
150 | if ($from->WebSocket->closing) {
151 | return;
152 | }
153 |
154 | $this->connections[$from]->buffer->onData($msg);
155 | }
156 |
157 | /**
158 | * {@inheritdoc}
159 | */
160 | public function onClose(ConnectionInterface $conn) {
161 | if ($this->connections->contains($conn)) {
162 | $context = $this->connections[$conn];
163 | $this->connections->detach($conn);
164 |
165 | $this->delegate->onClose($context->connection);
166 | }
167 | }
168 |
169 | /**
170 | * {@inheritdoc}
171 | */
172 | public function onError(ConnectionInterface $conn, \Exception $e) {
173 | if ($this->connections->contains($conn)) {
174 | $this->delegate->onError($this->connections[$conn]->connection, $e);
175 | } else {
176 | $conn->close();
177 | }
178 | }
179 |
180 | public function onControlFrame(FrameInterface $frame, WsConnection $conn) {
181 | switch ($frame->getOpCode()) {
182 | case Frame::OP_CLOSE:
183 | $conn->close($frame);
184 | break;
185 | case Frame::OP_PING:
186 | $conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG));
187 | break;
188 | case Frame::OP_PONG:
189 | $pongReceiver = $this->pongReceiver;
190 | $pongReceiver($frame, $conn);
191 | break;
192 | }
193 | }
194 |
195 | public function setStrictSubProtocolCheck($enable) {
196 | $this->handshakeNegotiator->setStrictSubProtocolCheck($enable);
197 | }
198 |
199 | public function enableKeepAlive(LoopInterface $loop, $interval = 30) {
200 | $lastPing = new Frame(uniqid(), true, Frame::OP_PING);
201 | $pingedConnections = new \SplObjectStorage;
202 | $splClearer = new \SplObjectStorage;
203 |
204 | $this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) {
205 | if ($frame->getPayload() === $lastPing->getPayload()) {
206 | $pingedConnections->detach($wsConn);
207 | }
208 | };
209 |
210 | $loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) {
211 | foreach ($pingedConnections as $wsConn) {
212 | $wsConn->close();
213 | }
214 | $pingedConnections->removeAllExcept($splClearer);
215 |
216 | $lastPing = new Frame(uniqid(), true, Frame::OP_PING);
217 |
218 | foreach ($this->connections as $key => $conn) {
219 | $wsConn = $this->connections[$conn]->connection;
220 |
221 | $wsConn->send($lastPing);
222 | $pingedConnections->attach($wsConn);
223 | }
224 | });
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/Ratchet/WebSocket/WsServerInterface.php:
--------------------------------------------------------------------------------
1 | send($msg);
9 | }
10 |
11 | public function onOpen(ConnectionInterface $conn) {
12 | }
13 |
14 | public function onClose(ConnectionInterface $conn) {
15 | }
16 |
17 | public function onError(ConnectionInterface $conn, \Exception $e) {
18 | }
19 | }
20 |
21 | $port = $argc > 1 ? $argv[1] : 8000;
22 | $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect');
23 |
24 | $loop = new $impl;
25 |
26 | // prefer SocketServer (reactphp/socket v1.9+) over legacy \React\Socket\Server
27 | $sock = class_exists('React\Socket\SocketServer') ? new React\Socket\SocketServer('0.0.0.0:' . $port, [], $loop) : new React\Socket\Server('0.0.0.0:' . $port, $loop);
28 |
29 | $wsServer = new Ratchet\WebSocket\WsServer(new BinaryEcho);
30 | // This is enabled to test https://github.com/ratchetphp/Ratchet/issues/430
31 | // The time is left at 10 minutes so that it will not try to every ping anything
32 | // This causes the Ratchet server to crash on test 2.7
33 | $wsServer->enableKeepAlive($loop, 600);
34 |
35 | $app = new Ratchet\Http\HttpServer($wsServer);
36 |
37 | $server = new Ratchet\Server\IoServer($app, $sock, $loop);
38 | $server->run();
39 |
--------------------------------------------------------------------------------
/tests/autobahn/fuzzingclient-all.json:
--------------------------------------------------------------------------------
1 | {
2 | "options": {"failByDrop": false}
3 | , "outdir": "reports/ab"
4 |
5 | , "servers": [
6 | {"agent": "Ratchet/0.4 libevent", "url": "ws://localhost:8001", "options": {"version": 18}}
7 | , {"agent": "Ratchet/0.4 libev", "url": "ws://localhost:8004", "options": {"version": 18}}
8 | , {"agent": "Ratchet/0.4 streams", "url": "ws://localhost:8002", "options": {"version": 18}}
9 | , {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}}
10 | ]
11 |
12 | , "cases": ["*"]
13 | , "exclude-cases": []
14 | , "exclude-agent-cases": {}
15 | }
16 |
--------------------------------------------------------------------------------
/tests/autobahn/fuzzingclient-profile.json:
--------------------------------------------------------------------------------
1 | {
2 | "options": {"failByDrop": false}
3 | , "outdir": "reports/profile"
4 |
5 | , "servers": [
6 | {"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}}
7 | ]
8 |
9 | , "cases": ["9.7.4"]
10 | , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"]
11 | , "exclude-agent-cases": {}
12 | }
13 |
--------------------------------------------------------------------------------
/tests/autobahn/fuzzingclient-quick.json:
--------------------------------------------------------------------------------
1 | {
2 | "options": {"failByDrop": false}
3 | , "outdir": "reports/rfc"
4 |
5 | , "servers": [
6 | {"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}}
7 | ]
8 |
9 | , "cases": ["*"]
10 | , "exclude-cases": []
11 | , "exclude-agent-cases": {}
12 | }
13 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | addPsr4('Ratchet\\', __DIR__ . '/helpers/Ratchet');
5 |
--------------------------------------------------------------------------------
/tests/helpers/Ratchet/AbstractMessageComponentTestCase.php:
--------------------------------------------------------------------------------
1 | _app = $this->getMockBuilder($this->getComponentClassString())->getMock();
20 | $decorator = $this->getDecoratorClassString();
21 | $this->_serv = new $decorator($this->_app);
22 | $this->_conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
23 |
24 | $this->doOpen($this->_conn);
25 | }
26 |
27 | protected function doOpen($conn) {
28 | $this->_serv->onOpen($conn);
29 | }
30 |
31 | public function isExpectedConnection() {
32 | return $this->isInstanceOf($this->getConnectionClassString());
33 | }
34 |
35 | public function testOpen() {
36 | $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection());
37 | $this->doOpen($this->getMockBuilder('Ratchet\Mock\Connection')->getMock());
38 | }
39 |
40 | public function testOnClose() {
41 | $this->_app->expects($this->once())->method('onClose')->with($this->isExpectedConnection());
42 | $this->_serv->onClose($this->_conn);
43 | }
44 |
45 | public function testOnError() {
46 | $e = new \Exception('Whoops!');
47 | $this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e);
48 | $this->_serv->onError($this->_conn, $e);
49 | }
50 |
51 | public function passthroughMessageTest($value) {
52 | $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value);
53 | $this->_serv->onMessage($this->_conn, $value);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/helpers/Ratchet/Mock/Connection.php:
--------------------------------------------------------------------------------
1 | ''
9 | , 'close' => false
10 | );
11 |
12 | public $remoteAddress = '127.0.0.1';
13 |
14 | public function send($data) {
15 | $this->last[__FUNCTION__] = $data;
16 | }
17 |
18 | public function close() {
19 | $this->last[__FUNCTION__] = true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/helpers/Ratchet/Mock/ConnectionDecorator.php:
--------------------------------------------------------------------------------
1 | ''
8 | , 'end' => false
9 | );
10 |
11 | public function send($data) {
12 | $this->last[__FUNCTION__] = $data;
13 |
14 | $this->getConnection()->send($data);
15 | }
16 |
17 | public function close() {
18 | $this->last[__FUNCTION__] = true;
19 |
20 | $this->getConnection()->close();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/helpers/Ratchet/Mock/WampComponent.php:
--------------------------------------------------------------------------------
1 | protocols;
14 | }
15 |
16 | public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) {
17 | $this->last[__FUNCTION__] = func_get_args();
18 | }
19 |
20 | public function onSubscribe(ConnectionInterface $conn, $topic) {
21 | $this->last[__FUNCTION__] = func_get_args();
22 | }
23 |
24 | public function onUnSubscribe(ConnectionInterface $conn, $topic) {
25 | $this->last[__FUNCTION__] = func_get_args();
26 | }
27 |
28 | public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {
29 | $this->last[__FUNCTION__] = func_get_args();
30 | }
31 |
32 | public function onOpen(ConnectionInterface $conn) {
33 | $this->last[__FUNCTION__] = func_get_args();
34 | }
35 |
36 | public function onClose(ConnectionInterface $conn) {
37 | $this->last[__FUNCTION__] = func_get_args();
38 | }
39 |
40 | public function onError(ConnectionInterface $conn, \Exception $e) {
41 | $this->last[__FUNCTION__] = func_get_args();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/helpers/Ratchet/NullComponent.php:
--------------------------------------------------------------------------------
1 | mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
20 | $this->l1 = new ConnectionDecorator($this->mock);
21 | $this->l2 = new ConnectionDecorator($this->l1);
22 | }
23 |
24 | public function testGet() {
25 | $var = 'hello';
26 | $val = 'world';
27 |
28 | $this->mock->$var = $val;
29 |
30 | $this->assertEquals($val, $this->l1->$var);
31 | $this->assertEquals($val, $this->l2->$var);
32 | }
33 |
34 | public function testSet() {
35 | $var = 'Chris';
36 | $val = 'Boden';
37 |
38 | $this->l1->$var = $val;
39 |
40 | $this->assertEquals($val, $this->mock->$var);
41 | }
42 |
43 | public function testSetLevel2() {
44 | $var = 'Try';
45 | $val = 'Again';
46 |
47 | $this->l2->$var = $val;
48 |
49 | $this->assertEquals($val, $this->mock->$var);
50 | }
51 |
52 | public function testIsSetTrue() {
53 | $var = 'PHP';
54 | $val = 'Ratchet';
55 |
56 | $this->mock->$var = $val;
57 |
58 | $this->assertTrue(isset($this->l1->$var));
59 | $this->assertTrue(isset($this->l2->$var));
60 | }
61 |
62 | public function testIsSetFalse() {
63 | $var = 'herp';
64 | $val = 'derp';
65 |
66 | $this->assertFalse(isset($this->l1->$var));
67 | $this->assertFalse(isset($this->l2->$var));
68 | }
69 |
70 | public function testUnset() {
71 | $var = 'Flying';
72 | $val = 'Monkey';
73 |
74 | $this->mock->$var = $val;
75 | unset($this->l1->$var);
76 |
77 | $this->assertFalse(isset($this->mock->$var));
78 | }
79 |
80 | public function testUnsetLevel2() {
81 | $var = 'Flying';
82 | $val = 'Monkey';
83 |
84 | $this->mock->$var = $val;
85 | unset($this->l2->$var);
86 |
87 | $this->assertFalse(isset($this->mock->$var));
88 | }
89 |
90 | public function testGetConnection() {
91 | $class = new \ReflectionClass('Ratchet\\AbstractConnectionDecorator');
92 | $method = $class->getMethod('getConnection');
93 | $method->setAccessible(true);
94 |
95 | $conn = $method->invokeArgs($this->l1, array());
96 |
97 | $this->assertSame($this->mock, $conn);
98 | }
99 |
100 | public function testGetConnectionLevel2() {
101 | $class = new \ReflectionClass('Ratchet\\AbstractConnectionDecorator');
102 | $method = $class->getMethod('getConnection');
103 | $method->setAccessible(true);
104 |
105 | $conn = $method->invokeArgs($this->l2, array());
106 |
107 | $this->assertSame($this->l1, $conn);
108 | }
109 |
110 | public function testWrapperCanStoreSelfInDecorator() {
111 | $this->mock->decorator = $this->l1;
112 |
113 | $this->assertSame($this->l1, $this->l2->decorator);
114 | }
115 |
116 | public function testDecoratorRecursion() {
117 | $this->mock->decorator = new \stdClass;
118 | $this->mock->decorator->conn = $this->l1;
119 |
120 | $this->assertSame($this->l1, $this->mock->decorator->conn);
121 | $this->assertSame($this->l1, $this->l1->decorator->conn);
122 | $this->assertSame($this->l1, $this->l2->decorator->conn);
123 | }
124 |
125 | public function testDecoratorRecursionLevel2() {
126 | $this->mock->decorator = new \stdClass;
127 | $this->mock->decorator->conn = $this->l2;
128 |
129 | $this->assertSame($this->l2, $this->mock->decorator->conn);
130 | $this->assertSame($this->l2, $this->l1->decorator->conn);
131 | $this->assertSame($this->l2, $this->l2->decorator->conn);
132 |
133 | // just for fun
134 | $this->assertSame($this->l2, $this->l2->decorator->conn->decorator->conn->decorator->conn);
135 | }
136 |
137 | public function testWarningGettingNothing() {
138 | $error = false;
139 | set_error_handler(function () use (&$error) {
140 | $error = true;
141 | }, PHP_VERSION_ID >= 80000 ? E_WARNING : E_NOTICE);
142 |
143 | $var = $this->mock->nonExistant;
144 |
145 | restore_error_handler();
146 |
147 | $this->assertTrue($error);
148 | $this->assertNull($var);
149 | }
150 |
151 | public function testWarningGettingNothingLevel1() {
152 | $error = false;
153 | set_error_handler(function () use (&$error) {
154 | $error = true;
155 | }, PHP_VERSION_ID >= 80000 ? E_WARNING : E_NOTICE);
156 |
157 | $var = $this->l1->nonExistant;
158 |
159 | restore_error_handler();
160 |
161 | $this->assertTrue($error);
162 | $this->assertNull($var);
163 | }
164 |
165 | public function testWarningGettingNothingLevel2() {
166 | $error = false;
167 | set_error_handler(function () use (&$error) {
168 | $error = true;
169 | }, PHP_VERSION_ID >= 80000 ? E_WARNING : E_NOTICE);
170 |
171 | $var = $this->l2->nonExistant;
172 |
173 | restore_error_handler();
174 |
175 | $this->assertTrue($error);
176 | $this->assertNull($var);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/tests/unit/AppTest.php:
--------------------------------------------------------------------------------
1 | expectException('InvalidArgumentException');
11 | $this->expectExceptionMessage('Argument #4 ($loop) expected null|React\EventLoop\LoopInterface');
12 | } else {
13 | $this->setExpectedException('InvalidArgumentException', 'Argument #4 ($loop) expected null|React\EventLoop\LoopInterface');
14 | }
15 | new App('localhost', 8080, '127.0.0.1', 'loop');
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/unit/Http/HttpRequestParserTest.php:
--------------------------------------------------------------------------------
1 | parser = new HttpRequestParser;
17 | }
18 |
19 | public function headersProvider() {
20 | return array(
21 | array(false, "GET / HTTP/1.1\r\nHost: socketo.me\r\n")
22 | , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n")
23 | , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n1")
24 | , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖")
25 | , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖\r\n\r\n")
26 | , array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie\r\n")
27 | );
28 | }
29 |
30 | /**
31 | * @dataProvider headersProvider
32 | */
33 | public function testIsEom($expected, $message) {
34 | $this->assertEquals($expected, $this->parser->isEom($message));
35 | }
36 |
37 | public function testBufferOverflowResponse() {
38 | $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
39 |
40 | $this->parser->maxSize = 20;
41 |
42 | $this->assertNull($this->parser->onMessage($conn, "GET / HTTP/1.1\r\n"));
43 |
44 | if (method_exists($this, 'expectException')) {
45 | $this->expectException('OverflowException');
46 | } else {
47 | $this->setExpectedException('OverflowException');
48 | }
49 |
50 | $this->parser->onMessage($conn, "Header-Is: Too Big");
51 | }
52 |
53 | public function testReturnTypeIsRequest() {
54 | $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
55 | $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n");
56 |
57 | $this->assertInstanceOf('Psr\Http\Message\RequestInterface', $return);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/unit/Http/HttpServerTest.php:
--------------------------------------------------------------------------------
1 | _conn->httpHeadersReceived = true;
15 | }
16 |
17 | public function getConnectionClassString() {
18 | return 'Ratchet\ConnectionInterface';
19 | }
20 |
21 | public function getDecoratorClassString() {
22 | return 'Ratchet\Http\HttpServer';
23 | }
24 |
25 | public function getComponentClassString() {
26 | return 'Ratchet\Http\HttpServerInterface';
27 | }
28 |
29 | public function testOpen() {
30 | $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n";
31 |
32 | $this->_conn->httpHeadersReceived = false;
33 | $this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection());
34 | $this->_serv->onMessage($this->_conn, $headers);
35 | }
36 |
37 | public function testOnMessageAfterHeaders() {
38 | $headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n";
39 | $this->_conn->httpHeadersReceived = false;
40 | $this->_serv->onMessage($this->_conn, $headers);
41 |
42 | $message = "Hello World!";
43 | $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message);
44 | $this->_serv->onMessage($this->_conn, $message);
45 | }
46 |
47 | public function testBufferOverflow() {
48 | $this->_conn->expects($this->once())->method('close');
49 | $this->_conn->httpHeadersReceived = false;
50 |
51 | $this->_serv->onMessage($this->_conn, str_repeat('a', 5000));
52 | }
53 |
54 | public function testCloseIfNotEstablished() {
55 | $this->_conn->httpHeadersReceived = false;
56 | $this->_conn->expects($this->once())->method('close');
57 | $this->_serv->onError($this->_conn, new \Exception('Whoops!'));
58 | }
59 |
60 | public function testBufferHeaders() {
61 | $this->_conn->httpHeadersReceived = false;
62 | $this->_app->expects($this->never())->method('onOpen');
63 | $this->_app->expects($this->never())->method('onMessage');
64 |
65 | $this->_serv->onMessage($this->_conn, "GET / HTTP/1.1");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/unit/Http/OriginCheckTest.php:
--------------------------------------------------------------------------------
1 | _reqStub = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock();
16 | $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost']));
17 |
18 | parent::setUpConnection();
19 |
20 | assert($this->_serv instanceof OriginCheck);
21 | $this->_serv->allowedOrigins[] = 'localhost';
22 | }
23 |
24 | protected function doOpen($conn) {
25 | $this->_serv->onOpen($conn, $this->_reqStub);
26 | }
27 |
28 | public function getConnectionClassString() {
29 | return 'Ratchet\ConnectionInterface';
30 | }
31 |
32 | public function getDecoratorClassString() {
33 | return 'Ratchet\Http\OriginCheck';
34 | }
35 |
36 | public function getComponentClassString() {
37 | return 'Ratchet\Http\HttpServerInterface';
38 | }
39 |
40 | public function testCloseOnNonMatchingOrigin() {
41 | $this->_serv->allowedOrigins = ['socketo.me'];
42 | $this->_conn->expects($this->once())->method('close');
43 |
44 | $this->_serv->onOpen($this->_conn, $this->_reqStub);
45 | }
46 |
47 | public function testOnMessage() {
48 | $this->passthroughMessageTest('Hello World!');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/unit/Http/RouterTest.php:
--------------------------------------------------------------------------------
1 | _conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
26 | $this->_uri = $this->getMockBuilder('Psr\Http\Message\UriInterface')->getMock();
27 | $this->_req = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock();
28 | $this->_req
29 | ->expects($this->any())
30 | ->method('getUri')
31 | ->will($this->returnValue($this->_uri));
32 | $this->_matcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface')->getMock();
33 | $this->_matcher
34 | ->expects($this->any())
35 | ->method('getContext')
36 | ->will($this->returnValue($this->getMockBuilder('Symfony\Component\Routing\RequestContext')->getMock()));
37 | $this->_router = new Router($this->_matcher);
38 |
39 | $this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/'));
40 | $this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) {
41 | $this->setResult($val);
42 |
43 | return true;
44 | }))->will($this->returnSelf());
45 | $this->_uri->expects($this->any())->method('getHost')->willReturn('example.com');
46 | $this->_req->expects($this->any())->method('withUri')->will($this->returnSelf());
47 | $this->_req->expects($this->any())->method('getMethod')->willReturn('GET');
48 | }
49 |
50 | public function testFourOhFour() {
51 | $this->_conn->expects($this->once())->method('close');
52 |
53 | $nope = new ResourceNotFoundException;
54 | $this->_matcher->expects($this->any())->method('match')->will($this->throwException($nope));
55 |
56 | $this->_router->onOpen($this->_conn, $this->_req);
57 | }
58 |
59 | public function testNullRequest() {
60 | if (method_exists($this, 'expectException')) {
61 | $this->expectException('UnexpectedValueException');
62 | } else {
63 | $this->setExpectedException('UnexpectedValueException');
64 | }
65 | $this->_router->onOpen($this->_conn);
66 | }
67 |
68 | public function testControllerIsMessageComponentInterface() {
69 | if (method_exists($this, 'expectException')) {
70 | $this->expectException('UnexpectedValueException');
71 | } else {
72 | $this->setExpectedException('UnexpectedValueException');
73 | }
74 | $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => new \StdClass)));
75 | $this->_router->onOpen($this->_conn, $this->_req);
76 | }
77 |
78 | public function testControllerOnOpen() {
79 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
80 | $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller)));
81 | $this->_router->onOpen($this->_conn, $this->_req);
82 |
83 | $expectedConn = $this->isInstanceOf('Ratchet\Mock\Connection');
84 | $controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req);
85 |
86 | $this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller)));
87 | $this->_router->onOpen($this->_conn, $this->_req);
88 | }
89 |
90 | public function testControllerOnMessageBubbles() {
91 | $message = "The greatest trick the Devil ever pulled was convincing the world he didn't exist";
92 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
93 | $controller->expects($this->once())->method('onMessage')->with($this->_conn, $message);
94 |
95 | $this->_conn->controller = $controller;
96 |
97 | $this->_router->onMessage($this->_conn, $message);
98 | }
99 |
100 | public function testControllerOnCloseBubbles() {
101 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
102 | $controller->expects($this->once())->method('onClose')->with($this->_conn);
103 |
104 | $this->_conn->controller = $controller;
105 |
106 | $this->_router->onClose($this->_conn);
107 | }
108 |
109 | public function testControllerOnErrorBubbles() {
110 | $e= new \Exception('One cannot be betrayed if one has no exceptions');
111 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
112 | $controller->expects($this->once())->method('onError')->with($this->_conn, $e);
113 |
114 | $this->_conn->controller = $controller;
115 |
116 | $this->_router->onError($this->_conn, $e);
117 | }
118 |
119 | public function testRouterGeneratesRouteParameters() {
120 | /** @var $controller WsServerInterface */
121 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
122 | /** @var $matcher UrlMatcherInterface */
123 | $this->_matcher->expects($this->any())->method('match')->will(
124 | $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux'])
125 | );
126 | $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
127 |
128 | $this->_uri->expects($this->once())->method('withQuery')->with('foo=bar&baz=qux')->willReturnSelf();
129 | $this->_req->expects($this->once())->method('withUri')->will($this->returnSelf());
130 |
131 | $router = new Router($this->_matcher);
132 |
133 | $router->onOpen($conn, $this->_req);
134 | }
135 |
136 | public function testQueryParams() {
137 | $controller = $this->getMockBuilder('Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock();
138 | $this->_matcher->expects($this->any())->method('match')->will(
139 | $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux'])
140 | );
141 |
142 | $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
143 | $request = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock();
144 | $uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope');
145 |
146 | $request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) {
147 | return $uri;
148 | }));
149 | $request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) {
150 | $uri = $url;
151 |
152 | return true;
153 | }))->will($this->returnSelf());
154 | $request->expects($this->once())->method('getMethod')->willReturn('GET');
155 |
156 | $router = new Router($this->_matcher);
157 | $router->onOpen($conn, $request);
158 |
159 | $this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery());
160 | $this->assertEquals('ws', $request->getUri()->getScheme());
161 | $this->assertEquals('doesnt.matter', $request->getUri()->getHost());
162 | }
163 |
164 | public function testImpatientClientOverflow() {
165 | $this->_conn->expects($this->once())->method('close');
166 |
167 | $header = "GET /nope HTTP/1.1
168 | Upgrade: websocket
169 | Connection: upgrade
170 | Host: localhost
171 | Origin: http://localhost
172 | Sec-WebSocket-Version: 13\r\n\r\n";
173 |
174 | $app = new HttpServer(new Router(new UrlMatcher(new RouteCollection, new RequestContext)));
175 | $app->onOpen($this->_conn);
176 | $app->onMessage($this->_conn, $header);
177 | $app->onMessage($this->_conn, 'Silly body');
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/tests/unit/Server/EchoServerTest.php:
--------------------------------------------------------------------------------
1 | _conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock();
15 | $this->_comp = new EchoServer;
16 | }
17 |
18 | public function testMessageEchod() {
19 | $message = 'Tillsonburg, my back still aches when I hear that word.';
20 | $this->_conn->expects($this->once())->method('send')->with($message);
21 | $this->_comp->onMessage($this->_conn, $message);
22 | }
23 |
24 | public function testErrorClosesConnection() {
25 | ob_start();
26 | $this->_conn->expects($this->once())->method('close');
27 | $this->_comp->onError($this->_conn, new \Exception);
28 | ob_end_clean();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/unit/Server/FlashPolicyComponentTest.php:
--------------------------------------------------------------------------------
1 | _policy = new FlashPolicy();
18 | }
19 |
20 | public function testPolicyRender() {
21 | $this->_policy->setSiteControl('all');
22 | $this->_policy->addAllowedAccess('example.com', '*');
23 | $this->_policy->addAllowedAccess('dev.example.com', '*');
24 |
25 | $this->assertInstanceOf('SimpleXMLElement', $this->_policy->renderPolicy());
26 | }
27 |
28 | public function testInvalidPolicyReader() {
29 | if (method_exists($this, 'expectException')) {
30 | $this->expectException('UnexpectedValueException');
31 | } else {
32 | $this->setExpectedException('UnexpectedValueException');
33 | }
34 | $this->_policy->renderPolicy();
35 | }
36 |
37 | public function testInvalidDomainPolicyReader() {
38 | if (method_exists($this, 'expectException')) {
39 | $this->expectException('UnexpectedValueException');
40 | } else {
41 | $this->setExpectedException('UnexpectedValueException');
42 | }
43 | $this->_policy->setSiteControl('all');
44 | $this->_policy->addAllowedAccess('dev.example.*', '*');
45 | $this->_policy->renderPolicy();
46 | }
47 |
48 | /**
49 | * @dataProvider siteControl
50 | */
51 | public function testSiteControlValidation($accept, $permittedCrossDomainPolicies) {
52 | $this->assertEquals($accept, $this->_policy->validateSiteControl($permittedCrossDomainPolicies));
53 | }
54 |
55 | public static function siteControl() {
56 | return array(
57 | array(true, 'all')
58 | , array(true, 'none')
59 | , array(true, 'master-only')
60 | , array(false, 'by-content-type')
61 | , array(false, 'by-ftp-filename')
62 | , array(false, '')
63 | , array(false, 'all ')
64 | , array(false, 'asdf')
65 | , array(false, '@893830')
66 | , array(false, '*')
67 | );
68 | }
69 |
70 | /**
71 | * @dataProvider URI
72 | */
73 | public function testDomainValidation($accept, $domain) {
74 | $this->assertEquals($accept, $this->_policy->validateDomain($domain));
75 | }
76 |
77 | public static function URI() {
78 | return array(
79 | array(true, '*')
80 | , array(true, 'example.com')
81 | , array(true, 'exam-ple.com')
82 | , array(true, '*.example.com')
83 | , array(true, 'www.example.com')
84 | , array(true, 'dev.dev.example.com')
85 | , array(true, 'http://example.com')
86 | , array(true, 'https://example.com')
87 | , array(true, 'http://*.example.com')
88 | , array(false, 'exam*ple.com')
89 | , array(true, '127.0.255.1')
90 | , array(true, 'localhost')
91 | , array(false, 'www.example.*')
92 | , array(false, 'www.exa*le.com')
93 | , array(false, 'www.example.*com')
94 | , array(false, '*.example.*')
95 | , array(false, 'gasldf*$#a0sdf0a8sdf')
96 | );
97 | }
98 |
99 | /**
100 | * @dataProvider ports
101 | */
102 | public function testPortValidation($accept, $ports) {
103 | $this->assertEquals($accept, $this->_policy->validatePorts($ports));
104 | }
105 |
106 | public static function ports() {
107 | return array(
108 | array(true, '*')
109 | , array(true, '80')
110 | , array(true, '80,443')
111 | , array(true, '507,516-523')
112 | , array(true, '507,516-523,333')
113 | , array(true, '507,516-523,507,516-523')
114 | , array(false, '516-')
115 | , array(true, '516-523,11')
116 | , array(false, '516,-523,11')
117 | , array(false, 'example')
118 | , array(false, 'asdf,123')
119 | , array(false, '--')
120 | , array(false, ',,,')
121 | , array(false, '838*')
122 | );
123 | }
124 |
125 | public function testAddAllowedAccessOnlyAcceptsValidPorts() {
126 | if (method_exists($this, 'expectException')) {
127 | $this->expectException('UnexpectedValueException');
128 | } else {
129 | $this->setExpectedException('UnexpectedValueException');
130 | }
131 |
132 | $this->_policy->addAllowedAccess('*', 'nope');
133 | }
134 |
135 | public function testSetSiteControlThrowsException() {
136 | if (method_exists($this, 'expectException')) {
137 | $this->expectException('UnexpectedValueException');
138 | } else {
139 | $this->setExpectedException('UnexpectedValueException');
140 | }
141 |
142 | $this->_policy->setSiteControl('nope');
143 | }
144 |
145 | public function testErrorClosesConnection() {
146 | $conn = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock();
147 | $conn->expects($this->once())->method('close');
148 |
149 | $this->_policy->onError($conn, new \Exception);
150 | }
151 |
152 | public function testOnMessageSendsString() {
153 | $this->_policy->addAllowedAccess('*', '*');
154 |
155 | $conn = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock();
156 | $conn->expects($this->once())->method('send')->with($this->isType('string'));
157 |
158 | $this->_policy->onMessage($conn, ' ');
159 | }
160 |
161 | public function testOnOpenExists() {
162 | $this->assertTrue(method_exists($this->_policy, 'onOpen'));
163 | $conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock();
164 | $this->_policy->onOpen($conn);
165 | }
166 |
167 | public function testOnCloseExists() {
168 | $this->assertTrue(method_exists($this->_policy, 'onClose'));
169 | $conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock();
170 | $this->_policy->onClose($conn);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/tests/unit/Server/IoConnectionTest.php:
--------------------------------------------------------------------------------
1 | sock = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();
18 | $this->conn = new IoConnection($this->sock);
19 | }
20 |
21 | public function testCloseBubbles() {
22 | $this->sock->expects($this->once())->method('end');
23 | $this->conn->close();
24 | }
25 |
26 | public function testSendBubbles() {
27 | $msg = '6 hour rides are productive';
28 |
29 | $this->sock->expects($this->once())->method('write')->with($msg);
30 | $this->conn->send($msg);
31 | }
32 |
33 | public function testSendReturnsSelf() {
34 | $this->assertSame($this->conn, $this->conn->send('fluent interface'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/unit/Server/IoServerTest.php:
--------------------------------------------------------------------------------
1 | futureTick(function () use ($loop) {
23 | $loop->stop();
24 | });
25 |
26 | $loop->run();
27 | }
28 |
29 | /**
30 | * @before
31 | */
32 | public function setUpServer() {
33 | $this->app = $this->getMockBuilder('Ratchet\\MessageComponentInterface')->getMock();
34 |
35 | $loop = new StreamSelectLoop;
36 |
37 | // prefer SocketServer (reactphp/socket v1.9+) over legacy \React\Socket\Server
38 | $this->reactor = class_exists('React\Socket\SocketServer') ? new SocketServer('127.0.0.1:0', [], $loop) : new LegacySocketServer('127.0.0.1:0', $loop);
39 |
40 | $uri = $this->reactor->getAddress();
41 | $this->port = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_PORT);
42 | $this->server = new IoServer($this->app, $this->reactor, $loop);
43 | }
44 |
45 | public function testCtorThrowsForInvalidLoop() {
46 | if (method_exists($this, 'expectException')) {
47 | $this->expectException('InvalidArgumentException');
48 | $this->expectExceptionMessage('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
49 | } else {
50 | $this->setExpectedException('InvalidArgumentException', 'Argument #3 ($loop) expected null|React\EventLoop\LoopInterface');
51 | }
52 | new IoServer($this->app, $this->reactor, 'loop');
53 | }
54 |
55 | public function testOnOpen() {
56 | $this->app->expects($this->once())->method('onOpen')->with($this->isInstanceOf('Ratchet\\ConnectionInterface'));
57 |
58 | $client = stream_socket_client("tcp://localhost:{$this->port}");
59 |
60 | $this->tickLoop($this->server->loop);
61 |
62 | //$this->assertTrue(is_string($this->app->last['onOpen'][0]->remoteAddress));
63 | //$this->assertTrue(is_int($this->app->last['onOpen'][0]->resourceId));
64 | }
65 |
66 | /**
67 | * @requires extension sockets
68 | */
69 | public function testOnData() {
70 | $msg = 'Hello World!';
71 |
72 | $this->app->expects($this->once())->method('onMessage')->with(
73 | $this->isInstanceOf('Ratchet\\ConnectionInterface')
74 | , $msg
75 | );
76 |
77 | $client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
78 | socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1);
79 | socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096);
80 | socket_set_block($client);
81 | socket_connect($client, 'localhost', $this->port);
82 |
83 | $this->tickLoop($this->server->loop);
84 |
85 | socket_write($client, $msg);
86 | $this->tickLoop($this->server->loop);
87 |
88 | socket_shutdown($client, 1);
89 | socket_shutdown($client, 0);
90 | socket_close($client);
91 |
92 | $this->tickLoop($this->server->loop);
93 | }
94 |
95 | /**
96 | * @requires extension sockets
97 | */
98 | public function testOnClose() {
99 | $this->app->expects($this->once())->method('onClose')->with($this->isInstanceOf('Ratchet\\ConnectionInterface'));
100 |
101 | $client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
102 | socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1);
103 | socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096);
104 | socket_set_block($client);
105 | socket_connect($client, 'localhost', $this->port);
106 |
107 | $this->tickLoop($this->server->loop);
108 |
109 | socket_shutdown($client, 1);
110 | socket_shutdown($client, 0);
111 | socket_close($client);
112 |
113 | $this->tickLoop($this->server->loop);
114 | }
115 |
116 | public function testFactory() {
117 | $server = IoServer::factory($this->app, 0);
118 | $server->socket->close();
119 |
120 | $this->assertInstanceOf('Ratchet\\Server\\IoServer', $server);
121 | }
122 |
123 | public function testNoLoopProvidedError() {
124 | if (method_exists($this, 'expectException')) {
125 | $this->expectException('RuntimeException');
126 | } else {
127 | $this->setExpectedException('RuntimeException');
128 | }
129 |
130 | $io = new IoServer($this->app, $this->reactor);
131 | $io->run();
132 | }
133 |
134 | public function testOnErrorPassesException() {
135 | $conn = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();
136 |
137 | // assign dynamic property without raising notice on PHP 8.2+
138 | set_error_handler(function () { }, E_DEPRECATED);
139 | $conn->decor = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock();
140 | restore_error_handler();
141 |
142 | $err = new \Exception("Nope");
143 |
144 | $this->app->expects($this->once())->method('onError')->with($conn->decor, $err);
145 |
146 | $this->server->handleError($err, $conn);
147 | }
148 |
149 | public function onErrorCalledWhenExceptionThrown() {
150 | $this->markTestIncomplete("Need to learn how to throw an exception from a mock");
151 |
152 | $conn = $this->getMockBuilder('React\\Socket\\ConnectionInterface')->getMock();
153 | $this->server->handleConnect($conn);
154 |
155 | $e = new \Exception;
156 | $this->app->expects($this->once())->method('onMessage')->with($this->isInstanceOf('Ratchet\\ConnectionInterface'), 'f')->will($e);
157 | $this->app->expects($this->once())->method('onError')->with($this->instanceOf('Ratchet\\ConnectionInterface', $e));
158 |
159 | $this->server->handleData('f', $conn);
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/tests/unit/Server/IpBlackListComponentTest.php:
--------------------------------------------------------------------------------
1 | mock = $this->getMockBuilder('Ratchet\\MessageComponentInterface')->getMock();
18 | $this->blocker = new IpBlackList($this->mock);
19 | }
20 |
21 | public function testOnOpen() {
22 | $this->mock->expects($this->exactly(3))->method('onOpen');
23 |
24 | $conn1 = $this->newConn();
25 | $conn2 = $this->newConn();
26 | $conn3 = $this->newConn();
27 |
28 | $this->blocker->onOpen($conn1);
29 | $this->blocker->onOpen($conn3);
30 | $this->blocker->onOpen($conn2);
31 | }
32 |
33 | public function testBlockDoesNotTriggerOnOpen() {
34 | $conn = $this->newConn();
35 |
36 | $this->blocker->blockAddress($conn->remoteAddress);
37 |
38 | $this->mock->expects($this->never())->method('onOpen');
39 |
40 | $ret = $this->blocker->onOpen($conn);
41 | }
42 |
43 | public function testBlockDoesNotTriggerOnClose() {
44 | $conn = $this->newConn();
45 |
46 | $this->blocker->blockAddress($conn->remoteAddress);
47 |
48 | $this->mock->expects($this->never())->method('onClose');
49 |
50 | $ret = $this->blocker->onOpen($conn);
51 | }
52 |
53 | public function testOnMessageDecoration() {
54 | $conn = $this->newConn();
55 | $msg = 'Hello not being blocked';
56 |
57 | $this->mock->expects($this->once())->method('onMessage')->with($conn, $msg);
58 |
59 | $this->blocker->onMessage($conn, $msg);
60 | }
61 |
62 | public function testOnCloseDecoration() {
63 | $conn = $this->newConn();
64 |
65 | $this->mock->expects($this->once())->method('onClose')->with($conn);
66 |
67 | $this->blocker->onClose($conn);
68 | }
69 |
70 | public function testBlockClosesConnection() {
71 | $conn = $this->newConn();
72 | $this->blocker->blockAddress($conn->remoteAddress);
73 |
74 | $conn->expects($this->once())->method('close');
75 |
76 | $this->blocker->onOpen($conn);
77 | }
78 |
79 | public function testAddAndRemoveWithFluentInterfaces() {
80 | $blockOne = '127.0.0.1';
81 | $blockTwo = '192.168.1.1';
82 | $unblock = '75.119.207.140';
83 |
84 | $this->blocker
85 | ->blockAddress($unblock)
86 | ->blockAddress($blockOne)
87 | ->unblockAddress($unblock)
88 | ->blockAddress($blockTwo)
89 | ;
90 |
91 | $this->assertEquals(array($blockOne, $blockTwo), $this->blocker->getBlockedAddresses());
92 | }
93 |
94 | public function testDecoratorPassesErrors() {
95 | $conn = $this->newConn();
96 | $e = new \Exception('I threw an error');
97 |
98 | $this->mock->expects($this->once())->method('onError')->with($conn, $e);
99 |
100 | $this->blocker->onError($conn, $e);
101 | }
102 |
103 | public function addressProvider() {
104 | return array(
105 | array('127.0.0.1', '127.0.0.1')
106 | , array('localhost', 'localhost')
107 | , array('fe80::1%lo0', 'fe80::1%lo0')
108 | , array('127.0.0.1', '127.0.0.1:6392')
109 | );
110 | }
111 |
112 | /**
113 | * @dataProvider addressProvider
114 | */
115 | public function testFilterAddress($expected, $input) {
116 | $this->assertEquals($expected, $this->blocker->filterAddress($input));
117 | }
118 |
119 | public function testUnblockingSilentlyFails() {
120 | $this->assertInstanceOf('Ratchet\\Server\\IpBlackList', $this->blocker->unblockAddress('localhost'));
121 | }
122 |
123 | protected function newConn() {
124 | $conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
125 | $conn->remoteAddress = '127.0.0.1';
126 |
127 | return $conn;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/tests/unit/Session/Serialize/PhpBinaryHandlerTest.php:
--------------------------------------------------------------------------------
1 | _handler = new PhpBinaryHandler;
17 | }
18 |
19 | public function serializedProvider() {
20 | return array(
21 | array(
22 | "\x0f" . '_sf2_attributes' . 'a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}' . "\x0c" . '_sf2_flashes' . 'a:0:{}'
23 | , array(
24 | '_sf2_attributes' => array(
25 | 'hello' => 'world'
26 | , 'last' => 1332872102
27 | )
28 | , '_sf2_flashes' => array()
29 | )
30 | )
31 | );
32 | }
33 |
34 | /**
35 | * @dataProvider serializedProvider
36 | */
37 | public function testUnserialize($in, $expected) {
38 | $this->assertEquals($expected, $this->_handler->unserialize($in));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/unit/Session/Serialize/PhpHandlerTest.php:
--------------------------------------------------------------------------------
1 | _handler = new PhpHandler;
17 | }
18 |
19 | public function serializedProvider() {
20 | return array(
21 | array(
22 | '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}'
23 | , array(
24 | '_sf2_attributes' => array(
25 | 'hello' => 'world'
26 | , 'last' => 1332872102
27 | )
28 | , '_sf2_flashes' => array()
29 | )
30 | )
31 | );
32 | }
33 |
34 | /**
35 | * @dataProvider serializedProvider
36 | */
37 | public function testUnserialize($in, $expected) {
38 | $this->assertEquals($expected, $this->_handler->unserialize($in));
39 | }
40 |
41 | /**
42 | * @dataProvider serializedProvider
43 | */
44 | public function testSerialize($serialized, $original) {
45 | $this->assertEquals($serialized, $this->_handler->serialize($original));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/unit/Session/SessionProviderTest.php:
--------------------------------------------------------------------------------
1 | markTestIncomplete('Test needs to be updated for ini_set issue in PHP 7.2');
18 |
19 | if (!class_exists('Symfony\Component\HttpFoundation\Session\Session')) {
20 | return $this->markTestSkipped('Dependency of Symfony HttpFoundation failed');
21 | }
22 |
23 | parent::setUpConnection();
24 | $this->_serv = new SessionProvider($this->_app, new NullSessionHandler);
25 | }
26 |
27 | public function testCtorThrowsForInvalidSerializer() {
28 | if (method_exists($this, 'expectException')) {
29 | $this->expectException('InvalidArgumentException');
30 | $this->expectExceptionMessage('Argument #4 ($serializer) expected null|Ratchet\Session\Serialize\HandlerInterface');
31 | } else {
32 | $this->setExpectedException('InvalidArgumentException', 'Argument #4 ($serializer) expected null|Ratchet\Session\Serialize\HandlerInterface');
33 | }
34 | new SessionProvider($this->_app, new NullSessionHandler(), [], 'serializer');
35 | }
36 |
37 | /**
38 | * @after
39 | */
40 | public function tearDownHandler() {
41 | ini_set('session.serialize_handler', 'php');
42 | }
43 |
44 | public function getConnectionClassString() {
45 | return 'Ratchet\ConnectionInterface';
46 | }
47 |
48 | public function getDecoratorClassString() {
49 | return 'Ratchet\NullComponent';
50 | }
51 |
52 | public function getComponentClassString() {
53 | return 'Ratchet\Http\HttpServerInterface';
54 | }
55 |
56 | public function classCaseProvider() {
57 | return array(
58 | array('php', 'Php')
59 | , array('php_binary', 'PhpBinary')
60 | );
61 | }
62 |
63 | /**
64 | * @dataProvider classCaseProvider
65 | */
66 | public function testToClassCase($in, $out) {
67 | $ref = new \ReflectionClass('Ratchet\\Session\\SessionProvider');
68 | $method = $ref->getMethod('toClassCase');
69 | $method->setAccessible(true);
70 |
71 | $component = new SessionProvider($this->getMockBuilder($this->getComponentClassString())->getMock(), $this->getMockBuilder('SessionHandlerInterface')->getMock());
72 | $this->assertEquals($out, $method->invokeArgs($component, array($in)));
73 | }
74 |
75 | /**
76 | * I think I have severely butchered this test...it's not so much of a unit test as it is a full-fledged component test
77 | */
78 | public function testConnectionValueFromPdo() {
79 | if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
80 | return $this->markTestSkipped('Session test requires PDO and pdo_sqlite');
81 | }
82 |
83 | $sessionId = md5('testSession');
84 |
85 | $dbOptions = array(
86 | 'db_table' => 'sessions'
87 | , 'db_id_col' => 'sess_id'
88 | , 'db_data_col' => 'sess_data'
89 | , 'db_time_col' => 'sess_time'
90 | , 'db_lifetime_col' => 'sess_lifetime'
91 | );
92 |
93 | $pdo = new \PDO("sqlite::memory:");
94 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
95 | $pdo->exec(vsprintf("CREATE TABLE %s (%s TEXT NOT NULL PRIMARY KEY, %s BLOB NOT NULL, %s INTEGER NOT NULL, %s INTEGER)", $dbOptions));
96 |
97 | $pdoHandler = new PdoSessionHandler($pdo, $dbOptions);
98 | $pdoHandler->write($sessionId, '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}');
99 |
100 | $component = new SessionProvider($this->getMockBuilder($this->getComponentClassString())->getMock(), $pdoHandler, array('auto_start' => 1));
101 | $connection = $this->getMockBuilder('Ratchet\\ConnectionInterface')->getMock();
102 |
103 | $headers = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock();
104 | $headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"]));
105 |
106 | $component->onOpen($connection, $headers);
107 |
108 | $this->assertEquals('world', $connection->Session->get('hello'));
109 | }
110 |
111 | protected function newConn() {
112 | $conn = $this->getMockBuilder('Ratchet\ConnectionInterface')->getMock();
113 |
114 | $headers = $this->getMockBuilder('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array()))->getMock();
115 | $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null));
116 |
117 | return $conn;
118 | }
119 |
120 | public function testOnMessageDecorator() {
121 | $message = "Database calls are usually blocking :(";
122 | $this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message);
123 | $this->_serv->onMessage($this->_conn, $message);
124 | }
125 |
126 | public function testRejectInvalidSeralizers() {
127 | if (!function_exists('wddx_serialize_value')) {
128 | $this->markTestSkipped();
129 | }
130 |
131 | ini_set('session.serialize_handler', 'wddx');
132 | $this->setExpectedException('RuntimeException');
133 | new SessionProvider($this->getMockBuilder($this->getComponentClassString())->getMock(), $this->getMockBuilder('SessionHandlerInterface')->getMock());
134 | }
135 |
136 | protected function doOpen($conn) {
137 | $request = $this->getMockBuilder('Psr\Http\Message\RequestInterface')->getMock();
138 | $request->expects($this->any())->method('getHeader')->will($this->returnValue([]));
139 |
140 | $this->_serv->onOpen($conn, $request);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/tests/unit/Session/Storage/VirtualSessionStoragePDOTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped('Session test requires PDO and pdo_sqlite');
23 | }
24 |
25 | $schema = <<_pathToDB = tempnam(sys_get_temp_dir(), 'SQ3');;
34 | $dsn = 'sqlite:' . $this->_pathToDB;
35 |
36 | $pdo = new \PDO($dsn);
37 | $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
38 | $pdo->exec($schema);
39 | $pdo = null;
40 |
41 | $sessionHandler = new PdoSessionHandler($dsn);
42 | $serializer = new PhpHandler();
43 | $this->_virtualSessionStorage = new VirtualSessionStorage($sessionHandler, 'foobar', $serializer);
44 | $this->_virtualSessionStorage->registerBag(new FlashBag());
45 | $this->_virtualSessionStorage->registerBag(new AttributeBag());
46 | }
47 |
48 | /**
49 | * @after
50 | */
51 | public function tearDownHandler() {
52 | unlink($this->_pathToDB);
53 | }
54 |
55 | public function testStartWithDSN() {
56 | $this->_virtualSessionStorage->start();
57 |
58 | $this->assertTrue($this->_virtualSessionStorage->isStarted());
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/unit/Wamp/ServerProtocolTest.php:
--------------------------------------------------------------------------------
1 | _app = new TestComponent;
22 | $this->_comp = new ServerProtocol($this->_app);
23 | }
24 |
25 | protected function newConn() {
26 | return new Connection;
27 | }
28 |
29 | public function invalidMessageProvider() {
30 | return [
31 | [0]
32 | , [3]
33 | , [4]
34 | , [8]
35 | , [9]
36 | ];
37 | }
38 |
39 | /**
40 | * @dataProvider invalidMessageProvider
41 | */
42 | public function testInvalidMessages($type) {
43 | if (method_exists($this, 'expectException')) {
44 | $this->expectException('Ratchet\Wamp\Exception');
45 | } else {
46 | $this->setExpectedException('Ratchet\Wamp\Exception');
47 | }
48 |
49 | $conn = $this->newConn();
50 | $this->_comp->onOpen($conn);
51 | $this->_comp->onMessage($conn, json_encode([$type]));
52 | }
53 |
54 | public function testWelcomeMessage() {
55 | $conn = $this->newConn();
56 |
57 | $this->_comp->onOpen($conn);
58 |
59 | $message = $conn->last['send'];
60 | $json = json_decode($message);
61 |
62 | $this->assertEquals(4, count($json));
63 | $this->assertEquals(0, $json[0]);
64 | $this->assertTrue(is_string($json[1]));
65 | $this->assertEquals(1, $json[2]);
66 | }
67 |
68 | public function testSubscribe() {
69 | $uri = 'http://example.com';
70 | $clientMessage = array(5, $uri);
71 |
72 | $conn = $this->newConn();
73 |
74 | $this->_comp->onOpen($conn);
75 | $this->_comp->onMessage($conn, json_encode($clientMessage));
76 |
77 | $this->assertEquals($uri, $this->_app->last['onSubscribe'][1]);
78 | }
79 |
80 | public function testUnSubscribe() {
81 | $uri = 'http://example.com/endpoint';
82 | $clientMessage = array(6, $uri);
83 |
84 | $conn = $this->newConn();
85 |
86 | $this->_comp->onOpen($conn);
87 | $this->_comp->onMessage($conn, json_encode($clientMessage));
88 |
89 | $this->assertEquals($uri, $this->_app->last['onUnSubscribe'][1]);
90 | }
91 |
92 | public function callProvider() {
93 | return [
94 | [2, 'a', 'b']
95 | , [2, ['a', 'b']]
96 | , [1, 'one']
97 | , [3, 'one', 'two', 'three']
98 | , [3, ['un', 'deux', 'trois']]
99 | , [2, 'hi', ['hello', 'world']]
100 | , [2, ['hello', 'world'], 'hi']
101 | , [2, ['hello' => 'world', 'herp' => 'derp']]
102 | ];
103 | }
104 |
105 | /**
106 | * @dataProvider callProvider
107 | */
108 | public function testCall() {
109 | $args = func_get_args();
110 | $paramNum = array_shift($args);
111 |
112 | $uri = 'http://example.com/endpoint/' . rand(1, 100);
113 | $id = uniqid('', false);
114 | $clientMessage = array_merge(array(2, $id, $uri), $args);
115 |
116 | $conn = $this->newConn();
117 |
118 | $this->_comp->onOpen($conn);
119 | $this->_comp->onMessage($conn, json_encode($clientMessage));
120 |
121 | $this->assertEquals($id, $this->_app->last['onCall'][1]);
122 | $this->assertEquals($uri, $this->_app->last['onCall'][2]);
123 |
124 | $this->assertEquals($paramNum, count($this->_app->last['onCall'][3]));
125 | }
126 |
127 | public function testPublish() {
128 | $conn = $this->newConn();
129 |
130 | $topic = 'pubsubhubbub';
131 | $event = 'Here I am, publishing data';
132 |
133 | $clientMessage = array(7, $topic, $event);
134 |
135 | $this->_comp->onOpen($conn);
136 | $this->_comp->onMessage($conn, json_encode($clientMessage));
137 |
138 | $this->assertEquals($topic, $this->_app->last['onPublish'][1]);
139 | $this->assertEquals($event, $this->_app->last['onPublish'][2]);
140 | $this->assertEquals(array(), $this->_app->last['onPublish'][3]);
141 | $this->assertEquals(array(), $this->_app->last['onPublish'][4]);
142 | }
143 |
144 | public function testPublishAndExcludeMe() {
145 | $conn = $this->newConn();
146 |
147 | $this->_comp->onOpen($conn);
148 | $this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', true)));
149 |
150 | $this->assertEquals($conn->WAMP->sessionId, $this->_app->last['onPublish'][3][0]);
151 | }
152 |
153 | public function testPublishAndEligible() {
154 | $conn = $this->newConn();
155 |
156 | $buddy = uniqid('', false);
157 | $friend = uniqid('', false);
158 |
159 | $this->_comp->onOpen($conn);
160 | $this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', false, array($buddy, $friend))));
161 |
162 | $this->assertEquals(array(), $this->_app->last['onPublish'][3]);
163 | $this->assertEquals(2, count($this->_app->last['onPublish'][4]));
164 | }
165 |
166 | public function eventProvider() {
167 | return array(
168 | array('http://example.com', array('one', 'two'))
169 | , array('curie', array(array('hello' => 'world', 'herp' => 'derp')))
170 | );
171 | }
172 |
173 | /**
174 | * @dataProvider eventProvider
175 | */
176 | public function testEvent($topic, $payload) {
177 | $conn = new WampConnection($this->newConn());
178 | $conn->event($topic, $payload);
179 |
180 | $eventString = $conn->last['send'];
181 |
182 | $this->assertSame(array(8, $topic, $payload), json_decode($eventString, true));
183 | }
184 |
185 | public function testOnClosePropagation() {
186 | $conn = new Connection;
187 |
188 | $this->_comp->onOpen($conn);
189 | $this->_comp->onClose($conn);
190 |
191 | $class = new \ReflectionClass('Ratchet\\Wamp\\WampConnection');
192 | $method = $class->getMethod('getConnection');
193 | $method->setAccessible(true);
194 |
195 | $check = $method->invokeArgs($this->_app->last['onClose'][0], array());
196 |
197 | $this->assertSame($conn, $check);
198 | }
199 |
200 | public function testOnErrorPropagation() {
201 | $conn = new Connection;
202 |
203 | $e = new \Exception('Nope');
204 |
205 | $this->_comp->onOpen($conn);
206 | $this->_comp->onError($conn, $e);
207 |
208 | $class = new \ReflectionClass('Ratchet\\Wamp\\WampConnection');
209 | $method = $class->getMethod('getConnection');
210 | $method->setAccessible(true);
211 |
212 | $check = $method->invokeArgs($this->_app->last['onError'][0], array());
213 |
214 | $this->assertSame($conn, $check);
215 | $this->assertSame($e, $this->_app->last['onError'][1]);
216 | }
217 |
218 | public function testPrefix() {
219 | $conn = new WampConnection($this->newConn());
220 | $this->_comp->onOpen($conn);
221 |
222 | $prefix = 'incoming';
223 | $fullURI = "http://example.com/$prefix";
224 | $method = 'call';
225 |
226 | $this->_comp->onMessage($conn, json_encode(array(1, $prefix, $fullURI)));
227 |
228 | $this->assertEquals($fullURI, $conn->WAMP->prefixes[$prefix]);
229 | $this->assertEquals("$fullURI#$method", $conn->getUri("$prefix:$method"));
230 | }
231 |
232 | public function testMessageMustBeJson() {
233 | if (method_exists($this, 'expectException')) {
234 | $this->expectException('Ratchet\Wamp\Exception');
235 | } else {
236 | $this->setExpectedException('Ratchet\Wamp\Exception');
237 | }
238 |
239 | $conn = new Connection;
240 |
241 | $this->_comp->onOpen($conn);
242 | $this->_comp->onMessage($conn, 'Hello World!');
243 | }
244 |
245 | public function testGetSubProtocolsReturnsArray() {
246 | $this->assertTrue(is_array($this->_comp->getSubProtocols()));
247 | }
248 |
249 | public function testGetSubProtocolsGetFromApp() {
250 | $this->_app->protocols = array('hello', 'world');
251 |
252 | $this->assertGreaterThanOrEqual(3, count($this->_comp->getSubProtocols()));
253 | }
254 |
255 | public function testWampOnMessageApp() {
256 | $app = $this->getMockBuilder('Ratchet\\Wamp\\WampServerInterface')->getMock();
257 | $wamp = new ServerProtocol($app);
258 |
259 | $this->assertContains('wamp', $wamp->getSubProtocols());
260 | }
261 |
262 | public function badFormatProvider() {
263 | return array(
264 | array(json_encode(true))
265 | , array('{"valid":"json", "invalid": "message"}')
266 | , array('{"0": "fail", "hello": "world"}')
267 | );
268 | }
269 |
270 | /**
271 | * @dataProvider badFormatProvider
272 | */
273 | public function testValidJsonButInvalidProtocol($message) {
274 | if (method_exists($this, 'expectException')) {
275 | $this->expectException('Ratchet\Wamp\Exception');
276 | } else {
277 | $this->setExpectedException('Ratchet\Wamp\Exception');
278 | }
279 |
280 | $conn = $this->newConn();
281 | $this->_comp->onOpen($conn);
282 | $this->_comp->onMessage($conn, $message);
283 | }
284 |
285 | public function testBadClientInputFromNonStringTopic() {
286 | if (method_exists($this, 'expectException')) {
287 | $this->expectException('Ratchet\Wamp\Exception');
288 | } else {
289 | $this->setExpectedException('Ratchet\Wamp\Exception');
290 | }
291 |
292 | $conn = new WampConnection($this->newConn());
293 | $this->_comp->onOpen($conn);
294 |
295 | $this->_comp->onMessage($conn, json_encode([5, ['hells', 'nope']]));
296 | }
297 |
298 | public function testBadPrefixWithNonStringTopic() {
299 | if (method_exists($this, 'expectException')) {
300 | $this->expectException('Ratchet\Wamp\Exception');
301 | } else {
302 | $this->setExpectedException('Ratchet\Wamp\Exception');
303 | }
304 |
305 | $conn = new WampConnection($this->newConn());
306 | $this->_comp->onOpen($conn);
307 |
308 | $this->_comp->onMessage($conn, json_encode([1, ['hells', 'nope'], ['bad', 'input']]));
309 | }
310 |
311 | public function testBadPublishWithNonStringTopic() {
312 | if (method_exists($this, 'expectException')) {
313 | $this->expectException('Ratchet\Wamp\Exception');
314 | } else {
315 | $this->setExpectedException('Ratchet\Wamp\Exception');
316 | }
317 |
318 | $conn = new WampConnection($this->newConn());
319 | $this->_comp->onOpen($conn);
320 |
321 | $this->_comp->onMessage($conn, json_encode([7, ['bad', 'input'], 'Hider']));
322 | }
323 | }
324 |
--------------------------------------------------------------------------------
/tests/unit/Wamp/TopicManagerTest.php:
--------------------------------------------------------------------------------
1 | conn = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
27 | $this->mock = $this->getMockBuilder('Ratchet\Wamp\WampServerInterface')->getMock();
28 | $this->mngr = new TopicManager($this->mock);
29 |
30 | $this->conn->WAMP = new \StdClass;
31 | $this->mngr->onOpen($this->conn);
32 | }
33 |
34 | public function testGetTopicReturnsTopicObject() {
35 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
36 | $method = $class->getMethod('getTopic');
37 | $method->setAccessible(true);
38 |
39 | $topic = $method->invokeArgs($this->mngr, array('The Topic'));
40 |
41 | $this->assertInstanceOf('Ratchet\Wamp\Topic', $topic);
42 | }
43 |
44 | public function testGetTopicCreatesTopicWithSameName() {
45 | $name = 'The Topic';
46 |
47 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
48 | $method = $class->getMethod('getTopic');
49 | $method->setAccessible(true);
50 |
51 | $topic = $method->invokeArgs($this->mngr, array($name));
52 |
53 | $this->assertEquals($name, $topic->getId());
54 | }
55 |
56 | public function testGetTopicReturnsSameObject() {
57 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
58 | $method = $class->getMethod('getTopic');
59 | $method->setAccessible(true);
60 |
61 | $topic = $method->invokeArgs($this->mngr, array('No copy'));
62 | $again = $method->invokeArgs($this->mngr, array('No copy'));
63 |
64 | $this->assertSame($topic, $again);
65 | }
66 |
67 | public function testOnOpen() {
68 | $this->mock->expects($this->once())->method('onOpen');
69 | $this->mngr->onOpen($this->conn);
70 | }
71 |
72 | public function testOnCall() {
73 | $id = uniqid();
74 |
75 | $this->mock->expects($this->once())->method('onCall')->with(
76 | $this->conn
77 | , $id
78 | , $this->isInstanceOf('Ratchet\Wamp\Topic')
79 | , array()
80 | );
81 |
82 | $this->mngr->onCall($this->conn, $id, 'new topic', array());
83 | }
84 |
85 | public function testOnSubscribeCreatesTopicObject() {
86 | $this->mock->expects($this->once())->method('onSubscribe')->with(
87 | $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic')
88 | );
89 |
90 | $this->mngr->onSubscribe($this->conn, 'new topic');
91 | }
92 |
93 | public function testTopicIsInConnectionOnSubscribe() {
94 | $name = 'New Topic';
95 |
96 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
97 | $method = $class->getMethod('getTopic');
98 | $method->setAccessible(true);
99 |
100 | $topic = $method->invokeArgs($this->mngr, array($name));
101 |
102 | $this->mngr->onSubscribe($this->conn, $name);
103 |
104 | $this->assertTrue($this->conn->WAMP->subscriptions->contains($topic));
105 | }
106 |
107 | public function testDoubleSubscriptionFiresOnce() {
108 | $this->mock->expects($this->exactly(1))->method('onSubscribe');
109 |
110 | $this->mngr->onSubscribe($this->conn, 'same topic');
111 | $this->mngr->onSubscribe($this->conn, 'same topic');
112 | }
113 |
114 | public function testUnsubscribeEvent() {
115 | $name = 'in and out';
116 | $this->mock->expects($this->once())->method('onUnsubscribe')->with(
117 | $this->conn, $this->isInstanceOf('Ratchet\Wamp\Topic')
118 | );
119 |
120 | $this->mngr->onSubscribe($this->conn, $name);
121 | $this->mngr->onUnsubscribe($this->conn, $name);
122 | }
123 |
124 | public function testUnsubscribeFiresOnce() {
125 | $name = 'getting sleepy';
126 | $this->mock->expects($this->exactly(1))->method('onUnsubscribe');
127 |
128 | $this->mngr->onSubscribe($this->conn, $name);
129 | $this->mngr->onUnsubscribe($this->conn, $name);
130 | $this->mngr->onUnsubscribe($this->conn, $name);
131 | }
132 |
133 | public function testUnsubscribeRemovesTopicFromConnection() {
134 | $name = 'Bye Bye Topic';
135 |
136 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
137 | $method = $class->getMethod('getTopic');
138 | $method->setAccessible(true);
139 |
140 | $topic = $method->invokeArgs($this->mngr, array($name));
141 |
142 | $this->mngr->onSubscribe($this->conn, $name);
143 | $this->mngr->onUnsubscribe($this->conn, $name);
144 |
145 | $this->assertFalse($this->conn->WAMP->subscriptions->contains($topic));
146 | }
147 |
148 | public function testOnPublishBubbles() {
149 | $msg = 'Cover all the code!';
150 |
151 | $this->mock->expects($this->once())->method('onPublish')->with(
152 | $this->conn
153 | , $this->isInstanceOf('Ratchet\Wamp\Topic')
154 | , $msg
155 | , $this->isType('array')
156 | , $this->isType('array')
157 | );
158 |
159 | $this->mngr->onPublish($this->conn, 'topic coverage', $msg, array(), array());
160 | }
161 |
162 | public function testOnCloseBubbles() {
163 | $this->mock->expects($this->once())->method('onClose')->with($this->conn);
164 | $this->mngr->onClose($this->conn);
165 | }
166 |
167 | protected function topicProvider($name) {
168 | $class = new \ReflectionClass('Ratchet\Wamp\TopicManager');
169 | $method = $class->getMethod('getTopic');
170 | $method->setAccessible(true);
171 |
172 | $attribute = $class->getProperty('topicLookup');
173 | $attribute->setAccessible(true);
174 |
175 | $topic = $method->invokeArgs($this->mngr, array($name));
176 |
177 | return array($topic, $attribute);
178 | }
179 |
180 | public function testConnIsRemovedFromTopicOnClose() {
181 | $name = 'State Testing';
182 | list($topic, $attribute) = $this->topicProvider($name);
183 |
184 | $this->assertCount(1, $attribute->getValue($this->mngr));
185 |
186 | $this->mngr->onSubscribe($this->conn, $name);
187 | $this->mngr->onClose($this->conn);
188 |
189 | $this->assertFalse($topic->has($this->conn));
190 | }
191 |
192 | public static function topicConnExpectationProvider() {
193 | return [
194 | [ 'onClose', 0]
195 | , ['onUnsubscribe', 0]
196 | ];
197 | }
198 |
199 | /**
200 | * @dataProvider topicConnExpectationProvider
201 | */
202 | public function testTopicRetentionFromLeavingConnections($methodCall, $expectation) {
203 | $topicName = 'checkTopic';
204 | list($topic, $attribute) = $this->topicProvider($topicName);
205 |
206 | $this->mngr->onSubscribe($this->conn, $topicName);
207 | call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName));
208 |
209 | $this->assertCount($expectation, $attribute->getValue($this->mngr));
210 | }
211 |
212 | public function testOnErrorBubbles() {
213 | $e = new \Exception('All work and no play makes Chris a dull boy');
214 | $this->mock->expects($this->once())->method('onError')->with($this->conn, $e);
215 |
216 | $this->mngr->onError($this->conn, $e);
217 | }
218 |
219 | public function testGetSubProtocolsReturnsArray() {
220 | if (method_exists($this, 'assertIsArray')) {
221 | // PHPUnit 7+
222 | $this->assertIsArray($this->mngr->getSubProtocols());
223 | } else {
224 | // legacy PHPUnit
225 | $this->assertInternalType('array', $this->mngr->getSubProtocols());
226 | }
227 | }
228 |
229 | public function testGetSubProtocolsBubbles() {
230 | $subs = array('hello', 'world');
231 | $app = $this->getMockBuilder('Ratchet\Wamp\Stub\WsWampServerInterface')->getMock();
232 | $app->expects($this->once())->method('getSubProtocols')->will($this->returnValue($subs));
233 | $mngr = new TopicManager($app);
234 |
235 | $this->assertEquals($subs, $mngr->getSubProtocols());
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/tests/unit/Wamp/TopicTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($id, $topic->getId());
15 | }
16 |
17 | public function testAddAndCount() {
18 | $topic = new Topic('merp');
19 |
20 | $topic->add($this->newConn());
21 | $topic->add($this->newConn());
22 | $topic->add($this->newConn());
23 |
24 | $this->assertEquals(3, count($topic));
25 | }
26 |
27 | public function testRemove() {
28 | $topic = new Topic('boop');
29 | $tracked = $this->newConn();
30 |
31 | $topic->add($this->newConn());
32 | $topic->add($tracked);
33 | $topic->add($this->newConn());
34 |
35 | $topic->remove($tracked);
36 |
37 | $this->assertEquals(2, count($topic));
38 | }
39 |
40 | public function testBroadcast() {
41 | $msg = 'Hello World!';
42 | $name = 'Batman';
43 | $protocol = json_encode(array(8, $name, $msg));
44 |
45 | $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
46 | $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
47 |
48 | $first->expects($this->once())
49 | ->method('send')
50 | ->with($this->equalTo($protocol));
51 |
52 | $second->expects($this->once())
53 | ->method('send')
54 | ->with($this->equalTo($protocol));
55 |
56 | $topic = new Topic($name);
57 | $topic->add($first);
58 | $topic->add($second);
59 |
60 | $topic->broadcast($msg);
61 | }
62 |
63 | public function testBroadcastWithExclude() {
64 | $msg = 'Hello odd numbers';
65 | $name = 'Excluding';
66 | $protocol = json_encode(array(8, $name, $msg));
67 |
68 | $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
69 | $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
70 | $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
71 |
72 | $first->expects($this->once())
73 | ->method('send')
74 | ->with($this->equalTo($protocol));
75 |
76 | $second->expects($this->never())->method('send');
77 |
78 | $third->expects($this->once())
79 | ->method('send')
80 | ->with($this->equalTo($protocol));
81 |
82 | $topic = new Topic($name);
83 | $topic->add($first);
84 | $topic->add($second);
85 | $topic->add($third);
86 |
87 | $topic->broadcast($msg, array($second->WAMP->sessionId));
88 | }
89 |
90 | public function testBroadcastWithEligible() {
91 | $msg = 'Hello white list';
92 | $name = 'Eligible';
93 | $protocol = json_encode(array(8, $name, $msg));
94 |
95 | $first = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
96 | $second = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
97 | $third = $this->getMockBuilder('Ratchet\\Wamp\\WampConnection')->setMethods(array('send'))->setConstructorArgs(array($this->getMockBuilder('Ratchet\Mock\Connection')->getMock()))->getMock();
98 |
99 | $first->expects($this->once())
100 | ->method('send')
101 | ->with($this->equalTo($protocol));
102 |
103 | $second->expects($this->never())->method('send');
104 |
105 | $third->expects($this->once())
106 | ->method('send')
107 | ->with($this->equalTo($protocol));
108 |
109 | $topic = new Topic($name);
110 | $topic->add($first);
111 | $topic->add($second);
112 | $topic->add($third);
113 |
114 | $topic->broadcast($msg, array(), array($first->WAMP->sessionId, $third->WAMP->sessionId));
115 | }
116 |
117 | public function testIterator() {
118 | $first = $this->newConn();
119 | $second = $this->newConn();
120 | $third = $this->newConn();
121 |
122 | $topic = new Topic('Joker');
123 | $topic->add($first)->add($second)->add($third);
124 |
125 | $check = array($first, $second, $third);
126 |
127 | foreach ($topic as $mock) {
128 | $this->assertNotSame(false, array_search($mock, $check));
129 | }
130 | }
131 |
132 | public function testToString() {
133 | $name = 'Bane';
134 | $topic = new Topic($name);
135 |
136 | $this->assertEquals($name, (string)$topic);
137 | }
138 |
139 | public function testDoesHave() {
140 | $conn = $this->newConn();
141 | $topic = new Topic('Two Face');
142 | $topic->add($conn);
143 |
144 | $this->assertTrue($topic->has($conn));
145 | }
146 |
147 | public function testDoesNotHave() {
148 | $conn = $this->newConn();
149 | $topic = new Topic('Alfred');
150 |
151 | $this->assertFalse($topic->has($conn));
152 | }
153 |
154 | public function testDoesNotHaveAfterRemove() {
155 | $conn = $this->newConn();
156 | $topic = new Topic('Ras');
157 |
158 | $topic->add($conn)->remove($conn);
159 |
160 | $this->assertFalse($topic->has($conn));
161 | }
162 |
163 | protected function newConn() {
164 | return new WampConnection($this->getMockBuilder('Ratchet\Mock\Connection')->getMock());
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/tests/unit/Wamp/WampConnectionTest.php:
--------------------------------------------------------------------------------
1 | mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
18 | $this->conn = new WampConnection($this->mock);
19 | }
20 |
21 | public function testCallResult() {
22 | $callId = uniqid();
23 | $data = array('hello' => 'world', 'herp' => 'derp');
24 |
25 | $this->mock->expects($this->once())->method('send')->with(json_encode(array(3, $callId, $data)));
26 |
27 | $this->conn->callResult($callId, $data);
28 | }
29 |
30 | public function testCallError() {
31 | $callId = uniqid();
32 | $uri = 'http://example.com/end/point';
33 |
34 | $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, '')));
35 |
36 | $this->conn->callError($callId, $uri);
37 | }
38 |
39 | public function testCallErrorWithTopic() {
40 | $callId = uniqid();
41 | $uri = 'http://example.com/end/point';
42 |
43 | $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, '')));
44 |
45 | $this->conn->callError($callId, new Topic($uri));
46 | }
47 |
48 | public function testDetailedCallError() {
49 | $callId = uniqid();
50 | $uri = 'http://example.com/end/point';
51 | $desc = 'beep boop beep';
52 | $detail = 'Error: Too much awesome';
53 |
54 | $this->mock->expects($this->once())->method('send')->with(json_encode(array(4, $callId, $uri, $desc, $detail)));
55 |
56 | $this->conn->callError($callId, $uri, $desc, $detail);
57 | }
58 |
59 | public function testPrefix() {
60 | $shortOut = 'outgoing';
61 | $longOut = 'http://example.com/outgoing';
62 |
63 | $this->mock->expects($this->once())->method('send')->with(json_encode(array(1, $shortOut, $longOut)));
64 |
65 | $this->conn->prefix($shortOut, $longOut);
66 | }
67 |
68 | public function testGetUriWhenNoCurieGiven() {
69 | $uri = 'http://example.com/noshort';
70 |
71 | $this->assertEquals($uri, $this->conn->getUri($uri));
72 | }
73 |
74 | public function testClose() {
75 | $mock = $this->getMockBuilder('Ratchet\Mock\Connection')->getMock();
76 | $conn = new WampConnection($mock);
77 |
78 | $mock->expects($this->once())->method('close');
79 |
80 | $conn->close();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/unit/Wamp/WampServerTest.php:
--------------------------------------------------------------------------------
1 | _app->expects($this->once())->method('onPublish')->with(
25 | $this->isExpectedConnection()
26 | , $this->isInstanceOf('Ratchet\Wamp\Topic')
27 | , $published
28 | , array()
29 | , array()
30 | );
31 |
32 | $this->_serv->onMessage($this->_conn, json_encode(array(7, 'topic', $published)));
33 | }
34 |
35 | public function testGetSubProtocols() {
36 | // todo: could expand on this
37 | if (method_exists($this, 'assertIsArray')) {
38 | // PHPUnit 7+
39 | $this->assertIsArray($this->_serv->getSubProtocols());
40 | } else {
41 | // legacy PHPUnit
42 | $this->assertInternalType('array', $this->_serv->getSubProtocols());
43 | }
44 | }
45 |
46 | public function testConnectionClosesOnInvalidJson() {
47 | $this->_conn->expects($this->once())->method('close');
48 | $this->_serv->onMessage($this->_conn, 'invalid json');
49 | }
50 |
51 | public function testConnectionClosesOnProtocolError() {
52 | $this->_conn->expects($this->once())->method('close');
53 | $this->_serv->onMessage($this->_conn, json_encode(array('valid' => 'json', 'invalid' => 'protocol')));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------