575 changed files with 76309 additions and 0 deletions
@ -0,0 +1,179 @@
@@ -0,0 +1,179 @@
|
||||
<?php |
||||
require_once 'config.php'; |
||||
|
||||
/** |
||||
* @fileName comm |
||||
* @author Amir <amirsanni@gmail.com> |
||||
* @date 22-Dec-2016 |
||||
*/ |
||||
|
||||
?> |
||||
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
||||
<title>Chat App</title> |
||||
|
||||
<!-- Favicon --> |
||||
<link rel="shortcut icon" href="img/favicon.ico"> |
||||
<!-- favicon ends --> |
||||
|
||||
<!--- LOAD FILES --> |
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> |
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"> |
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome-animation/0.0.8/font-awesome-animation.min.css"> |
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> |
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> |
||||
|
||||
<!-- Custom styles --> |
||||
<link rel="stylesheet" href="css/comm.css"> |
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> |
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> |
||||
<!--[if lt IE 9]> |
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.3/html5shiv.js"></script> |
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> |
||||
<![endif]--> |
||||
</head> |
||||
|
||||
|
||||
<body> |
||||
<div class="container-fluid"> |
||||
<div class="row"> |
||||
<!-- Remote Video --> |
||||
<video id="peerVid" poster="img/vidbg.png" playsinline autoplay></video> |
||||
<!-- Remote Video --> |
||||
</div> |
||||
|
||||
<div class="row margin-top-20"> |
||||
<!-- Call Buttons --> |
||||
<div class="col-sm-12 text-center" id="callBtns"> |
||||
<button class="btn btn-success btn-sm initCall" id="initAudio"><i class="fa fa-phone"></i></button> |
||||
<button class="btn btn-info btn-sm initCall" id="initVideo"><i class="fa fa-video-camera"></i></button> |
||||
<button class="btn btn-danger btn-sm" id="terminateCall" disabled><i class="fa fa-phone-square"></i></button> |
||||
</div> |
||||
<!-- Call Buttons --> |
||||
|
||||
<!-- Timer --> |
||||
<div class="col-sm-12 text-center margin-top-5" style="color:#fff"> |
||||
<span id="countHr"></span>h: |
||||
<span id="countMin"></span>m: |
||||
<span id="countSec"></span>s |
||||
</div> |
||||
<!-- Timer --> |
||||
</div> |
||||
|
||||
|
||||
<!-- Local Video --> |
||||
<div class="row"> |
||||
<div class="col-sm-12"> |
||||
<video id="myVid" poster="img/vidbg.png" muted autoplay></video> |
||||
</div> |
||||
</div> |
||||
<!-- Local Video --> |
||||
</div> |
||||
|
||||
<div class="container-fluid chat-pane"> |
||||
<!-- CHAT PANEL--> |
||||
<div class="row chat-window col-xs-12 col-md-4"> |
||||
<div class=""> |
||||
<div class="panel panel-default chat-pane-panel"> |
||||
<div class="panel-heading chat-pane-top-bar"> |
||||
<div class="col-xs-10" style="margin-left:-20px"> |
||||
<i class="fa fa-comment" id="remoteStatus"></i> Remote |
||||
<b id="remoteStatusTxt">(Offline)</b> |
||||
</div> |
||||
<div class="col-xs-2 pull-right"> |
||||
<span id="minim_chat_window" class="panel-collapsed fa fa-plus icon_minim pointer"></span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="panel-body msg_container_base" id="chats"></div> |
||||
|
||||
<div class="panel-footer"> |
||||
<span id="typingInfo"></span> |
||||
<div class="input-group"> |
||||
<input id="chatInput" type="text" class="form-control input-sm chat_input" placeholder="Type message here..."> |
||||
<span class="input-group-btn"> |
||||
<button class="btn btn-primary btn-sm" id="chatSendBtn">Send</button> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!-- CHAT PANEL --> |
||||
</div> |
||||
|
||||
<!--Modal to show that we are calling--> |
||||
<div id="callModal" class="modal fade" role="dialog" data-backdrop="static"> |
||||
<div class="modal-dialog"> |
||||
<div class="modal-content text-center"> |
||||
<div class="modal-header" id="callerInfo"></div> |
||||
|
||||
<div class="modal-body"> |
||||
<button type="button" class="btn btn-danger btn-sm" id='endCall'> |
||||
<i class="fa fa-times-circle"></i> End Call |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--Modal end--> |
||||
|
||||
|
||||
<!--Modal to give options to receive call--> |
||||
<div id="rcivModal" class="modal fade" role="dialog" data-backdrop="static"> |
||||
<div class="modal-dialog"> |
||||
<div class="modal-content"> |
||||
<div class="modal-header" id="calleeInfo"></div> |
||||
|
||||
<div class="modal-body text-center"> |
||||
<button type="button" class="btn btn-success btn-sm answerCall" id='startAudio'> |
||||
<i class="fa fa-phone"></i> Audio Call |
||||
</button> |
||||
<button type="button" class="btn btn-success btn-sm answerCall" id='startVideo'> |
||||
<i class="fa fa-video-camera"></i> Video Call |
||||
</button> |
||||
<button type="button" class="btn btn-danger btn-sm" id='rejectCall'> |
||||
<i class="fa fa-times-circle"></i> Reject Call |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--Modal end--> |
||||
|
||||
<!--Modal to show flash message--> |
||||
<div id="flashMsgModal" class="modal fade" role="dialog" data-backdrop="static"> |
||||
<div class="modal-dialog"> |
||||
<div class="modal-content"> |
||||
<div class="modal-header" id="flashMsgHeader"> |
||||
<button type="button" class="close" id='closeFlashMsg' data-dismiss="modal">×</button> |
||||
<center><i id="flashMsgIcon"></i> <font id="flashMsg"></font></center> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<!--Modal end--> |
||||
|
||||
<!--Snackbar --> |
||||
<div id="snackbar"></div> |
||||
<!-- Snackbar --> |
||||
|
||||
<!-- custom js --> |
||||
<script> |
||||
var room = "<?=filter_input(INPUT_GET, 'room')?>"; |
||||
var wsChat = new WebSocket("<?=WS_URL?>"); |
||||
</script> |
||||
<script src="js/adapter.js"></script> |
||||
<script src="js/comm.js"></script> |
||||
<audio id="callerTone" src="media/callertone.mp3" loop preload="auto"></audio> |
||||
<audio id="msgTone" src="media/msgtone.mp3" preload="auto"></audio> |
||||
</body> |
||||
</html> |
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
<?php |
||||
define('WS_URL', 'ws://localhost:8080/comm'); |
@ -0,0 +1,232 @@
@@ -0,0 +1,232 @@
|
||||
/* |
||||
Created on : 22-Dec-2016 |
||||
Author : Amir <amirsanni@gmail.com> |
||||
*/ |
||||
|
||||
.margin-top-20{ |
||||
margin-top: 20px; |
||||
} |
||||
|
||||
.pointer{ |
||||
cursor: pointer; |
||||
} |
||||
|
||||
/* CHAT PANE */ |
||||
#typingInfo{ |
||||
font-size: 10px; |
||||
color: #3a87ad; |
||||
} |
||||
|
||||
.col-md-4, .col-md-10{ |
||||
padding:0; |
||||
} |
||||
.chat-pane-panel{ |
||||
margin-bottom: 0px; |
||||
} |
||||
.chat-window{ |
||||
bottom:0; |
||||
position:fixed; |
||||
} |
||||
.chat-pane-panel{ |
||||
border-radius: 5px 5px 0 0; |
||||
} |
||||
.icon_minim{ |
||||
padding:2px 10px; |
||||
} |
||||
.msg_container_base{ |
||||
background: #e5e5e5; |
||||
margin: 0; |
||||
padding: 0 10px 10px; |
||||
max-height:300px; |
||||
overflow-x:hidden; |
||||
display: none; |
||||
} |
||||
.chat-pane-top-bar { |
||||
background: #666; |
||||
color: white; |
||||
position: relative; |
||||
overflow: hidden; |
||||
} |
||||
.msg_receive{ |
||||
padding-left:0; |
||||
margin-left:0; |
||||
} |
||||
.msg_sent{ |
||||
padding-bottom:20px !important; |
||||
margin-right:0; |
||||
} |
||||
.messages { |
||||
background: white; |
||||
padding: 10px; |
||||
border-radius: 2px; |
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); |
||||
max-width:100%; |
||||
} |
||||
.messages > p { |
||||
font-size: 13px; |
||||
margin: 0 0 0.2rem 0; |
||||
word-wrap: break-word |
||||
} |
||||
.messages > time { |
||||
font-size: 11px; |
||||
color: #ccc; |
||||
} |
||||
.msg_container { |
||||
padding: 10px; |
||||
overflow: hidden; |
||||
display: flex; |
||||
} |
||||
.chat-pane-img { |
||||
display: block; |
||||
width: 100%; |
||||
} |
||||
.chat-avatar { |
||||
position: relative; |
||||
} |
||||
.base_receive > .chat-avatar:after { |
||||
content: ""; |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
width: 0; |
||||
height: 0; |
||||
border: 5px solid #FFF; |
||||
border-left-color: rgba(0, 0, 0, 0); |
||||
border-bottom-color: rgba(0, 0, 0, 0); |
||||
} |
||||
|
||||
.base_sent { |
||||
justify-content: flex-end; |
||||
align-items: flex-end; |
||||
} |
||||
.base_sent > .chat-avatar:after { |
||||
content: ""; |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: 0; |
||||
height: 0; |
||||
border: 5px solid white; |
||||
border-right-color: transparent; |
||||
border-top-color: transparent; |
||||
box-shadow: 1px 1px 2px rgba(black, 0.2); /*not quite perfect but close*/ |
||||
} |
||||
|
||||
.msg_sent > time{ |
||||
float: right; |
||||
} |
||||
|
||||
|
||||
|
||||
.msg_container_base::-webkit-scrollbar-track |
||||
{ |
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); |
||||
background-color: #F5F5F5; |
||||
} |
||||
|
||||
.msg_container_base::-webkit-scrollbar |
||||
{ |
||||
width: 12px; |
||||
background-color: #F5F5F5; |
||||
} |
||||
|
||||
.msg_container_base::-webkit-scrollbar-thumb |
||||
{ |
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); |
||||
background-color: #555; |
||||
} |
||||
/* _CHAT PANE */ |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/* VIDEO */ |
||||
#peerVid{ |
||||
position: fixed; |
||||
top: 50%; |
||||
left: 50%; |
||||
min-width: 100%; |
||||
min-height: 100%; |
||||
width: auto; |
||||
height: auto; |
||||
z-index: -100; |
||||
-ms-transform: translateX(-50%) translateY(-50%); |
||||
-moz-transform: translateX(-50%) translateY(-50%); |
||||
-webkit-transform: translateX(-50%) translateY(-50%); |
||||
transform: translateX(-50%) translateY(-50%); |
||||
background: url(../img/vidbg.png) no-repeat; |
||||
background-size: cover; |
||||
} |
||||
|
||||
#myVid{ |
||||
width: 300px; |
||||
height: 200px; |
||||
bottom: 0; |
||||
position: fixed; |
||||
right: 0; |
||||
} |
||||
|
||||
|
||||
/* Small screens */ |
||||
@media(max-width:768px){ |
||||
#myVid{ |
||||
width: 200px; |
||||
height: 100px; |
||||
bottom: 100px; |
||||
position: fixed; |
||||
left: 0; |
||||
} |
||||
} |
||||
|
||||
/* _VIDEO */ |
||||
|
||||
|
||||
|
||||
/* SNACKBAR */ |
||||
/* The snackbar - position it at the bottom and in the middle of the screen */ |
||||
#snackbar { |
||||
visibility: hidden; /* Hidden by default. Visible on click */ |
||||
min-width: 250px; /* Set a default minimum width */ |
||||
margin-left: -125px; /* Divide value of min-width by 2 */ |
||||
background-color: rgba(158,31,99,0.9); /* background color */ |
||||
color: #fff; /* White text color */ |
||||
text-align: center; /* Centered text */ |
||||
border-radius: 2px; /* Rounded borders */ |
||||
padding: 16px; /* Padding */ |
||||
position: fixed; /* Sit on top of the screen */ |
||||
z-index: 1; /* Add a z-index if needed */ |
||||
right: 0; /* Right the snackbar */ |
||||
top: 90px; /* 90px from the top */ |
||||
} |
||||
|
||||
/* Show the snackbar when clicking on a button (class added with JavaScript) */ |
||||
#snackbar.show { |
||||
visibility: visible; /* Show the snackbar */ |
||||
} |
||||
|
||||
/* Animations to fade the snackbar in and out */ |
||||
@-webkit-keyframes fadein { |
||||
from {right: 0; opacity: 0;} |
||||
to {top: 90px; opacity: 1;} |
||||
} |
||||
|
||||
@keyframes fadein { |
||||
from {right: 0; opacity: 0;} |
||||
to {top: 90px; opacity: 1;} |
||||
} |
||||
|
||||
@-webkit-keyframes fadeout { |
||||
from {top: 90px; opacity: 1;} |
||||
to {right: 0; opacity: 0;} |
||||
} |
||||
|
||||
@keyframes fadeout { |
||||
from {top: 90px; opacity: 1;} |
||||
to {right: 0; opacity: 0;} |
||||
} |
||||
/* _SNACKBAR */ |
After Width: | Height: | Size: 361 KiB |
After Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
<?php |
||||
/** |
||||
* @fileName index.php |
||||
* @author Amir <amirsanni@gmail.com> |
||||
* @date 22-Dec-2016 |
||||
*/ |
||||
?> |
||||
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
|
||||
<title>Chat App</title> |
||||
|
||||
<!-- Favicon --> |
||||
<link rel="shortcut icon" href="img/favicon.ico"> |
||||
<!-- favicon ends --> |
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> |
||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> |
||||
<!--[if lt IE 9]> |
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.3/html5shiv.js"></script> |
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> |
||||
<![endif]--> |
||||
</head> |
||||
|
||||
|
||||
<body> |
||||
<input type="button" id='createRoom'> |
||||
</body> |
||||
</html> |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,195 @@
@@ -0,0 +1,195 @@
|
||||
<?php |
||||
namespace Amir; |
||||
|
||||
/** |
||||
* Description of Comm |
||||
* |
||||
* @author Amir <amirsanni@gmail.com> |
||||
* @date 26-Oct-2016 |
||||
*/ |
||||
|
||||
|
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
class Comm implements MessageComponentInterface { |
||||
protected $clients; |
||||
private $rooms; |
||||
|
||||
public function __construct() { |
||||
$this->clients = new \SplObjectStorage; |
||||
$this->rooms = []; |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/** |
||||
* |
||||
* @param ConnectionInterface $conn |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
// Store the new connection |
||||
$this->clients->attach($conn); |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/** |
||||
* |
||||
* @param ConnectionInterface $from |
||||
* @param type $msg |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
$data = json_decode($msg); |
||||
$action = $data->action; |
||||
$room = isset($data->room) ? $data->room : ""; |
||||
|
||||
if(($action == 'subscribe') && $room){ |
||||
//subscribe user to room only if he hasn't subscribed |
||||
|
||||
//if room exist and user is yet to subscribe, then subscibe him to room |
||||
//OR |
||||
//if room does not exist, create it by adding user to it |
||||
if((array_key_exists($room, $this->rooms) && !in_array($from, $this->rooms[$room])) || !array_key_exists($room, $this->rooms)){ |
||||
if(isset($this->rooms[$room]) && count($this->rooms[$room]) >= 2){ |
||||
//maximum number of connection reached |
||||
$msg_to_send = json_encode(['action'=>'subRejected']); |
||||
|
||||
$from->send($msg_to_send); |
||||
} |
||||
|
||||
else{ |
||||
$this->rooms[$room][] = $from;//subscribe user to room |
||||
|
||||
$this->notifyUsersOfConnection($room, $from); |
||||
} |
||||
} |
||||
|
||||
else{ |
||||
//tell user he has subscribed on another device/browser |
||||
$msg_to_send = json_encode(['action'=>'subRejected']); |
||||
|
||||
$from->send($msg_to_send); |
||||
} |
||||
} |
||||
|
||||
//for other actions |
||||
else if($room && isset($this->rooms[$room])){ |
||||
//send to everybody subscribed to the room received except the sender |
||||
foreach($this->rooms[$room] as $client){ |
||||
if ($client !== $from) { |
||||
$client->send($msg); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/** |
||||
* |
||||
* @param ConnectionInterface $conn |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
// The connection is closed, remove connection |
||||
$this->clients->detach($conn); |
||||
|
||||
if(count($this->rooms)){//if there is at least one room created |
||||
foreach($this->rooms as $room=>$arr_of_subscribers){//loop through the rooms |
||||
foreach ($arr_of_subscribers as $key=>$ratchet_conn){//loop through the users connected to each room |
||||
if($ratchet_conn == $conn){//if the disconnecting user subscribed to this room |
||||
unset($this->rooms[$room][$key]);//remove him from the room |
||||
|
||||
//notify other subscribers that he has disconnected |
||||
$this->notifyUsersOfDisconnection($room, $conn); |
||||
|
||||
echo 'user left'; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/** |
||||
* |
||||
* @param ConnectionInterface $conn |
||||
* @param \Exception $e |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
//echo "An error has occurred: {$e->getMessage()}\n"; |
||||
|
||||
$conn->close(); |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
/** |
||||
* |
||||
* @param type $room |
||||
* @param type $from |
||||
*/ |
||||
private function notifyUsersOfConnection($room, $from){ |
||||
|
||||
//echo "User subscribed to room ".$room ."\n"; |
||||
|
||||
$msg_to_broadcast = json_encode(['action'=>'newSub', 'room'=>$room]); |
||||
|
||||
//notify user that someone has joined room |
||||
foreach($this->rooms[$room] as $client){ |
||||
if ($client !== $from) { |
||||
$client->send($msg_to_broadcast); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
******************************************************************************************************************************** |
||||
*/ |
||||
|
||||
private function notifyUsersOfDisconnection($room, $from){ |
||||
$msg_to_broadcast = json_encode(['action'=>'imOffline', 'room'=>$room]); |
||||
|
||||
//notify user that remote has left the room |
||||
foreach($this->rooms[$room] as $client){ |
||||
if ($client !== $from) { |
||||
$client->send($msg_to_broadcast); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
/** |
||||
* Description of server |
||||
* |
||||
* @author Amir <amirsanni@gmail.com> |
||||
* @date 23-Dec-2016 |
||||
*/ |
||||
|
||||
require '../vendor/autoload.php'; |
||||
|
||||
use Amir\Comm; |
||||
|
||||
//set an array of origins allowed to connect to this server |
||||
$allowed_origins = ['localhost', '127.0.0.1']; |
||||
|
||||
// Run the server application through the WebSocket protocol on port 8080 |
||||
$app = new Ratchet\App('localhost', 8080, '0.0.0.0');//App(hostname, port, 'whoCanConnectIP', '') |
||||
|
||||
//create socket routes |
||||
//route(uri, classInstance, arrOfAllowedOrigins) |
||||
$app->route('/comm', new Comm, $allowed_origins); |
||||
|
||||
//run websocket |
||||
$app->run(); |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
{ |
||||
"require":{ |
||||
"cboden/ratchet" : "0.3.*" |
||||
}, |
||||
|
||||
"autoload": { |
||||
"psr-4": { |
||||
"Amir\\": "bin/amir/" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,691 @@
@@ -0,0 +1,691 @@
|
||||
{ |
||||
"_readme": [ |
||||
"This file locks the dependencies of your project to a known state", |
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", |
||||
"This file is @generated automatically" |
||||
], |
||||
"hash": "762037d3d8d94aec831d234f71791628", |
||||
"content-hash": "858ccf2b00629904fbfcaf8568d3cb4b", |
||||
"packages": [ |
||||
{ |
||||
"name": "cboden/ratchet", |
||||
"version": "v0.3.5", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/ratchetphp/Ratchet.git", |
||||
"reference": "b5ccecad9390db85d2c8df7cbeb047292fbbf4b8" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/b5ccecad9390db85d2c8df7cbeb047292fbbf4b8", |
||||
"reference": "b5ccecad9390db85d2c8df7cbeb047292fbbf4b8", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"guzzle/http": "^3.6", |
||||
"php": ">=5.3.9", |
||||
"react/socket": "^0.3 || ^0.4", |
||||
"symfony/http-foundation": "^2.2|^3.0", |
||||
"symfony/routing": "^2.2|^3.0" |
||||
}, |
||||
"type": "library", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Ratchet\\": "src/Ratchet" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Chris Boden", |
||||
"email": "cboden@gmail.com", |
||||
"role": "Developer" |
||||
} |
||||
], |
||||
"description": "PHP WebSocket library", |
||||
"homepage": "http://socketo.me", |
||||
"keywords": [ |
||||
"Ratchet", |
||||
"WebSockets", |
||||
"server", |
||||
"sockets" |
||||
], |
||||
"time": "2016-05-25 12:55:03" |
||||
}, |
||||
{ |
||||
"name": "evenement/evenement", |
||||
"version": "v2.0.0", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/igorw/evenement.git", |
||||
"reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/igorw/evenement/zipball/f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", |
||||
"reference": "f6e843799fd4f4184d54d8fc7b5b3551c9fa803e", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.4.0" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "2.0-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Evenement": "src" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Igor Wiedler", |
||||
"email": "igor@wiedler.ch", |
||||
"homepage": "http://wiedler.ch/igor/" |
||||
} |
||||
], |
||||
"description": "Événement is a very simple event dispatching library for PHP", |
||||
"keywords": [ |
||||
"event-dispatcher", |
||||
"event-emitter" |
||||
], |
||||
"time": "2012-11-02 14:49:47" |
||||
}, |
||||
{ |
||||
"name": "guzzle/common", |
||||
"version": "v3.9.2", |
||||
"target-dir": "Guzzle/Common", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/Guzzle3/common.git", |
||||
"reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/Guzzle3/common/zipball/2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc", |
||||
"reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.3.2", |
||||
"symfony/event-dispatcher": ">=2.1" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.7-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Guzzle\\Common": "" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"description": "Common libraries used by Guzzle", |
||||
"homepage": "http://guzzlephp.org/", |
||||
"keywords": [ |
||||
"collection", |
||||
"common", |
||||
"event", |
||||
"exception" |
||||
], |
||||
"abandoned": "guzzle/guzzle", |
||||
"time": "2014-08-11 04:32:36" |
||||
}, |
||||
{ |
||||
"name": "guzzle/http", |
||||
"version": "v3.9.2", |
||||
"target-dir": "Guzzle/Http", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/Guzzle3/http.git", |
||||
"reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/Guzzle3/http/zipball/1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", |
||||
"reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"guzzle/common": "self.version", |
||||
"guzzle/parser": "self.version", |
||||
"guzzle/stream": "self.version", |
||||
"php": ">=5.3.2" |
||||
}, |
||||
"suggest": { |
||||
"ext-curl": "*" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.7-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Guzzle\\Http": "" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Michael Dowling", |
||||
"email": "mtdowling@gmail.com", |
||||
"homepage": "https://github.com/mtdowling" |
||||
} |
||||
], |
||||
"description": "HTTP libraries used by Guzzle", |
||||
"homepage": "http://guzzlephp.org/", |
||||
"keywords": [ |
||||
"Guzzle", |
||||
"client", |
||||
"curl", |
||||
"http", |
||||
"http client" |
||||
], |
||||
"abandoned": "guzzle/guzzle", |
||||
"time": "2014-08-11 04:32:36" |
||||
}, |
||||
{ |
||||
"name": "guzzle/parser", |
||||
"version": "v3.9.2", |
||||
"target-dir": "Guzzle/Parser", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/Guzzle3/parser.git", |
||||
"reference": "6874d171318a8e93eb6d224cf85e4678490b625c" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/Guzzle3/parser/zipball/6874d171318a8e93eb6d224cf85e4678490b625c", |
||||
"reference": "6874d171318a8e93eb6d224cf85e4678490b625c", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.3.2" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.7-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Guzzle\\Parser": "" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"description": "Interchangeable parsers used by Guzzle", |
||||
"homepage": "http://guzzlephp.org/", |
||||
"keywords": [ |
||||
"URI Template", |
||||
"cookie", |
||||
"http", |
||||
"message", |
||||
"url" |
||||
], |
||||
"abandoned": "guzzle/guzzle", |
||||
"time": "2014-02-05 18:29:46" |
||||
}, |
||||
{ |
||||
"name": "guzzle/stream", |
||||
"version": "v3.9.2", |
||||
"target-dir": "Guzzle/Stream", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/Guzzle3/stream.git", |
||||
"reference": "60c7fed02e98d2c518dae8f97874c8f4622100f0" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/Guzzle3/stream/zipball/60c7fed02e98d2c518dae8f97874c8f4622100f0", |
||||
"reference": "60c7fed02e98d2c518dae8f97874c8f4622100f0", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"guzzle/common": "self.version", |
||||
"php": ">=5.3.2" |
||||
}, |
||||
"suggest": { |
||||
"guzzle/http": "To convert Guzzle request objects to PHP streams" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.7-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Guzzle\\Stream": "" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Michael Dowling", |
||||
"email": "mtdowling@gmail.com", |
||||
"homepage": "https://github.com/mtdowling" |
||||
} |
||||
], |
||||
"description": "Guzzle stream wrapper component", |
||||
"homepage": "http://guzzlephp.org/", |
||||
"keywords": [ |
||||
"Guzzle", |
||||
"component", |
||||
"stream" |
||||
], |
||||
"abandoned": "guzzle/guzzle", |
||||
"time": "2014-05-01 21:36:02" |
||||
}, |
||||
{ |
||||
"name": "react/event-loop", |
||||
"version": "v0.4.2", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/reactphp/event-loop.git", |
||||
"reference": "164799f73175e1c80bba92a220ea35df6ca371dd" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/reactphp/event-loop/zipball/164799f73175e1c80bba92a220ea35df6ca371dd", |
||||
"reference": "164799f73175e1c80bba92a220ea35df6ca371dd", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.4.0" |
||||
}, |
||||
"suggest": { |
||||
"ext-event": "~1.0", |
||||
"ext-libev": "*", |
||||
"ext-libevent": ">=0.1.0" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "0.5-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"React\\EventLoop\\": "src" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"description": "Event loop abstraction layer that libraries can use for evented I/O.", |
||||
"keywords": [ |
||||
"asynchronous", |
||||
"event-loop" |
||||
], |
||||
"time": "2016-03-08 02:09:32" |
||||
}, |
||||
{ |
||||
"name": "react/socket", |
||||
"version": "v0.4.3", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/reactphp/socket.git", |
||||
"reference": "ce015ec5879b96f5d30905f035f223aa85013fcc" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/reactphp/socket/zipball/ce015ec5879b96f5d30905f035f223aa85013fcc", |
||||
"reference": "ce015ec5879b96f5d30905f035f223aa85013fcc", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"evenement/evenement": "~2.0|~1.0", |
||||
"php": ">=5.3.0", |
||||
"react/event-loop": "0.4.*|0.3.*", |
||||
"react/stream": "0.4.*|0.3.*" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "0.4-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"React\\Socket\\": "src" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"description": "Library for building an evented socket server.", |
||||
"keywords": [ |
||||
"Socket" |
||||
], |
||||
"time": "2016-03-01 20:10:35" |
||||
}, |
||||
{ |
||||
"name": "react/stream", |
||||
"version": "v0.4.4", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/reactphp/stream.git", |
||||
"reference": "fcc9e7cea126962cff303c90c05e2efdcec09a27" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/reactphp/stream/zipball/fcc9e7cea126962cff303c90c05e2efdcec09a27", |
||||
"reference": "fcc9e7cea126962cff303c90c05e2efdcec09a27", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"evenement/evenement": "^2.0|^1.0", |
||||
"php": ">=5.3.8" |
||||
}, |
||||
"require-dev": { |
||||
"clue/stream-filter": "~1.2", |
||||
"react/event-loop": "^0.4|^0.3", |
||||
"react/promise": "^2.0|^1.0" |
||||
}, |
||||
"suggest": { |
||||
"react/event-loop": "^0.4", |
||||
"react/promise": "^2.0" |
||||
}, |
||||
"type": "library", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"React\\Stream\\": "src" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"description": "Basic readable and writable stream interfaces that support piping.", |
||||
"keywords": [ |
||||
"pipe", |
||||
"stream" |
||||
], |
||||
"time": "2016-08-22 09:51:07" |
||||
}, |
||||
{ |
||||
"name": "symfony/event-dispatcher", |
||||
"version": "v3.1.6", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/symfony/event-dispatcher.git", |
||||
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/28b0832b2553ffb80cabef6a7a812ff1e670c0bc", |
||||
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.9" |
||||
}, |
||||
"require-dev": { |
||||
"psr/log": "~1.0", |
||||
"symfony/config": "~2.8|~3.0", |
||||
"symfony/dependency-injection": "~2.8|~3.0", |
||||
"symfony/expression-language": "~2.8|~3.0", |
||||
"symfony/stopwatch": "~2.8|~3.0" |
||||
}, |
||||
"suggest": { |
||||
"symfony/dependency-injection": "", |
||||
"symfony/http-kernel": "" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.1-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Symfony\\Component\\EventDispatcher\\": "" |
||||
}, |
||||
"exclude-from-classmap": [ |
||||
"/Tests/" |
||||
] |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Fabien Potencier", |
||||
"email": "fabien@symfony.com" |
||||
}, |
||||
{ |
||||
"name": "Symfony Community", |
||||
"homepage": "https://symfony.com/contributors" |
||||
} |
||||
], |
||||
"description": "Symfony EventDispatcher Component", |
||||
"homepage": "https://symfony.com", |
||||
"time": "2016-10-13 06:28:43" |
||||
}, |
||||
{ |
||||
"name": "symfony/http-foundation", |
||||
"version": "v3.1.6", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/symfony/http-foundation.git", |
||||
"reference": "f21e5a8b88274b7720779aa88f9c02c6d6ec08d7" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/f21e5a8b88274b7720779aa88f9c02c6d6ec08d7", |
||||
"reference": "f21e5a8b88274b7720779aa88f9c02c6d6ec08d7", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.9", |
||||
"symfony/polyfill-mbstring": "~1.1" |
||||
}, |
||||
"require-dev": { |
||||
"symfony/expression-language": "~2.8|~3.0" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.1-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Symfony\\Component\\HttpFoundation\\": "" |
||||
}, |
||||
"exclude-from-classmap": [ |
||||
"/Tests/" |
||||
] |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Fabien Potencier", |
||||
"email": "fabien@symfony.com" |
||||
}, |
||||
{ |
||||
"name": "Symfony Community", |
||||
"homepage": "https://symfony.com/contributors" |
||||
} |
||||
], |
||||
"description": "Symfony HttpFoundation Component", |
||||
"homepage": "https://symfony.com", |
||||
"time": "2016-10-24 15:52:44" |
||||
}, |
||||
{ |
||||
"name": "symfony/polyfill-mbstring", |
||||
"version": "v1.2.0", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/symfony/polyfill-mbstring.git", |
||||
"reference": "dff51f72b0706335131b00a7f49606168c582594" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594", |
||||
"reference": "dff51f72b0706335131b00a7f49606168c582594", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.3.3" |
||||
}, |
||||
"suggest": { |
||||
"ext-mbstring": "For best performance" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "1.2-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Symfony\\Polyfill\\Mbstring\\": "" |
||||
}, |
||||
"files": [ |
||||
"bootstrap.php" |
||||
] |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Nicolas Grekas", |
||||
"email": "p@tchwork.com" |
||||
}, |
||||
{ |
||||
"name": "Symfony Community", |
||||
"homepage": "https://symfony.com/contributors" |
||||
} |
||||
], |
||||
"description": "Symfony polyfill for the Mbstring extension", |
||||
"homepage": "https://symfony.com", |
||||
"keywords": [ |
||||
"compatibility", |
||||
"mbstring", |
||||
"polyfill", |
||||
"portable", |
||||
"shim" |
||||
], |
||||
"time": "2016-05-18 14:26:46" |
||||
}, |
||||
{ |
||||
"name": "symfony/routing", |
||||
"version": "v3.1.6", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/symfony/routing.git", |
||||
"reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/symfony/routing/zipball/8edf62498a1a4c57ba317664a4b698339c10cdf6", |
||||
"reference": "8edf62498a1a4c57ba317664a4b698339c10cdf6", |
||||
"shasum": "" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.9" |
||||
}, |
||||
"conflict": { |
||||
"symfony/config": "<2.8" |
||||
}, |
||||
"require-dev": { |
||||
"doctrine/annotations": "~1.0", |
||||
"doctrine/common": "~2.2", |
||||
"psr/log": "~1.0", |
||||
"symfony/config": "~2.8|~3.0", |
||||
"symfony/expression-language": "~2.8|~3.0", |
||||
"symfony/http-foundation": "~2.8|~3.0", |
||||
"symfony/yaml": "~2.8|~3.0" |
||||
}, |
||||
"suggest": { |
||||
"doctrine/annotations": "For using the annotation loader", |
||||
"symfony/config": "For using the all-in-one router or any loader", |
||||
"symfony/dependency-injection": "For loading routes from a service", |
||||
"symfony/expression-language": "For using expression matching", |
||||
"symfony/http-foundation": "For using a Symfony Request object", |
||||
"symfony/yaml": "For using the YAML loader" |
||||
}, |
||||
"type": "library", |
||||
"extra": { |
||||
"branch-alias": { |
||||
"dev-master": "3.1-dev" |
||||
} |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Symfony\\Component\\Routing\\": "" |
||||
}, |
||||
"exclude-from-classmap": [ |
||||
"/Tests/" |
||||
] |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Fabien Potencier", |
||||
"email": "fabien@symfony.com" |
||||
}, |
||||
{ |
||||
"name": "Symfony Community", |
||||
"homepage": "https://symfony.com/contributors" |
||||
} |
||||
], |
||||
"description": "Symfony Routing Component", |
||||
"homepage": "https://symfony.com", |
||||
"keywords": [ |
||||
"router", |
||||
"routing", |
||||
"uri", |
||||
"url" |
||||
], |
||||
"time": "2016-08-16 14:58:24" |
||||
} |
||||
], |
||||
"packages-dev": [], |
||||
"aliases": [], |
||||
"minimum-stability": "stable", |
||||
"stability-flags": [], |
||||
"prefer-stable": false, |
||||
"prefer-lowest": false, |
||||
"platform": [], |
||||
"platform-dev": [] |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<?php |
||||
|
||||
// autoload.php @generated by Composer |
||||
|
||||
require_once __DIR__ . '/composer' . '/autoload_real.php'; |
||||
|
||||
return ComposerAutoloaderInita3d708ca3bb0f3f4e817238e4fb57153::getLoader(); |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
phpunit.xml |
||||
reports |
||||
sandbox |
||||
vendor |
||||
composer.lock |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
language: php |
||||
|
||||
php: |
||||
- 5.3 |
||||
- 5.4 |
||||
- 5.5 |
||||
- 5.6 |
||||
- 7 |
||||
- hhvm |
||||
|
||||
before_script: |
||||
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "session.serialize_handler = php" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' |
||||
- composer install --dev --prefer-source |
@ -0,0 +1,134 @@
@@ -0,0 +1,134 @@
|
||||
CHANGELOG |
||||
========= |
||||
|
||||
###Legend |
||||
|
||||
* "BC": Backwards compatibility break (from public component APIs) |
||||
* "BF": Bug fix |
||||
|
||||
--- |
||||
|
||||
* 0.3.5 (2016-05-25) |
||||
|
||||
* BF: Unmask responding close frame |
||||
* Added write handler for PHP session serializer |
||||
|
||||
* 0.3.4 (2015-12-23) |
||||
|
||||
* BF: Edge case where version check wasn't run on message coalesce |
||||
* BF: Session didn't start when using pdo_sqlite |
||||
* BF: WAMP currie prefix check when using '#' |
||||
* Compatibility with Symfony 3 |
||||
|
||||
* 0.3.3 (2015-05-26) |
||||
|
||||
* BF: Framing bug on large messages upon TCP fragmentation |
||||
* BF: Symfony Router query parameter defaults applied to Request |
||||
* BF: WAMP CURIE on all URIs |
||||
* OriginCheck rules applied to FlashPolicy |
||||
* Switched from PSR-0 to PSR-4 |
||||
|
||||
* 0.3.2 (2014-06-08) |
||||
|
||||
* BF: No messages after closing handshake (fixed rare race condition causing 100% CPU) |
||||
* BF: Fixed accidental BC break from v0.3.1 |
||||
* Added autoDelete parameter to Topic to destroy when empty of connections |
||||
* Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App) |
||||
* Normalized Exceptions in WAMP |
||||
|
||||
* 0.3.1 (2014-05-26) |
||||
|
||||
* Added query parameter support to Router, set in HTTP request (ws://server?hello=world) |
||||
* HHVM compatibility |
||||
* BF: React/0.4 support; CPU starvation bug fixes |
||||
* BF: Allow App::route to ignore Host header |
||||
* Added expected filters to WAMP Topic broadcast method |
||||
* Resource cleanup in WAMP TopicManager |
||||
|
||||
* 0.3.0 (2013-10-14) |
||||
|
||||
* Added the `App` class to help making Ratchet so easy to use it's silly |
||||
* BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks |
||||
* Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router |
||||
* BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer |
||||
* BF: Single sub-protocol selection to conform with RFC6455 |
||||
* BF: Sanity checks on WAMP protocol to prevent errors |
||||
|
||||
* 0.2.8 (2013-09-19) |
||||
|
||||
* React 0.3 support |
||||
|
||||
* 0.2.7 (2013-06-09) |
||||
|
||||
* BF: Sub-protocol negotation with Guzzle 3.6 |
||||
|
||||
* 0.2.6 (2013-06-01) |
||||
|
||||
* Guzzle 3.6 support |
||||
|
||||
* 0.2.5 (2013-04-01) |
||||
|
||||
* Fixed Hixie-76 handshake bug |
||||
|
||||
* 0.2.4 (2013-03-09) |
||||
|
||||
* Support for Symfony 2.2 and Guzzle 2.3 |
||||
* Minor bug fixes when handling errors |
||||
|
||||
* 0.2.3 (2012-11-21) |
||||
|
||||
* Bumped dep: Guzzle to v3, React to v0.2.4 |
||||
* More tests |
||||
|
||||
* 0.2.2 (2012-10-20) |
||||
|
||||
* Bumped deps to use React v0.2 |
||||
|
||||
* 0.2.1 (2012-10-13) |
||||
|
||||
* BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string) |
||||
* Documentation corrections |
||||
* Using new composer structure |
||||
|
||||
* 0.2 (2012-09-07) |
||||
|
||||
* Ratchet passes every non-binary-frame test from the Autobahn Testsuite |
||||
* Major performance improvements |
||||
* BC: Renamed "WampServer" to "ServerProtocol" |
||||
* BC: New "WampServer" component passes Topic container objects of subscribed Connections |
||||
* Option to turn off UTF-8 checks in order to increase performance |
||||
* Switched dependency guzzle/guzzle to guzzle/http (no API changes) |
||||
* mbstring no longer required |
||||
|
||||
* 0.1.5 (2012-07-12) |
||||
|
||||
* BF: Error where service wouldn't run on PHP <= 5.3.8 |
||||
* Dependency library updates |
||||
|
||||
* 0.1.4 (2012-06-17) |
||||
|
||||
* Fixed dozens of failing AB tests |
||||
* BF: Proper socket buffer handling |
||||
|
||||
* 0.1.3 (2012-06-15) |
||||
|
||||
* Major refactor inside WebSocket protocol handling, more loosley coupled |
||||
* BF: Proper error handling on failed WebSocket connections |
||||
* BF: Handle TCP message concatenation |
||||
* Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance |
||||
* mb_string now a requirement |
||||
|
||||
* 0.1.2 (2012-05-19) |
||||
|
||||
* BC/BF: Updated WAMP API to coincide with the official spec |
||||
* Tweaks to improve running as a long lived process |
||||
|
||||
* 0.1.1 (2012-05-14) |
||||
|
||||
* Separated interfaces allowing WebSockets to support multiple sub protocols |
||||
* BF: remoteAddress variable on connections returns proper value |
||||
|
||||
* 0.1 (2012-05-11) |
||||
|
||||
* First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList |
||||
* I/O now handled by React, making Ratchet fully asynchronous |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011-2016 Chris Boden |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is furnished |
||||
to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
# This file is intended to ease the author's development and testing process
|
||||
# Users do not need to use `make`; Ratchet does not need to be compiled
|
||||
|
||||
test: |
||||
phpunit |
||||
|
||||
cover: |
||||
phpunit --coverage-text --coverage-html=reports/coverage |
||||
|
||||
abtests: |
||||
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent & |
||||
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect & |
||||
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 StreamSelect & |
||||
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & |
||||
wstest -m testeeserver -w ws://localhost:8000 & |
||||
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json |
||||
killall php wstest |
||||
|
||||
abtest: |
||||
ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect & |
||||
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json |
||||
killall php |
||||
|
||||
profile: |
||||
php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent & |
||||
wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json |
||||
killall php |
||||
|
||||
apidocs: |
||||
apigen --title Ratchet -d reports/api -s src/ \
|
||||
-s vendor/react \
|
||||
-s vendor/guzzle \
|
||||
-s vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session \
|
||||
-s vendor/symfony/routing/Symfony/Component/Routing \
|
||||
-s vendor/evenement/evenement/src/Evenement |
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
#Ratchet |
||||
|
||||
[](http://travis-ci.org/ratchetphp/Ratchet) |
||||
[](https://packagist.org/packages/cboden/ratchet) |
||||
|
||||
A PHP 5.3 library for asynchronously serving WebSockets. |
||||
Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. |
||||
|
||||
##WebSocket Compliance |
||||
|
||||
* Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time) |
||||
* Tested on Chrome 13+, Firefox 6+, Safari 5+, iOS 4.2+, IE 8+ |
||||
* Ratchet [passes](http://socketo.me/reports/ab/) the [Autobahn Testsuite](http://autobahn.ws/testsuite) (non-binary messages) |
||||
|
||||
##Requirements |
||||
|
||||
Shell access is required and root access is recommended. |
||||
To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access. |
||||
In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines. |
||||
You can find more details in the [server conf docs](http://socketo.me/docs/deploy#serverconfiguration). |
||||
|
||||
PHP 5.3.9 (or higher) is required. If you have access, PHP 5.4 (or higher) is *highly* recommended for its performance improvements. |
||||
|
||||
### Documentation |
||||
|
||||
User and API documentation is available on Ratchet's website: http://socketo.me |
||||
|
||||
See https://github.com/cboden/Ratchet-examples for some out-of-the-box working demos using Ratchet. |
||||
|
||||
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). |
||||
|
||||
--- |
||||
|
||||
###A quick example |
||||
|
||||
```php |
||||
<?php |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
// Make sure composer dependencies have been installed |
||||
require __DIR__ . '/vendor/autoload.php'; |
||||
|
||||
/** |
||||
* chat.php |
||||
* Send any incoming messages to all connected clients (except sender) |
||||
*/ |
||||
class MyChat implements MessageComponentInterface { |
||||
protected $clients; |
||||
|
||||
public function __construct() { |
||||
$this->clients = new \SplObjectStorage; |
||||
} |
||||
|
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$this->clients->attach($conn); |
||||
} |
||||
|
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
foreach ($this->clients as $client) { |
||||
if ($from != $client) { |
||||
$client->send($msg); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public function onClose(ConnectionInterface $conn) { |
||||
$this->clients->detach($conn); |
||||
} |
||||
|
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$conn->close(); |
||||
} |
||||
} |
||||
|
||||
// Run the server application through the WebSocket protocol on port 8080 |
||||
$app = new Ratchet\App('localhost', 8080); |
||||
$app->route('/chat', new MyChat); |
||||
$app->route('/echo', new Ratchet\Server\EchoServer, array('*')); |
||||
$app->run(); |
||||
``` |
||||
|
||||
$ php chat.php |
||||
|
||||
```javascript |
||||
// Then some JavaScript in the browser: |
||||
var conn = new WebSocket('ws://localhost:8080/echo'); |
||||
conn.onmessage = function(e) { console.log(e.data); }; |
||||
conn.send('Hello Me!'); |
||||
``` |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
{ |
||||
"name": "cboden/ratchet" |
||||
, "type": "library" |
||||
, "description": "PHP WebSocket library" |
||||
, "keywords": ["WebSockets", "Server", "Ratchet", "Sockets"] |
||||
, "homepage": "http://socketo.me" |
||||
, "license": "MIT" |
||||
, "authors": [ |
||||
{ |
||||
"name": "Chris Boden" |
||||
, "email": "cboden@gmail.com" |
||||
, "role": "Developer" |
||||
} |
||||
] |
||||
, "support": { |
||||
"forum": "https://groups.google.com/forum/#!forum/ratchet-php" |
||||
, "issues": "https://github.com/ratchetphp/Ratchet/issues" |
||||
, "irc": "irc://irc.freenode.org/reactphp" |
||||
} |
||||
, "autoload": { |
||||
"psr-4": { |
||||
"Ratchet\\": "src/Ratchet" |
||||
} |
||||
} |
||||
, "require": { |
||||
"php": ">=5.3.9" |
||||
, "react/socket": "^0.3 || ^0.4" |
||||
, "guzzle/http": "^3.6" |
||||
, "symfony/http-foundation": "^2.2|^3.0" |
||||
, "symfony/routing": "^2.2|^3.0" |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<phpunit |
||||
forceCoversAnnotation="true" |
||||
mapTestClassNameToCoveredClassName="true" |
||||
bootstrap="tests/bootstrap.php" |
||||
colors="true" |
||||
backupGlobals="false" |
||||
backupStaticAttributes="false" |
||||
syntaxCheck="false" |
||||
stopOnError="false" |
||||
> |
||||
|
||||
<testsuites> |
||||
<testsuite name="unit"> |
||||
<directory>./tests/unit/</directory> |
||||
</testsuite> |
||||
</testsuites> |
||||
|
||||
<testsuites> |
||||
<testsuite name="integration"> |
||||
<directory>./tests/integration/</directory> |
||||
</testsuite> |
||||
</testsuites> |
||||
|
||||
<filter> |
||||
<whitelist> |
||||
<directory>./src/</directory> |
||||
</whitelist> |
||||
</filter> |
||||
</phpunit> |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
/** |
||||
* Wraps ConnectionInterface objects via the decorator pattern but allows |
||||
* parameters to bubble through with magic methods |
||||
* @todo It sure would be nice if I could make most of this a trait... |
||||
*/ |
||||
abstract class AbstractConnectionDecorator implements ConnectionInterface { |
||||
/** |
||||
* @var ConnectionInterface |
||||
*/ |
||||
protected $wrappedConn; |
||||
|
||||
public function __construct(ConnectionInterface $conn) { |
||||
$this->wrappedConn = $conn; |
||||
} |
||||
|
||||
/** |
||||
* @return ConnectionInterface |
||||
*/ |
||||
protected function getConnection() { |
||||
return $this->wrappedConn; |
||||
} |
||||
|
||||
public function __set($name, $value) { |
||||
$this->wrappedConn->$name = $value; |
||||
} |
||||
|
||||
public function __get($name) { |
||||
return $this->wrappedConn->$name; |
||||
} |
||||
|
||||
public function __isset($name) { |
||||
return isset($this->wrappedConn->$name); |
||||
} |
||||
|
||||
public function __unset($name) { |
||||
unset($this->wrappedConn->$name); |
||||
} |
||||
} |
@ -0,0 +1,146 @@
@@ -0,0 +1,146 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
use React\EventLoop\LoopInterface; |
||||
use React\EventLoop\Factory as LoopFactory; |
||||
use React\Socket\Server as Reactor; |
||||
use Ratchet\Http\HttpServerInterface; |
||||
use Ratchet\Http\OriginCheck; |
||||
use Ratchet\Wamp\WampServerInterface; |
||||
use Ratchet\Server\IoServer; |
||||
use Ratchet\Server\FlashPolicy; |
||||
use Ratchet\Http\HttpServer; |
||||
use Ratchet\Http\Router; |
||||
use Ratchet\WebSocket\WsServer; |
||||
use Ratchet\Wamp\WampServer; |
||||
use Symfony\Component\Routing\RouteCollection; |
||||
use Symfony\Component\Routing\Route; |
||||
use Symfony\Component\Routing\RequestContext; |
||||
use Symfony\Component\Routing\Matcher\UrlMatcher; |
||||
|
||||
/** |
||||
* An opinionated facade class to quickly and easily create a WebSocket server. |
||||
* A few configuration assumptions are made and some best-practice security conventions are applied by default. |
||||
*/ |
||||
class App { |
||||
/** |
||||
* @var \Symfony\Component\Routing\RouteCollection |
||||
*/ |
||||
public $routes; |
||||
|
||||
/** |
||||
* @var \Ratchet\Server\IoServer |
||||
*/ |
||||
public $flashServer; |
||||
|
||||
/** |
||||
* @var \Ratchet\Server\IoServer |
||||
*/ |
||||
protected $_server; |
||||
|
||||
/** |
||||
* The Host passed in construct used for same origin policy |
||||
* @var string |
||||
*/ |
||||
protected $httpHost; |
||||
|
||||
/*** |
||||
* The port the socket is listening |
||||
* @var int |
||||
*/ |
||||
protected $port; |
||||
|
||||
/** |
||||
* @var int |
||||
*/ |
||||
protected $_routeCounter = 0; |
||||
|
||||
/** |
||||
* @param string $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');` |
||||
* @param int $port Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 |
||||
* @param string $address IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. |
||||
* @param LoopInterface $loop Specific React\EventLoop to bind the application to. null will create one for you. |
||||
*/ |
||||
public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null) { |
||||
if (extension_loaded('xdebug')) { |
||||
trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING); |
||||
} |
||||
|
||||
if (3 !== strlen('✓')) { |
||||
throw new \DomainException('Bad encoding, length of unicode character ✓ should be 3. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); |
||||
} |
||||
|
||||
if (null === $loop) { |
||||
$loop = LoopFactory::create(); |
||||
} |
||||
|
||||
$this->httpHost = $httpHost; |
||||
$this->port = $port; |
||||
|
||||
$socket = new Reactor($loop); |
||||
$socket->listen($port, $address); |
||||
|
||||
$this->routes = new RouteCollection; |
||||
$this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); |
||||
|
||||
$policy = new FlashPolicy; |
||||
$policy->addAllowedAccess($httpHost, 80); |
||||
$policy->addAllowedAccess($httpHost, $port); |
||||
$flashSock = new Reactor($loop); |
||||
$this->flashServer = new IoServer($policy, $flashSock); |
||||
if (80 == $port) { |
||||
$flashSock->listen(843, '0.0.0.0'); |
||||
} else { |
||||
$flashSock->listen(8843); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Add an endpoint/application to the server |
||||
* @param string $path The URI the client will connect to |
||||
* @param ComponentInterface $controller Your application to server for the route. If not specified, assumed to be for a WebSocket |
||||
* @param array $allowedOrigins An array of hosts allowed to connect (same host by default), ['*'] for any |
||||
* @param string $httpHost Override the $httpHost variable provided in the __construct |
||||
* @return ComponentInterface|WsServer |
||||
*/ |
||||
public function route($path, ComponentInterface $controller, array $allowedOrigins = array(), $httpHost = null) { |
||||
if ($controller instanceof HttpServerInterface || $controller instanceof WsServer) { |
||||
$decorated = $controller; |
||||
} elseif ($controller instanceof WampServerInterface) { |
||||
$decorated = new WsServer(new WampServer($controller)); |
||||
} elseif ($controller instanceof MessageComponentInterface) { |
||||
$decorated = new WsServer($controller); |
||||
} else { |
||||
$decorated = $controller; |
||||
} |
||||
|
||||
if ($httpHost === null) { |
||||
$httpHost = $this->httpHost; |
||||
} |
||||
|
||||
$allowedOrigins = array_values($allowedOrigins); |
||||
if (0 === count($allowedOrigins)) { |
||||
$allowedOrigins[] = $httpHost; |
||||
} |
||||
if ('*' !== $allowedOrigins[0]) { |
||||
$decorated = new OriginCheck($decorated, $allowedOrigins); |
||||
} |
||||
|
||||
//allow origins in flash policy server |
||||
if(empty($this->flashServer) === false) { |
||||
foreach($allowedOrigins as $allowedOrgin) { |
||||
$this->flashServer->app->addAllowedAccess($allowedOrgin, $this->port); |
||||
} |
||||
} |
||||
|
||||
$this->routes->add('rr-' . ++$this->_routeCounter, new Route($path, array('_controller' => $decorated), array('Origin' => $this->httpHost), array(), $httpHost)); |
||||
|
||||
return $decorated; |
||||
} |
||||
|
||||
/** |
||||
* Run the server by entering the event loop |
||||
*/ |
||||
public function run() { |
||||
$this->_server->run(); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
/** |
||||
* This is the interface to build a Ratchet application with. |
||||
* It implements the decorator pattern to build an application stack |
||||
*/ |
||||
interface ComponentInterface { |
||||
/** |
||||
* When a new connection is opened it will be passed to this method |
||||
* @param ConnectionInterface $conn The socket/connection that just connected to your application |
||||
* @throws \Exception |
||||
*/ |
||||
function onOpen(ConnectionInterface $conn); |
||||
|
||||
/** |
||||
* This is called before or after a socket is closed (depends on how it's closed). SendMessage to $conn will not result in an error if it has already been closed. |
||||
* @param ConnectionInterface $conn The socket/connection that is closing/closed |
||||
* @throws \Exception |
||||
*/ |
||||
function onClose(ConnectionInterface $conn); |
||||
|
||||
/** |
||||
* If there is an error with one of the sockets, or somewhere in the application where an Exception is thrown, |
||||
* the Exception is sent back down the stack, handled by the Server and bubbled back up the application through this method |
||||
* @param ConnectionInterface $conn |
||||
* @param \Exception $e |
||||
* @throws \Exception |
||||
*/ |
||||
function onError(ConnectionInterface $conn, \Exception $e); |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
/** |
||||
* The version of Ratchet being used |
||||
* @var string |
||||
*/ |
||||
const VERSION = 'Ratchet/0.3.5'; |
||||
|
||||
/** |
||||
* A proxy object representing a connection to the application |
||||
* This acts as a container to store data (in memory) about the connection |
||||
*/ |
||||
interface ConnectionInterface { |
||||
/** |
||||
* Send data to the connection |
||||
* @param string $data |
||||
* @return \Ratchet\ConnectionInterface |
||||
*/ |
||||
function send($data); |
||||
|
||||
/** |
||||
* Close the connection |
||||
*/ |
||||
function close(); |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
<?php |
||||
namespace Ratchet\Http\Guzzle\Http\Message; |
||||
use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory; |
||||
use Guzzle\Http\EntityBody; |
||||
|
||||
class RequestFactory extends GuzzleRequestFactory { |
||||
|
||||
protected static $ratchetInstance; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public static function getInstance() |
||||
{ |
||||
// @codeCoverageIgnoreStart |
||||
if (!static::$ratchetInstance) { |
||||
static::$ratchetInstance = new static(); |
||||
} |
||||
// @codeCoverageIgnoreEnd |
||||
|
||||
return static::$ratchetInstance; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function create($method, $url, $headers = null, $body = '', array $options = array()) { |
||||
$c = $this->entityEnclosingRequestClass; |
||||
$request = new $c($method, $url, $headers); |
||||
$request->setBody(EntityBody::factory($body)); |
||||
|
||||
return $request; |
||||
} |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\MessageInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\Http\Guzzle\Http\Message\RequestFactory; |
||||
|
||||
/** |
||||
* This class receives streaming data from a client request |
||||
* and parses HTTP headers, returning a Guzzle Request object |
||||
* once it's been buffered |
||||
*/ |
||||
class HttpRequestParser implements MessageInterface { |
||||
const EOM = "\r\n\r\n"; |
||||
|
||||
/** |
||||
* The maximum number of bytes the request can be |
||||
* This is a security measure to prevent attacks |
||||
* @var int |
||||
*/ |
||||
public $maxSize = 4096; |
||||
|
||||
/** |
||||
* @param \Ratchet\ConnectionInterface $context |
||||
* @param string $data Data stream to buffer |
||||
* @return \Guzzle\Http\Message\RequestInterface|null |
||||
* @throws \OverflowException If the message buffer has become too large |
||||
*/ |
||||
public function onMessage(ConnectionInterface $context, $data) { |
||||
if (!isset($context->httpBuffer)) { |
||||
$context->httpBuffer = ''; |
||||
} |
||||
|
||||
$context->httpBuffer .= $data; |
||||
|
||||
if (strlen($context->httpBuffer) > (int)$this->maxSize) { |
||||
throw new \OverflowException("Maximum buffer size of {$this->maxSize} exceeded parsing HTTP header"); |
||||
} |
||||
|
||||
if ($this->isEom($context->httpBuffer)) { |
||||
$request = RequestFactory::getInstance()->fromMessage($context->httpBuffer); |
||||
|
||||
unset($context->httpBuffer); |
||||
|
||||
return $request; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Determine if the message has been buffered as per the HTTP specification |
||||
* @param string $message |
||||
* @return boolean |
||||
*/ |
||||
public function isEom($message) { |
||||
return (boolean)strpos($message, static::EOM); |
||||
} |
||||
} |
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
|
||||
class HttpServer implements MessageComponentInterface { |
||||
/** |
||||
* Buffers incoming HTTP requests returning a Guzzle Request when coalesced |
||||
* @var HttpRequestParser |
||||
* @note May not expose this in the future, may do through facade methods |
||||
*/ |
||||
protected $_reqParser; |
||||
|
||||
/** |
||||
* @var \Ratchet\Http\HttpServerInterface |
||||
*/ |
||||
protected $_httpServer; |
||||
|
||||
/** |
||||
* @param HttpServerInterface |
||||
*/ |
||||
public function __construct(HttpServerInterface $component) { |
||||
$this->_httpServer = $component; |
||||
$this->_reqParser = new HttpRequestParser; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$conn->httpHeadersReceived = false; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
if (true !== $from->httpHeadersReceived) { |
||||
try { |
||||
if (null === ($request = $this->_reqParser->onMessage($from, $msg))) { |
||||
return; |
||||
} |
||||
} catch (\OverflowException $oe) { |
||||
return $this->close($from, 413); |
||||
} |
||||
|
||||
$from->httpHeadersReceived = true; |
||||
|
||||
return $this->_httpServer->onOpen($from, $request); |
||||
} |
||||
|
||||
$this->_httpServer->onMessage($from, $msg); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
if ($conn->httpHeadersReceived) { |
||||
$this->_httpServer->onClose($conn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
if ($conn->httpHeadersReceived) { |
||||
$this->_httpServer->onError($conn, $e); |
||||
} else { |
||||
$this->close($conn, 500); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Close a connection with an HTTP response |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param int $code HTTP status code |
||||
* @return null |
||||
*/ |
||||
protected function close(ConnectionInterface $conn, $code = 400) { |
||||
$response = new Response($code, array( |
||||
'X-Powered-By' => \Ratchet\VERSION |
||||
)); |
||||
|
||||
$conn->send((string)$response); |
||||
$conn->close(); |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
|
||||
interface HttpServerInterface extends MessageComponentInterface { |
||||
/** |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param \Guzzle\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! |
||||
* @throws \UnexpectedValueException if a RequestInterface is not passed |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null); |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
|
||||
/** |
||||
* A middleware to ensure JavaScript clients connecting are from the expected domain. |
||||
* This protects other websites from open WebSocket connections to your application. |
||||
* Note: This can be spoofed from non-web browser clients |
||||
*/ |
||||
class OriginCheck implements HttpServerInterface { |
||||
/** |
||||
* @var \Ratchet\MessageComponentInterface |
||||
*/ |
||||
protected $_component; |
||||
|
||||
public $allowedOrigins = array(); |
||||
|
||||
/** |
||||
* @param MessageComponentInterface $component Component/Application to decorate |
||||
* @param array $allowed An array of allowed domains that are allowed to connect from |
||||
*/ |
||||
public function __construct(MessageComponentInterface $component, array $allowed = array()) { |
||||
$this->_component = $component; |
||||
$this->allowedOrigins += $allowed; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { |
||||
$header = (string)$request->getHeader('Origin'); |
||||
$origin = parse_url($header, PHP_URL_HOST) ?: $header; |
||||
|
||||
if (!in_array($origin, $this->allowedOrigins)) { |
||||
return $this->close($conn, 403); |
||||
} |
||||
|
||||
return $this->_component->onOpen($conn, $request); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onMessage(ConnectionInterface $from, $msg) { |
||||
return $this->_component->onMessage($from, $msg); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onClose(ConnectionInterface $conn) { |
||||
return $this->_component->onClose($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onError(ConnectionInterface $conn, \Exception $e) { |
||||
return $this->_component->onError($conn, $e); |
||||
} |
||||
|
||||
/** |
||||
* Close a connection with an HTTP response |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param int $code HTTP status code |
||||
* @return null |
||||
*/ |
||||
protected function close(ConnectionInterface $conn, $code = 400) { |
||||
$response = new Response($code, array( |
||||
'X-Powered-By' => \Ratchet\VERSION |
||||
)); |
||||
|
||||
$conn->send((string)$response); |
||||
$conn->close(); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\ConnectionInterface; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
use Guzzle\Http\Url; |
||||
use Symfony\Component\Routing\Matcher\UrlMatcherInterface; |
||||
use Symfony\Component\Routing\Exception\MethodNotAllowedException; |
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
||||
|
||||
class Router implements HttpServerInterface { |
||||
/** |
||||
* @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface |
||||
*/ |
||||
protected $_matcher; |
||||
|
||||
public function __construct(UrlMatcherInterface $matcher) { |
||||
$this->_matcher = $matcher; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { |
||||
if (null === $request) { |
||||
throw new \UnexpectedValueException('$request can not be null'); |
||||
} |
||||
|
||||
$context = $this->_matcher->getContext(); |
||||
$context->setMethod($request->getMethod()); |
||||
$context->setHost($request->getHost()); |
||||
|
||||
try { |
||||
$route = $this->_matcher->match($request->getPath()); |
||||
} catch (MethodNotAllowedException $nae) { |
||||
return $this->close($conn, 403); |
||||
} catch (ResourceNotFoundException $nfe) { |
||||
return $this->close($conn, 404); |
||||
} |
||||
|
||||
if (is_string($route['_controller']) && class_exists($route['_controller'])) { |
||||
$route['_controller'] = new $route['_controller']; |
||||
} |
||||
|
||||
if (!($route['_controller'] instanceof HttpServerInterface)) { |
||||
throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); |
||||
} |
||||
|
||||
$parameters = array(); |
||||
foreach($route as $key => $value) { |
||||
if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { |
||||
$parameters[$key] = $value; |
||||
} |
||||
} |
||||
$parameters = array_merge($parameters, $request->getQuery()->getAll()); |
||||
|
||||
$url = Url::factory($request->getPath()); |
||||
$url->setQuery($parameters); |
||||
$request->setUrl($url); |
||||
|
||||
$conn->controller = $route['_controller']; |
||||
$conn->controller->onOpen($conn, $request); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onMessage(ConnectionInterface $from, $msg) { |
||||
$from->controller->onMessage($from, $msg); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onClose(ConnectionInterface $conn) { |
||||
if (isset($conn->controller)) { |
||||
$conn->controller->onClose($conn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onError(ConnectionInterface $conn, \Exception $e) { |
||||
if (isset($conn->controller)) { |
||||
$conn->controller->onError($conn, $e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Close a connection with an HTTP response |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param int $code HTTP status code |
||||
* @return null |
||||
*/ |
||||
protected function close(ConnectionInterface $conn, $code = 400) { |
||||
$response = new Response($code, array( |
||||
'X-Powered-By' => \Ratchet\VERSION |
||||
)); |
||||
|
||||
$conn->send((string)$response); |
||||
$conn->close(); |
||||
} |
||||
} |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
interface MessageComponentInterface extends ComponentInterface, MessageInterface { |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
interface MessageInterface { |
||||
/** |
||||
* Triggered when a client sends data through the socket |
||||
* @param \Ratchet\ConnectionInterface $from The socket/connection that sent the message to your application |
||||
* @param string $msg The message received |
||||
* @throws \Exception |
||||
*/ |
||||
function onMessage(ConnectionInterface $from, $msg); |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* A simple Ratchet application that will reply to all messages with the message it received |
||||
*/ |
||||
class EchoServer implements MessageComponentInterface { |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
} |
||||
|
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
$from->send($msg); |
||||
} |
||||
|
||||
public function onClose(ConnectionInterface $conn) { |
||||
} |
||||
|
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$conn->close(); |
||||
} |
||||
} |
@ -0,0 +1,200 @@
@@ -0,0 +1,200 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* An app to go on a server stack to pass a policy file to a Flash socket |
||||
* Useful if you're using Flash as a WebSocket polyfill on IE |
||||
* Be sure to run your server instance on port 843 |
||||
* By default this lets accepts everything, make sure you tighten the rules up for production |
||||
* @final |
||||
* @link http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html |
||||
* @link http://learn.adobe.com/wiki/download/attachments/64389123/CrossDomain_PolicyFile_Specification.pdf?version=1 |
||||
* @link view-source:http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd |
||||
*/ |
||||
class FlashPolicy implements MessageComponentInterface { |
||||
|
||||
/** |
||||
* Contains the root policy node |
||||
* @var string |
||||
*/ |
||||
protected $_policy = '<?xml version="1.0"?><!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"><cross-domain-policy></cross-domain-policy>'; |
||||
|
||||
/** |
||||
* Stores an array of allowed domains and their ports |
||||
* @var array |
||||
*/ |
||||
protected $_access = array(); |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $_siteControl = ''; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $_cache = ''; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $_cacheValid = false; |
||||
|
||||
/** |
||||
* Add a domain to an allowed access list. |
||||
* |
||||
* @param string $domain Specifies a requesting domain to be granted access. Both named domains and IP |
||||
* addresses are acceptable values. Subdomains are considered different domains. A wildcard (*) can |
||||
* be used to match all domains when used alone, or multiple domains (subdomains) when used as a |
||||
* prefix for an explicit, second-level domain name separated with a dot (.) |
||||
* @param string $ports A comma-separated list of ports or range of ports that a socket connection |
||||
* is allowed to connect to. A range of ports is specified through a dash (-) between two port numbers. |
||||
* Ranges can be used with individual ports when separated with a comma. A single wildcard (*) can |
||||
* be used to allow all ports. |
||||
* @param bool $secure |
||||
* @throws \UnexpectedValueException |
||||
* @return FlashPolicy |
||||
*/ |
||||
public function addAllowedAccess($domain, $ports = '*', $secure = false) { |
||||
if (!$this->validateDomain($domain)) { |
||||
throw new \UnexpectedValueException('Invalid domain'); |
||||
} |
||||
|
||||
if (!$this->validatePorts($ports)) { |
||||
throw new \UnexpectedValueException('Invalid Port'); |
||||
} |
||||
|
||||
$this->_access[] = array($domain, $ports, (boolean)$secure); |
||||
$this->_cacheValid = false; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Removes all domains from the allowed access list. |
||||
* |
||||
* @return \Ratchet\Server\FlashPolicy |
||||
*/ |
||||
public function clearAllowedAccess() { |
||||
$this->_access = array(); |
||||
$this->_cacheValid = false; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* site-control defines the meta-policy for the current domain. A meta-policy specifies acceptable |
||||
* domain policy files other than the master policy file located in the target domain's root and named |
||||
* crossdomain.xml. |
||||
* |
||||
* @param string $permittedCrossDomainPolicies |
||||
* @throws \UnexpectedValueException |
||||
* @return FlashPolicy |
||||
*/ |
||||
public function setSiteControl($permittedCrossDomainPolicies = 'all') { |
||||
if (!$this->validateSiteControl($permittedCrossDomainPolicies)) { |
||||
throw new \UnexpectedValueException('Invalid site control set'); |
||||
} |
||||
|
||||
$this->_siteControl = $permittedCrossDomainPolicies; |
||||
$this->_cacheValid = false; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
if (!$this->_cacheValid) { |
||||
$this->_cache = $this->renderPolicy()->asXML(); |
||||
$this->_cacheValid = true; |
||||
} |
||||
|
||||
$from->send($this->_cache . "\0"); |
||||
$from->close(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$conn->close(); |
||||
} |
||||
|
||||
/** |
||||
* Builds the crossdomain file based on the template policy |
||||
* |
||||
* @throws \UnexpectedValueException |
||||
* @return \SimpleXMLElement |
||||
*/ |
||||
public function renderPolicy() { |
||||
$policy = new \SimpleXMLElement($this->_policy); |
||||
|
||||
$siteControl = $policy->addChild('site-control'); |
||||
|
||||
if ($this->_siteControl == '') { |
||||
$this->setSiteControl(); |
||||
} |
||||
|
||||
$siteControl->addAttribute('permitted-cross-domain-policies', $this->_siteControl); |
||||
|
||||
if (empty($this->_access)) { |
||||
throw new \UnexpectedValueException('You must add a domain through addAllowedAccess()'); |
||||
} |
||||
|
||||
foreach ($this->_access as $access) { |
||||
$tmp = $policy->addChild('allow-access-from'); |
||||
$tmp->addAttribute('domain', $access[0]); |
||||
$tmp->addAttribute('to-ports', $access[1]); |
||||
$tmp->addAttribute('secure', ($access[2] === true) ? 'true' : 'false'); |
||||
} |
||||
|
||||
return $policy; |
||||
} |
||||
|
||||
/** |
||||
* Make sure the proper site control was passed |
||||
* |
||||
* @param string $permittedCrossDomainPolicies |
||||
* @return bool |
||||
*/ |
||||
public function validateSiteControl($permittedCrossDomainPolicies) { |
||||
//'by-content-type' and 'by-ftp-filename' are not available for sockets |
||||
return (bool)in_array($permittedCrossDomainPolicies, array('none', 'master-only', 'all')); |
||||
} |
||||
|
||||
/** |
||||
* Validate for proper domains (wildcards allowed) |
||||
* |
||||
* @param string $domain |
||||
* @return bool |
||||
*/ |
||||
public function validateDomain($domain) { |
||||
return (bool)preg_match("/^((http(s)?:\/\/)?([a-z0-9-_]+\.|\*\.)*([a-z0-9-_\.]+)|\*)$/i", $domain); |
||||
} |
||||
|
||||
/** |
||||
* Make sure valid ports were passed |
||||
* |
||||
* @param string $port |
||||
* @return bool |
||||
*/ |
||||
public function validatePorts($port) { |
||||
return (bool)preg_match('/^(\*|(\d+[,-]?)*\d+)$/', $port); |
||||
} |
||||
} |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\ConnectionInterface; |
||||
use React\Socket\ConnectionInterface as ReactConn; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
class IoConnection implements ConnectionInterface { |
||||
/** |
||||
* @var \React\Socket\ConnectionInterface |
||||
*/ |
||||
protected $conn; |
||||
|
||||
|
||||
/** |
||||
* @param \React\Socket\ConnectionInterface $conn |
||||
*/ |
||||
public function __construct(ReactConn $conn) { |
||||
$this->conn = $conn; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function send($data) { |
||||
$this->conn->write($data); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function close() { |
||||
$this->conn->end(); |
||||
} |
||||
} |
@ -0,0 +1,141 @@
@@ -0,0 +1,141 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\MessageComponentInterface; |
||||
use React\EventLoop\LoopInterface; |
||||
use React\Socket\ServerInterface; |
||||
use React\EventLoop\Factory as LoopFactory; |
||||
use React\Socket\Server as Reactor; |
||||
|
||||
/** |
||||
* Creates an open-ended socket to listen on a port for incoming connections. |
||||
* Events are delegated through this to attached applications |
||||
*/ |
||||
class IoServer { |
||||
/** |
||||
* @var \React\EventLoop\LoopInterface |
||||
*/ |
||||
public $loop; |
||||
|
||||
/** |
||||
* @var \Ratchet\MessageComponentInterface |
||||
*/ |
||||
public $app; |
||||
|
||||
/** |
||||
* Array of React event handlers |
||||
* @var \SplFixedArray |
||||
*/ |
||||
protected $handlers; |
||||
|
||||
/** |
||||
* The socket server the Ratchet Application is run off of |
||||
* @var \React\Socket\ServerInterface |
||||
*/ |
||||
public $socket; |
||||
|
||||
/** |
||||
* @param \Ratchet\MessageComponentInterface $app The Ratchet application stack to host |
||||
* @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of |
||||
* @param \React\EventLoop\LoopInterface|null $loop The React looper to run the Ratchet application off of |
||||
*/ |
||||
public function __construct(MessageComponentInterface $app, ServerInterface $socket, LoopInterface $loop = null) { |
||||
if (false === strpos(PHP_VERSION, "hiphop")) { |
||||
gc_enable(); |
||||
} |
||||
|
||||
set_time_limit(0); |
||||
ob_implicit_flush(); |
||||
|
||||
$this->loop = $loop; |
||||
$this->app = $app; |
||||
$this->socket = $socket; |
||||
|
||||
$socket->on('connection', array($this, 'handleConnect')); |
||||
|
||||
$this->handlers = new \SplFixedArray(3); |
||||
$this->handlers[0] = array($this, 'handleData'); |
||||
$this->handlers[1] = array($this, 'handleEnd'); |
||||
$this->handlers[2] = array($this, 'handleError'); |
||||
} |
||||
|
||||
/** |
||||
* @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received |
||||
* @param int $port The port to server sockets on |
||||
* @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) |
||||
* @return IoServer |
||||
*/ |
||||
public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { |
||||
$loop = LoopFactory::create(); |
||||
$socket = new Reactor($loop); |
||||
$socket->listen($port, $address); |
||||
|
||||
return new static($component, $socket, $loop); |
||||
} |
||||
|
||||
/** |
||||
* Run the application by entering the event loop |
||||
* @throws \RuntimeException If a loop was not previously specified |
||||
*/ |
||||
public function run() { |
||||
if (null === $this->loop) { |
||||
throw new \RuntimeException("A React Loop was not provided during instantiation"); |
||||
} |
||||
|
||||
// @codeCoverageIgnoreStart |
||||
$this->loop->run(); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
|
||||
/** |
||||
* Triggered when a new connection is received from React |
||||
* @param \React\Socket\ConnectionInterface $conn |
||||
*/ |
||||
public function handleConnect($conn) { |
||||
$conn->decor = new IoConnection($conn); |
||||
|
||||
$conn->decor->resourceId = (int)$conn->stream; |
||||
$conn->decor->remoteAddress = $conn->getRemoteAddress(); |
||||
|
||||
$this->app->onOpen($conn->decor); |
||||
|
||||
$conn->on('data', $this->handlers[0]); |
||||
$conn->on('end', $this->handlers[1]); |
||||
$conn->on('error', $this->handlers[2]); |
||||
} |
||||
|
||||
/** |
||||
* Data has been received from React |
||||
* @param string $data |
||||
* @param \React\Socket\ConnectionInterface $conn |
||||
*/ |
||||
public function handleData($data, $conn) { |
||||
try { |
||||
$this->app->onMessage($conn->decor, $data); |
||||
} catch (\Exception $e) { |
||||
$this->handleError($e, $conn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* A connection has been closed by React |
||||
* @param \React\Socket\ConnectionInterface $conn |
||||
*/ |
||||
public function handleEnd($conn) { |
||||
try { |
||||
$this->app->onClose($conn->decor); |
||||
} catch (\Exception $e) { |
||||
$this->handleError($e, $conn); |
||||
} |
||||
|
||||
unset($conn->decor); |
||||
} |
||||
|
||||
/** |
||||
* An error has occurred, let the listening application know |
||||
* @param \Exception $e |
||||
* @param \React\Socket\ConnectionInterface $conn |
||||
*/ |
||||
public function handleError(\Exception $e, $conn) { |
||||
$this->app->onError($conn->decor, $e); |
||||
} |
||||
} |
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
class IpBlackList implements MessageComponentInterface { |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $_blacklist = array(); |
||||
|
||||
/** |
||||
* @var \Ratchet\MessageComponentInterface |
||||
*/ |
||||
protected $_decorating; |
||||
|
||||
/** |
||||
* @param \Ratchet\MessageComponentInterface $component |
||||
*/ |
||||
public function __construct(MessageComponentInterface $component) { |
||||
$this->_decorating = $component; |
||||
} |
||||
|
||||
/** |
||||
* Add an address to the blacklist that will not be allowed to connect to your application |
||||
* @param string $ip IP address to block from connecting to your application |
||||
* @return IpBlackList |
||||
*/ |
||||
public function blockAddress($ip) { |
||||
$this->_blacklist[$ip] = true; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Unblock an address so they can access your application again |
||||
* @param string $ip IP address to unblock from connecting to your application |
||||
* @return IpBlackList |
||||
*/ |
||||
public function unblockAddress($ip) { |
||||
if (isset($this->_blacklist[$this->filterAddress($ip)])) { |
||||
unset($this->_blacklist[$this->filterAddress($ip)]); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param string $address |
||||
* @return bool |
||||
*/ |
||||
public function isBlocked($address) { |
||||
return (isset($this->_blacklist[$this->filterAddress($address)])); |
||||
} |
||||
|
||||
/** |
||||
* Get an array of all the addresses blocked |
||||
* @return array |
||||
*/ |
||||
public function getBlockedAddresses() { |
||||
return array_keys($this->_blacklist); |
||||
} |
||||
|
||||
/** |
||||
* @param string $address |
||||
* @return string |
||||
*/ |
||||
public function filterAddress($address) { |
||||
if (strstr($address, ':') && substr_count($address, '.') == 3) { |
||||
list($address, $port) = explode(':', $address); |
||||
} |
||||
|
||||
return $address; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onOpen(ConnectionInterface $conn) { |
||||
if ($this->isBlocked($conn->remoteAddress)) { |
||||
return $conn->close(); |
||||
} |
||||
|
||||
return $this->_decorating->onOpen($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onMessage(ConnectionInterface $from, $msg) { |
||||
return $this->_decorating->onMessage($from, $msg); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onClose(ConnectionInterface $conn) { |
||||
if (!$this->isBlocked($conn->remoteAddress)) { |
||||
$this->_decorating->onClose($conn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onError(ConnectionInterface $conn, \Exception $e) { |
||||
if (!$this->isBlocked($conn->remoteAddress)) { |
||||
$this->_decorating->onError($conn, $e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Serialize; |
||||
|
||||
interface HandlerInterface { |
||||
/** |
||||
* @param array |
||||
* @return string |
||||
*/ |
||||
function serialize(array $data); |
||||
|
||||
/** |
||||
* @param string |
||||
* @return array |
||||
*/ |
||||
function unserialize($raw); |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Serialize; |
||||
|
||||
class PhpBinaryHandler implements HandlerInterface { |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function serialize(array $data) { |
||||
throw new \RuntimeException("Serialize PhpHandler:serialize code not written yet, write me!"); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net |
||||
*/ |
||||
public function unserialize($raw) { |
||||
$returnData = array(); |
||||
$offset = 0; |
||||
|
||||
while ($offset < strlen($raw)) { |
||||
$num = ord($raw[$offset]); |
||||
$offset += 1; |
||||
$varname = substr($raw, $offset, $num); |
||||
$offset += $num; |
||||
$data = unserialize(substr($raw, $offset)); |
||||
|
||||
$returnData[$varname] = $data; |
||||
$offset += strlen(serialize($data)); |
||||
} |
||||
|
||||
return $returnData; |
||||
} |
||||
} |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Serialize; |
||||
|
||||
class PhpHandler implements HandlerInterface { |
||||
/** |
||||
* Simply reverse behaviour of unserialize method. |
||||
* {@inheritdoc} |
||||
*/ |
||||
function serialize(array $data) { |
||||
$preSerialized = array(); |
||||
$serialized = ''; |
||||
|
||||
if (count($data)) { |
||||
foreach ($data as $bucket => $bucketData) { |
||||
$preSerialized[] = $bucket . '|' . serialize($bucketData); |
||||
} |
||||
$serialized = implode('', $preSerialized); |
||||
} |
||||
|
||||
return $serialized; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @link http://ca2.php.net/manual/en/function.session-decode.php#108037 Code from this comment on php.net |
||||
* @throws \UnexpectedValueException If there is a problem parsing the data |
||||
*/ |
||||
public function unserialize($raw) { |
||||
$returnData = array(); |
||||
$offset = 0; |
||||
|
||||
while ($offset < strlen($raw)) { |
||||
if (!strstr(substr($raw, $offset), "|")) { |
||||
throw new \UnexpectedValueException("invalid data, remaining: " . substr($raw, $offset)); |
||||
} |
||||
|
||||
$pos = strpos($raw, "|", $offset); |
||||
$num = $pos - $offset; |
||||
$varname = substr($raw, $offset, $num); |
||||
$offset += $num + 1; |
||||
$data = unserialize(substr($raw, $offset)); |
||||
|
||||
$returnData[$varname] = $data; |
||||
$offset += strlen(serialize($data)); |
||||
} |
||||
|
||||
return $returnData; |
||||
} |
||||
} |
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
<?php |
||||
namespace Ratchet\Session; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\Session\Storage\VirtualSessionStorage; |
||||
use Ratchet\Session\Serialize\HandlerInterface; |
||||
use Symfony\Component\HttpFoundation\Session\Session; |
||||
use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; |
||||
|
||||
/** |
||||
* This component will allow access to session data from your website for each user connected |
||||
* Symfony HttpFoundation is required for this component to work |
||||
* Your website must also use Symfony HttpFoundation Sessions to read your sites session data |
||||
* If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer) |
||||
*/ |
||||
class SessionProvider implements MessageComponentInterface, WsServerInterface { |
||||
/** |
||||
* @var \Ratchet\MessageComponentInterface |
||||
*/ |
||||
protected $_app; |
||||
|
||||
/** |
||||
* Selected handler storage assigned by the developer |
||||
* @var \SessionHandlerInterface |
||||
*/ |
||||
protected $_handler; |
||||
|
||||
/** |
||||
* Null storage handler if no previous session was found |
||||
* @var \SessionHandlerInterface |
||||
*/ |
||||
protected $_null; |
||||
|
||||
/** |
||||
* @var \Ratchet\Session\Serialize\HandlerInterface |
||||
*/ |
||||
protected $_serializer; |
||||
|
||||
/** |
||||
* @param \Ratchet\MessageComponentInterface $app |
||||
* @param \SessionHandlerInterface $handler |
||||
* @param array $options |
||||
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer |
||||
* @throws \RuntimeException |
||||
*/ |
||||
public function __construct(MessageComponentInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { |
||||
$this->_app = $app; |
||||
$this->_handler = $handler; |
||||
$this->_null = new NullSessionHandler; |
||||
|
||||
ini_set('session.auto_start', 0); |
||||
ini_set('session.cache_limiter', ''); |
||||
ini_set('session.use_cookies', 0); |
||||
|
||||
$this->setOptions($options); |
||||
|
||||
if (null === $serializer) { |
||||
$serialClass = __NAMESPACE__ . "\\Serialize\\{$this->toClassCase(ini_get('session.serialize_handler'))}Handler"; // awesome/terrible hack, eh? |
||||
if (!class_exists($serialClass)) { |
||||
throw new \RuntimeException('Unable to parse session serialize handler'); |
||||
} |
||||
|
||||
$serializer = new $serialClass; |
||||
} |
||||
|
||||
$this->_serializer = $serializer; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onOpen(ConnectionInterface $conn) { |
||||
if (!isset($conn->WebSocket) || null === ($id = $conn->WebSocket->request->getCookie(ini_get('session.name')))) { |
||||
$saveHandler = $this->_null; |
||||
$id = ''; |
||||
} else { |
||||
$saveHandler = $this->_handler; |
||||
} |
||||
|
||||
$conn->Session = new Session(new VirtualSessionStorage($saveHandler, $id, $this->_serializer)); |
||||
|
||||
if (ini_get('session.auto_start')) { |
||||
$conn->Session->start(); |
||||
} |
||||
|
||||
return $this->_app->onOpen($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onMessage(ConnectionInterface $from, $msg) { |
||||
return $this->_app->onMessage($from, $msg); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onClose(ConnectionInterface $conn) { |
||||
// "close" session for Connection |
||||
|
||||
return $this->_app->onClose($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
function onError(ConnectionInterface $conn, \Exception $e) { |
||||
return $this->_app->onError($conn, $e); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getSubProtocols() { |
||||
if ($this->_app instanceof WsServerInterface) { |
||||
return $this->_app->getSubProtocols(); |
||||
} else { |
||||
return array(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Set all the php session. ini options |
||||
* © Symfony |
||||
* @param array $options |
||||
* @return array |
||||
*/ |
||||
protected function setOptions(array $options) { |
||||
$all = array( |
||||
'auto_start', 'cache_limiter', 'cookie_domain', 'cookie_httponly', |
||||
'cookie_lifetime', 'cookie_path', 'cookie_secure', |
||||
'entropy_file', 'entropy_length', 'gc_divisor', |
||||
'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', |
||||
'hash_function', 'name', 'referer_check', |
||||
'serialize_handler', 'use_cookies', |
||||
'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', |
||||
'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', |
||||
'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags' |
||||
); |
||||
|
||||
foreach ($all as $key) { |
||||
if (!array_key_exists($key, $options)) { |
||||
$options[$key] = ini_get("session.{$key}"); |
||||
} else { |
||||
ini_set("session.{$key}", $options[$key]); |
||||
} |
||||
} |
||||
|
||||
return $options; |
||||
} |
||||
|
||||
/** |
||||
* @param string $langDef Input to convert |
||||
* @return string |
||||
*/ |
||||
protected function toClassCase($langDef) { |
||||
return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); |
||||
} |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Storage\Proxy; |
||||
use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; |
||||
|
||||
class VirtualProxy extends SessionHandlerProxy { |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $_sessionId; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $_sessionName; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function __construct(\SessionHandlerInterface $handler) { |
||||
parent::__construct($handler); |
||||
|
||||
$this->saveHandlerName = 'user'; |
||||
$this->_sessionName = ini_get('session.name'); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getId() { |
||||
return $this->_sessionId; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function setId($id) { |
||||
$this->_sessionId = $id; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getName() { |
||||
return $this->_sessionName; |
||||
} |
||||
|
||||
/** |
||||
* DO NOT CALL THIS METHOD |
||||
* @internal |
||||
*/ |
||||
public function setName($name) { |
||||
throw new \RuntimeException("Can not change session name in VirtualProxy"); |
||||
} |
||||
} |
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Storage; |
||||
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; |
||||
use Ratchet\Session\Storage\Proxy\VirtualProxy; |
||||
use Ratchet\Session\Serialize\HandlerInterface; |
||||
|
||||
class VirtualSessionStorage extends NativeSessionStorage { |
||||
/** |
||||
* @var \Ratchet\Session\Serialize\HandlerInterface |
||||
*/ |
||||
protected $_serializer; |
||||
|
||||
/** |
||||
* @param \SessionHandlerInterface $handler |
||||
* @param string $sessionId The ID of the session to retrieve |
||||
* @param \Ratchet\Session\Serialize\HandlerInterface $serializer |
||||
*/ |
||||
public function __construct(\SessionHandlerInterface $handler, $sessionId, HandlerInterface $serializer) { |
||||
$this->setSaveHandler($handler); |
||||
$this->saveHandler->setId($sessionId); |
||||
$this->_serializer = $serializer; |
||||
$this->setMetadataBag(null); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function start() { |
||||
if ($this->started && !$this->closed) { |
||||
return true; |
||||
} |
||||
|
||||
// You have to call Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler::open() to use |
||||
// pdo_sqlite (and possible pdo_*) as session storage, if you are using a DSN string instead of a \PDO object |
||||
// in the constructor. The method arguments are filled with the values, which are also used by the symfony |
||||
// framework in this case. This must not be the best choice, but it works. |
||||
$this->saveHandler->open(session_save_path(), session_name()); |
||||
|
||||
$rawData = $this->saveHandler->read($this->saveHandler->getId()); |
||||
$sessionData = $this->_serializer->unserialize($rawData); |
||||
|
||||
$this->loadSession($sessionData); |
||||
|
||||
if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { |
||||
$this->saveHandler->setActive(false); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function regenerate($destroy = false, $lifetime = null) { |
||||
// .. ? |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function save() { |
||||
// get the data from the bags? |
||||
// serialize the data |
||||
// save the data using the saveHandler |
||||
// $this->saveHandler->write($this->saveHandler->getId(), |
||||
|
||||
if (!$this->saveHandler->isWrapper() && !$this->getSaveHandler()->isSessionHandlerInterface()) { |
||||
$this->saveHandler->setActive(false); |
||||
} |
||||
|
||||
$this->closed = true; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function setSaveHandler($saveHandler = null) { |
||||
if (!($saveHandler instanceof \SessionHandlerInterface)) { |
||||
throw new \InvalidArgumentException('Handler must be instance of SessionHandlerInterface'); |
||||
} |
||||
|
||||
if (!($saveHandler instanceof VirtualProxy)) { |
||||
$saveHandler = new VirtualProxy($saveHandler); |
||||
} |
||||
|
||||
$this->saveHandler = $saveHandler; |
||||
} |
||||
} |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
|
||||
class Exception extends \Exception { |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
|
||||
class JsonException extends Exception { |
||||
public function __construct() { |
||||
$code = json_last_error(); |
||||
|
||||
switch ($code) { |
||||
case JSON_ERROR_DEPTH: |
||||
$msg = 'Maximum stack depth exceeded'; |
||||
break; |
||||
case JSON_ERROR_STATE_MISMATCH: |
||||
$msg = 'Underflow or the modes mismatch'; |
||||
break; |
||||
case JSON_ERROR_CTRL_CHAR: |
||||
$msg = 'Unexpected control character found'; |
||||
break; |
||||
case JSON_ERROR_SYNTAX: |
||||
$msg = 'Syntax error, malformed JSON'; |
||||
break; |
||||
case JSON_ERROR_UTF8: |
||||
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded'; |
||||
break; |
||||
default: |
||||
$msg = 'Unknown error'; |
||||
break; |
||||
} |
||||
|
||||
parent::__construct($msg, $code); |
||||
} |
||||
} |
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* WebSocket Application Messaging Protocol |
||||
* |
||||
* @link http://wamp.ws/spec |
||||
* @link https://github.com/oberstet/AutobahnJS |
||||
* |
||||
* +--------------+----+------------------+ |
||||
* | Message Type | ID | DIRECTION | |
||||
* |--------------+----+------------------+ |
||||
* | WELCOME | 0 | Server-to-Client | |
||||
* | PREFIX | 1 | Bi-Directional | |
||||
* | CALL | 2 | Client-to-Server | |
||||
* | CALL RESULT | 3 | Server-to-Client | |
||||
* | CALL ERROR | 4 | Server-to-Client | |
||||
* | SUBSCRIBE | 5 | Client-to-Server | |
||||
* | UNSUBSCRIBE | 6 | Client-to-Server | |
||||
* | PUBLISH | 7 | Client-to-Server | |
||||
* | EVENT | 8 | Server-to-Client | |
||||
* +--------------+----+------------------+ |
||||
*/ |
||||
class ServerProtocol implements MessageComponentInterface, WsServerInterface { |
||||
const MSG_WELCOME = 0; |
||||
const MSG_PREFIX = 1; |
||||
const MSG_CALL = 2; |
||||
const MSG_CALL_RESULT = 3; |
||||
const MSG_CALL_ERROR = 4; |
||||
const MSG_SUBSCRIBE = 5; |
||||
const MSG_UNSUBSCRIBE = 6; |
||||
const MSG_PUBLISH = 7; |
||||
const MSG_EVENT = 8; |
||||
|
||||
/** |
||||
* @var WampServerInterface |
||||
*/ |
||||
protected $_decorating; |
||||
|
||||
/** |
||||
* @var \SplObjectStorage |
||||
*/ |
||||
protected $connections; |
||||
|
||||
/** |
||||
* @param WampServerInterface $serverComponent An class to propagate calls through |
||||
*/ |
||||
public function __construct(WampServerInterface $serverComponent) { |
||||
$this->_decorating = $serverComponent; |
||||
$this->connections = new \SplObjectStorage; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getSubProtocols() { |
||||
if ($this->_decorating instanceof WsServerInterface) { |
||||
$subs = $this->_decorating->getSubProtocols(); |
||||
$subs[] = 'wamp'; |
||||
|
||||
return $subs; |
||||
} else { |
||||
return array('wamp'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$decor = new WampConnection($conn); |
||||
$this->connections->attach($conn, $decor); |
||||
|
||||
$this->_decorating->onOpen($decor); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @throws \Ratchet\Wamp\Exception |
||||
* @throws \Ratchet\Wamp\JsonException |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
$from = $this->connections[$from]; |
||||
|
||||
if (null === ($json = @json_decode($msg, true))) { |
||||
throw new JsonException; |
||||
} |
||||
|
||||
if (!is_array($json) || $json !== array_values($json)) { |
||||
throw new Exception("Invalid WAMP message format"); |
||||
} |
||||
|
||||
switch ($json[0]) { |
||||
case static::MSG_PREFIX: |
||||
$from->WAMP->prefixes[$json[1]] = $json[2]; |
||||
break; |
||||
|
||||
case static::MSG_CALL: |
||||
array_shift($json); |
||||
$callID = array_shift($json); |
||||
$procURI = array_shift($json); |
||||
|
||||
if (count($json) == 1 && is_array($json[0])) { |
||||
$json = $json[0]; |
||||
} |
||||
|
||||
$this->_decorating->onCall($from, $callID, $from->getUri($procURI), $json); |
||||
break; |
||||
|
||||
case static::MSG_SUBSCRIBE: |
||||
$this->_decorating->onSubscribe($from, $from->getUri($json[1])); |
||||
break; |
||||
|
||||
case static::MSG_UNSUBSCRIBE: |
||||
$this->_decorating->onUnSubscribe($from, $from->getUri($json[1])); |
||||
break; |
||||
|
||||
case static::MSG_PUBLISH: |
||||
$exclude = (array_key_exists(3, $json) ? $json[3] : null); |
||||
if (!is_array($exclude)) { |
||||
if (true === (boolean)$exclude) { |
||||
$exclude = array($from->WAMP->sessionId); |
||||
} else { |
||||
$exclude = array(); |
||||
} |
||||
} |
||||
|
||||
$eligible = (array_key_exists(4, $json) ? $json[4] : array()); |
||||
|
||||
$this->_decorating->onPublish($from, $from->getUri($json[1]), $json[2], $exclude, $eligible); |
||||
break; |
||||
|
||||
default: |
||||
throw new Exception('Invalid WAMP message type'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
$decor = $this->connections[$conn]; |
||||
$this->connections->detach($conn); |
||||
|
||||
$this->_decorating->onClose($decor); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
return $this->_decorating->onError($this->connections[$conn], $e); |
||||
} |
||||
} |
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* A topic/channel containing connections that have subscribed to it |
||||
*/ |
||||
class Topic implements \IteratorAggregate, \Countable { |
||||
/** |
||||
* If true the TopicManager will destroy this object if it's ever empty of connections |
||||
* @deprecated in v0.4 |
||||
* @type bool |
||||
*/ |
||||
public $autoDelete = false; |
||||
|
||||
private $id; |
||||
|
||||
private $subscribers; |
||||
|
||||
/** |
||||
* @param string $topicId Unique ID for this object |
||||
*/ |
||||
public function __construct($topicId) { |
||||
$this->id = $topicId; |
||||
$this->subscribers = new \SplObjectStorage; |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getId() { |
||||
return $this->id; |
||||
} |
||||
|
||||
public function __toString() { |
||||
return $this->getId(); |
||||
} |
||||
|
||||
/** |
||||
* Send a message to all the connections in this topic |
||||
* @param string $msg Payload to publish |
||||
* @param array $exclude A list of session IDs the message should be excluded from (blacklist) |
||||
* @param array $eligible A list of session Ids the message should be send to (whitelist) |
||||
* @return Topic The same Topic object to chain |
||||
*/ |
||||
public function broadcast($msg, array $exclude = array(), array $eligible = array()) { |
||||
$useEligible = (bool)count($eligible); |
||||
foreach ($this->subscribers as $client) { |
||||
if (in_array($client->WAMP->sessionId, $exclude)) { |
||||
continue; |
||||
} |
||||
|
||||
if ($useEligible && !in_array($client->WAMP->sessionId, $eligible)) { |
||||
continue; |
||||
} |
||||
|
||||
$client->event($this->id, $msg); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param WampConnection $conn |
||||
* @return boolean |
||||
*/ |
||||
public function has(ConnectionInterface $conn) { |
||||
return $this->subscribers->contains($conn); |
||||
} |
||||
|
||||
/** |
||||
* @param WampConnection $conn |
||||
* @return Topic |
||||
*/ |
||||
public function add(ConnectionInterface $conn) { |
||||
$this->subscribers->attach($conn); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param WampConnection $conn |
||||
* @return Topic |
||||
*/ |
||||
public function remove(ConnectionInterface $conn) { |
||||
if ($this->subscribers->contains($conn)) { |
||||
$this->subscribers->detach($conn); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getIterator() { |
||||
return $this->subscribers; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function count() { |
||||
return $this->subscribers->count(); |
||||
} |
||||
} |
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
|
||||
class TopicManager implements WsServerInterface, WampServerInterface { |
||||
/** |
||||
* @var WampServerInterface |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $topicLookup = array(); |
||||
|
||||
public function __construct(WampServerInterface $app) { |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$conn->WAMP->subscriptions = new \SplObjectStorage; |
||||
$this->app->onOpen($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) { |
||||
$this->app->onCall($conn, $id, $this->getTopic($topic), $params); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onSubscribe(ConnectionInterface $conn, $topic) { |
||||
$topicObj = $this->getTopic($topic); |
||||
|
||||
if ($conn->WAMP->subscriptions->contains($topicObj)) { |
||||
return; |
||||
} |
||||
|
||||
$this->topicLookup[$topic]->add($conn); |
||||
$conn->WAMP->subscriptions->attach($topicObj); |
||||
$this->app->onSubscribe($conn, $topicObj); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onUnsubscribe(ConnectionInterface $conn, $topic) { |
||||
$topicObj = $this->getTopic($topic); |
||||
|
||||
if (!$conn->WAMP->subscriptions->contains($topicObj)) { |
||||
return; |
||||
} |
||||
|
||||
$this->cleanTopic($topicObj, $conn); |
||||
|
||||
$this->app->onUnsubscribe($conn, $topicObj); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { |
||||
$this->app->onPublish($conn, $this->getTopic($topic), $event, $exclude, $eligible); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
$this->app->onClose($conn); |
||||
|
||||
foreach ($this->topicLookup as $topic) { |
||||
$this->cleanTopic($topic, $conn); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$this->app->onError($conn, $e); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getSubProtocols() { |
||||
if ($this->app instanceof WsServerInterface) { |
||||
return $this->app->getSubProtocols(); |
||||
} |
||||
|
||||
return array(); |
||||
} |
||||
|
||||
/** |
||||
* @param string |
||||
* @return Topic |
||||
*/ |
||||
protected function getTopic($topic) { |
||||
if (!array_key_exists($topic, $this->topicLookup)) { |
||||
$this->topicLookup[$topic] = new Topic($topic); |
||||
} |
||||
|
||||
return $this->topicLookup[$topic]; |
||||
} |
||||
|
||||
protected function cleanTopic(Topic $topic, ConnectionInterface $conn) { |
||||
if ($conn->WAMP->subscriptions->contains($topic)) { |
||||
$conn->WAMP->subscriptions->detach($topic); |
||||
} |
||||
|
||||
$this->topicLookup[$topic->getId()]->remove($conn); |
||||
|
||||
if ($topic->autoDelete && 0 === $topic->count()) { |
||||
unset($this->topicLookup[$topic->getId()]); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\AbstractConnectionDecorator; |
||||
use Ratchet\Wamp\ServerProtocol as WAMP; |
||||
|
||||
/** |
||||
* A ConnectionInterface object wrapper that is passed to your WAMP application |
||||
* representing a client. Methods on this Connection are therefore different. |
||||
* @property \stdClass $WAMP |
||||
*/ |
||||
class WampConnection extends AbstractConnectionDecorator { |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function __construct(ConnectionInterface $conn) { |
||||
parent::__construct($conn); |
||||
|
||||
$this->WAMP = new \StdClass; |
||||
$this->WAMP->sessionId = str_replace('.', '', uniqid(mt_rand(), true)); |
||||
$this->WAMP->prefixes = array(); |
||||
|
||||
$this->send(json_encode(array(WAMP::MSG_WELCOME, $this->WAMP->sessionId, 1, \Ratchet\VERSION))); |
||||
} |
||||
|
||||
/** |
||||
* Successfully respond to a call made by the client |
||||
* @param string $id The unique ID given by the client to respond to |
||||
* @param array $data an object or array |
||||
* @return WampConnection |
||||
*/ |
||||
public function callResult($id, $data = array()) { |
||||
return $this->send(json_encode(array(WAMP::MSG_CALL_RESULT, $id, $data))); |
||||
} |
||||
|
||||
/** |
||||
* Respond with an error to a client call |
||||
* @param string $id The unique ID given by the client to respond to |
||||
* @param string $errorUri The URI given to identify the specific error |
||||
* @param string $desc A developer-oriented description of the error |
||||
* @param string $details An optional human readable detail message to send back |
||||
* @return WampConnection |
||||
*/ |
||||
public function callError($id, $errorUri, $desc = '', $details = null) { |
||||
if ($errorUri instanceof Topic) { |
||||
$errorUri = (string)$errorUri; |
||||
} |
||||
|
||||
$data = array(WAMP::MSG_CALL_ERROR, $id, $errorUri, $desc); |
||||
|
||||
if (null !== $details) { |
||||
$data[] = $details; |
||||
} |
||||
|
||||
return $this->send(json_encode($data)); |
||||
} |
||||
|
||||
/** |
||||
* @param string $topic The topic to broadcast to |
||||
* @param mixed $msg Data to send with the event. Anything that is json'able |
||||
* @return WampConnection |
||||
*/ |
||||
public function event($topic, $msg) { |
||||
return $this->send(json_encode(array(WAMP::MSG_EVENT, (string)$topic, $msg))); |
||||
} |
||||
|
||||
/** |
||||
* @param string $curie |
||||
* @param string $uri |
||||
* @return WampConnection |
||||
*/ |
||||
public function prefix($curie, $uri) { |
||||
$this->WAMP->prefixes[$curie] = (string)$uri; |
||||
|
||||
return $this->send(json_encode(array(WAMP::MSG_PREFIX, $curie, (string)$uri))); |
||||
} |
||||
|
||||
/** |
||||
* Get the full request URI from the connection object if a prefix has been established for it |
||||
* @param string $uri |
||||
* @return string |
||||
*/ |
||||
public function getUri($uri) { |
||||
$curieSeperator = ':'; |
||||
|
||||
if (preg_match('/http(s*)\:\/\//', $uri) == false) { |
||||
if (strpos($uri, $curieSeperator) !== false) { |
||||
list($prefix, $action) = explode($curieSeperator, $uri); |
||||
|
||||
if(isset($this->WAMP->prefixes[$prefix]) === true){ |
||||
return $this->WAMP->prefixes[$prefix] . '#' . $action; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $uri; |
||||
} |
||||
|
||||
/** |
||||
* @internal |
||||
*/ |
||||
public function send($data) { |
||||
$this->getConnection()->send($data); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function close($opt = null) { |
||||
$this->getConnection()->close($opt); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* Enable support for the official WAMP sub-protocol in your application |
||||
* WAMP allows for Pub/Sub and RPC |
||||
* @link http://wamp.ws The WAMP specification |
||||
* @link https://github.com/oberstet/AutobahnJS Souce for client side library |
||||
* @link http://autobahn.s3.amazonaws.com/js/autobahn.min.js Minified client side library |
||||
*/ |
||||
class WampServer implements MessageComponentInterface, WsServerInterface { |
||||
/** |
||||
* @var ServerProtocol |
||||
*/ |
||||
protected $wampProtocol; |
||||
|
||||
/** |
||||
* This class just makes it 1 step easier to use Topic objects in WAMP |
||||
* If you're looking at the source code, look in the __construct of this |
||||
* class and use that to make your application instead of using this |
||||
*/ |
||||
public function __construct(WampServerInterface $app) { |
||||
$this->wampProtocol = new ServerProtocol(new TopicManager($app)); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$this->wampProtocol->onOpen($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onMessage(ConnectionInterface $conn, $msg) { |
||||
try { |
||||
$this->wampProtocol->onMessage($conn, $msg); |
||||
} catch (Exception $we) { |
||||
$conn->close(1007); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
$this->wampProtocol->onClose($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$this->wampProtocol->onError($conn, $e); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getSubProtocols() { |
||||
return $this->wampProtocol->getSubProtocols(); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp; |
||||
use Ratchet\ComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
/** |
||||
* An extension of Ratchet\ComponentInterface to server a WAMP application |
||||
* onMessage is replaced by various types of messages for this protocol (pub/sub or rpc) |
||||
*/ |
||||
interface WampServerInterface extends ComponentInterface { |
||||
/** |
||||
* An RPC call has been received |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param string $id The unique ID of the RPC, required to respond to |
||||
* @param string|Topic $topic The topic to execute the call against |
||||
* @param array $params Call parameters received from the client |
||||
*/ |
||||
function onCall(ConnectionInterface $conn, $id, $topic, array $params); |
||||
|
||||
/** |
||||
* A request to subscribe to a topic has been made |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param string|Topic $topic The topic to subscribe to |
||||
*/ |
||||
function onSubscribe(ConnectionInterface $conn, $topic); |
||||
|
||||
/** |
||||
* A request to unsubscribe from a topic has been made |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param string|Topic $topic The topic to unsubscribe from |
||||
*/ |
||||
function onUnSubscribe(ConnectionInterface $conn, $topic); |
||||
|
||||
/** |
||||
* A client is attempting to publish content to a subscribed connections on a URI |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param string|Topic $topic The topic the user has attempted to publish to |
||||
* @param string $event Payload of the publish |
||||
* @param array $exclude A list of session IDs the message should be excluded from (blacklist) |
||||
* @param array $eligible A list of session Ids the message should be send to (whitelist) |
||||
*/ |
||||
function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible); |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Encoding; |
||||
|
||||
class ToggleableValidator implements ValidatorInterface { |
||||
/** |
||||
* Toggle if checkEncoding checks the encoding or not |
||||
* @var bool |
||||
*/ |
||||
public $on; |
||||
|
||||
/** |
||||
* @var Validator |
||||
*/ |
||||
private $validator; |
||||
|
||||
public function __construct($on = true) { |
||||
$this->validator = new Validator; |
||||
$this->on = (boolean)$on; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function checkEncoding($str, $encoding) { |
||||
if (!(boolean)$this->on) { |
||||
return true; |
||||
} |
||||
|
||||
return $this->validator->checkEncoding($str, $encoding); |
||||
} |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Encoding; |
||||
|
||||
/** |
||||
* This class handled encoding validation |
||||
*/ |
||||
class Validator { |
||||
const UTF8_ACCEPT = 0; |
||||
const UTF8_REJECT = 1; |
||||
|
||||
/** |
||||
* Incremental UTF-8 validator with constant memory consumption (minimal state). |
||||
* |
||||
* Implements the algorithm "Flexible and Economical UTF-8 Decoder" by |
||||
* Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/). |
||||
*/ |
||||
protected static $dfa = array( |
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00..1f |
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 20..3f |
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 40..5f |
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 60..7f |
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, # 80..9f |
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, # a0..bf |
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, # c0..df |
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, # e0..ef |
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, # f0..ff |
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, # s0..s0 |
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, # s1..s2 |
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, # s3..s4 |
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, # s5..s6 |
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, # s7..s8 |
||||
); |
||||
|
||||
/** |
||||
* Lookup if mbstring is available |
||||
* @var bool |
||||
*/ |
||||
private $hasMbString = false; |
||||
|
||||
/** |
||||
* Lookup if iconv is available |
||||
* @var bool |
||||
*/ |
||||
private $hasIconv = false; |
||||
|
||||
public function __construct() { |
||||
$this->hasMbString = extension_loaded('mbstring'); |
||||
$this->hasIconv = extension_loaded('iconv'); |
||||
} |
||||
|
||||
/** |
||||
* @param string $str The value to check the encoding |
||||
* @param string $against The type of encoding to check against |
||||
* @return bool |
||||
*/ |
||||
public function checkEncoding($str, $against) { |
||||
if ('UTF-8' == $against) { |
||||
return $this->isUtf8($str); |
||||
} |
||||
|
||||
if ($this->hasMbString) { |
||||
return mb_check_encoding($str, $against); |
||||
} elseif ($this->hasIconv) { |
||||
return ($str == iconv($against, "{$against}//IGNORE", $str)); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
protected function isUtf8($str) { |
||||
if ($this->hasMbString) { |
||||
if (false === mb_check_encoding($str, 'UTF-8')) { |
||||
return false; |
||||
} |
||||
} elseif ($this->hasIconv) { |
||||
if ($str != iconv('UTF-8', 'UTF-8//IGNORE', $str)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
$state = static::UTF8_ACCEPT; |
||||
|
||||
for ($i = 0, $len = strlen($str); $i < $len; $i++) { |
||||
$state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; |
||||
|
||||
if (static::UTF8_REJECT === $state) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Encoding; |
||||
|
||||
interface ValidatorInterface { |
||||
/** |
||||
* Verify a string matches the encoding type |
||||
* @param string $str The string to check |
||||
* @param string $encoding The encoding type to check against |
||||
* @return bool |
||||
*/ |
||||
function checkEncoding($str, $encoding); |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
|
||||
interface DataInterface { |
||||
/** |
||||
* Determine if the message is complete or still fragmented |
||||
* @return bool |
||||
*/ |
||||
function isCoalesced(); |
||||
|
||||
/** |
||||
* Get the number of bytes the payload is set to be |
||||
* @return int |
||||
*/ |
||||
function getPayloadLength(); |
||||
|
||||
/** |
||||
* Get the payload (message) sent from peer |
||||
* @return string |
||||
*/ |
||||
function getPayload(); |
||||
|
||||
/** |
||||
* Get raw contents of the message |
||||
* @return string |
||||
*/ |
||||
function getContents(); |
||||
} |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
|
||||
interface FrameInterface extends DataInterface { |
||||
/** |
||||
* Add incoming data to the frame from peer |
||||
* @param string |
||||
*/ |
||||
function addBuffer($buf); |
||||
|
||||
/** |
||||
* Is this the final frame in a fragmented message? |
||||
* @return bool |
||||
*/ |
||||
function isFinal(); |
||||
|
||||
/** |
||||
* Is the payload masked? |
||||
* @return bool |
||||
*/ |
||||
function isMasked(); |
||||
|
||||
/** |
||||
* @return int |
||||
*/ |
||||
function getOpcode(); |
||||
|
||||
/** |
||||
* @return int |
||||
*/ |
||||
//function getReceivedPayloadLength(); |
||||
|
||||
/** |
||||
* 32-big string |
||||
* @return string |
||||
*/ |
||||
function getMaskingKey(); |
||||
} |
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\MessageInterface; |
||||
use Ratchet\WebSocket\Version\Hixie76\Connection; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
use Ratchet\WebSocket\Version\Hixie76\Frame; |
||||
|
||||
/** |
||||
* FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application! |
||||
* Hixie76 is bad for 2 (there's more) reasons: |
||||
* 1) The handshake is done in HTTP, which includes a key for signing in the body... |
||||
* BUT there is no Length defined in the header (as per HTTP spec) so the TCP buffer can't tell when the message is done! |
||||
* 2) By nature it's insecure. Google did a test study where they were able to do a |
||||
* man-in-the-middle attack on 10%-15% of the people who saw their ad who had a browser (currently only Safari) supporting the Hixie76 protocol. |
||||
* This was exploited by taking advantage of proxy servers in front of the user who ignored some HTTP headers in the handshake |
||||
* The Hixie76 is currently implemented by Safari |
||||
* @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 |
||||
*/ |
||||
class Hixie76 implements VersionInterface { |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isProtocol(RequestInterface $request) { |
||||
return !(null === $request->getHeader('Sec-WebSocket-Key2')); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getVersionNumber() { |
||||
return 0; |
||||
} |
||||
|
||||
/** |
||||
* @param \Guzzle\Http\Message\RequestInterface $request |
||||
* @return \Guzzle\Http\Message\Response |
||||
* @throws \UnderflowException If there hasn't been enough data received |
||||
*/ |
||||
public function handshake(RequestInterface $request) { |
||||
$body = substr($request->getBody(), 0, 8); |
||||
if (8 !== strlen($body)) { |
||||
throw new \UnderflowException("Not enough data received to issue challenge response"); |
||||
} |
||||
|
||||
$challenge = $this->sign((string)$request->getHeader('Sec-WebSocket-Key1'), (string)$request->getHeader('Sec-WebSocket-Key2'), $body); |
||||
|
||||
$headers = array( |
||||
'Upgrade' => 'WebSocket' |
||||
, 'Connection' => 'Upgrade' |
||||
, 'Sec-WebSocket-Origin' => (string)$request->getHeader('Origin') |
||||
, 'Sec-WebSocket-Location' => 'ws://' . (string)$request->getHeader('Host') . $request->getPath() |
||||
); |
||||
|
||||
$response = new Response(101, $headers, $challenge); |
||||
$response->setStatus(101, 'WebSocket Protocol Handshake'); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { |
||||
$upgraded = new Connection($conn); |
||||
|
||||
if (!isset($upgraded->WebSocket)) { |
||||
$upgraded->WebSocket = new \StdClass; |
||||
} |
||||
|
||||
$upgraded->WebSocket->coalescedCallback = $coalescedCallback; |
||||
|
||||
return $upgraded; |
||||
} |
||||
|
||||
public function onMessage(ConnectionInterface $from, $data) { |
||||
$overflow = ''; |
||||
|
||||
if (!isset($from->WebSocket->frame)) { |
||||
$from->WebSocket->frame = $this->newFrame(); |
||||
} |
||||
|
||||
$from->WebSocket->frame->addBuffer($data); |
||||
if ($from->WebSocket->frame->isCoalesced()) { |
||||
$overflow = $from->WebSocket->frame->extractOverflow(); |
||||
|
||||
$parsed = $from->WebSocket->frame->getPayload(); |
||||
unset($from->WebSocket->frame); |
||||
|
||||
$from->WebSocket->coalescedCallback->onMessage($from, $parsed); |
||||
|
||||
unset($from->WebSocket->frame); |
||||
} |
||||
|
||||
if (strlen($overflow) > 0) { |
||||
$this->onMessage($from, $overflow); |
||||
} |
||||
} |
||||
|
||||
public function newFrame() { |
||||
return new Frame; |
||||
} |
||||
|
||||
public function generateKeyNumber($key) { |
||||
if (0 === substr_count($key, ' ')) { |
||||
return 0; |
||||
} |
||||
|
||||
return preg_replace('[\D]', '', $key) / substr_count($key, ' '); |
||||
} |
||||
|
||||
protected function sign($key1, $key2, $code) { |
||||
return md5( |
||||
pack('N', $this->generateKeyNumber($key1)) |
||||
. pack('N', $this->generateKeyNumber($key2)) |
||||
. $code |
||||
, true); |
||||
} |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\Hixie76; |
||||
use Ratchet\AbstractConnectionDecorator; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @property \StdClass $WebSocket |
||||
*/ |
||||
class Connection extends AbstractConnectionDecorator { |
||||
public function send($msg) { |
||||
if (!$this->WebSocket->closing) { |
||||
$this->getConnection()->send(chr(0) . $msg . chr(255)); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
public function close() { |
||||
if (!$this->WebSocket->closing) { |
||||
$this->getConnection()->send(chr(255)); |
||||
$this->getConnection()->close(); |
||||
|
||||
$this->WebSocket->closing = true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\Hixie76; |
||||
use Ratchet\WebSocket\Version\FrameInterface; |
||||
|
||||
/** |
||||
* This does not entirely follow the protocol to spec, but (mostly) works |
||||
* Hixie76 probably should not even be supported |
||||
*/ |
||||
class Frame implements FrameInterface { |
||||
/** |
||||
* @type string |
||||
*/ |
||||
protected $_data = ''; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isCoalesced() { |
||||
return (boolean)($this->_data[0] == chr(0) && substr($this->_data, -1) == chr(255)); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function addBuffer($buf) { |
||||
$this->_data .= (string)$buf; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isFinal() { |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isMasked() { |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getOpcode() { |
||||
return 1; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayloadLength() { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException('Not enough of the message has been buffered to determine the length of the payload'); |
||||
} |
||||
|
||||
return strlen($this->_data) - 2; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getMaskingKey() { |
||||
return ''; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayload() { |
||||
if (!$this->isCoalesced()) { |
||||
return new \UnderflowException('Not enough data buffered to read payload'); |
||||
} |
||||
|
||||
return substr($this->_data, 1, strlen($this->_data) - 2); |
||||
} |
||||
|
||||
public function getContents() { |
||||
return $this->_data; |
||||
} |
||||
|
||||
public function extractOverflow() { |
||||
return ''; |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
|
||||
class HyBi10 extends RFC6455 { |
||||
public function isProtocol(RequestInterface $request) { |
||||
$version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); |
||||
|
||||
return ($version >= 6 && $version < 13); |
||||
} |
||||
|
||||
public function getVersionNumber() { |
||||
return 6; |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
|
||||
interface MessageInterface extends DataInterface { |
||||
/** |
||||
* @param FrameInterface $fragment |
||||
* @return MessageInterface |
||||
*/ |
||||
function addFrame(FrameInterface $fragment); |
||||
|
||||
/** |
||||
* @return int |
||||
*/ |
||||
function getOpcode(); |
||||
} |
@ -0,0 +1,273 @@
@@ -0,0 +1,273 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\MessageInterface; |
||||
use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier; |
||||
use Ratchet\WebSocket\Version\RFC6455\Message; |
||||
use Ratchet\WebSocket\Version\RFC6455\Frame; |
||||
use Ratchet\WebSocket\Version\RFC6455\Connection; |
||||
use Ratchet\WebSocket\Encoding\ValidatorInterface; |
||||
use Ratchet\WebSocket\Encoding\Validator; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
|
||||
/** |
||||
* The latest version of the WebSocket protocol |
||||
* @link http://tools.ietf.org/html/rfc6455 |
||||
* @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE'); |
||||
*/ |
||||
class RFC6455 implements VersionInterface { |
||||
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; |
||||
|
||||
/** |
||||
* @var RFC6455\HandshakeVerifier |
||||
*/ |
||||
protected $_verifier; |
||||
|
||||
/** |
||||
* A lookup of the valid close codes that can be sent in a frame |
||||
* @var array |
||||
*/ |
||||
private $closeCodes = array(); |
||||
|
||||
/** |
||||
* @var \Ratchet\WebSocket\Encoding\ValidatorInterface |
||||
*/ |
||||
protected $validator; |
||||
|
||||
public function __construct(ValidatorInterface $validator = null) { |
||||
$this->_verifier = new HandshakeVerifier; |
||||
$this->setCloseCodes(); |
||||
|
||||
if (null === $validator) { |
||||
$validator = new Validator; |
||||
} |
||||
|
||||
$this->validator = $validator; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isProtocol(RequestInterface $request) { |
||||
$version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); |
||||
|
||||
return ($this->getVersionNumber() === $version); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getVersionNumber() { |
||||
return 13; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function handshake(RequestInterface $request) { |
||||
if (true !== $this->_verifier->verifyAll($request)) { |
||||
return new Response(400); |
||||
} |
||||
|
||||
return new Response(101, array( |
||||
'Upgrade' => 'websocket' |
||||
, 'Connection' => 'Upgrade' |
||||
, 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')) |
||||
)); |
||||
} |
||||
|
||||
/** |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param \Ratchet\MessageInterface $coalescedCallback |
||||
* @return \Ratchet\WebSocket\Version\RFC6455\Connection |
||||
*/ |
||||
public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { |
||||
$upgraded = new Connection($conn); |
||||
|
||||
if (!isset($upgraded->WebSocket)) { |
||||
$upgraded->WebSocket = new \StdClass; |
||||
} |
||||
|
||||
$upgraded->WebSocket->coalescedCallback = $coalescedCallback; |
||||
|
||||
return $upgraded; |
||||
} |
||||
|
||||
/** |
||||
* @param \Ratchet\WebSocket\Version\RFC6455\Connection $from |
||||
* @param string $data |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $data) { |
||||
$overflow = ''; |
||||
|
||||
if (!isset($from->WebSocket->message)) { |
||||
$from->WebSocket->message = $this->newMessage(); |
||||
} |
||||
|
||||
// There is a frame fragment attached to the connection, add to it |
||||
if (!isset($from->WebSocket->frame)) { |
||||
$from->WebSocket->frame = $this->newFrame(); |
||||
} |
||||
|
||||
$from->WebSocket->frame->addBuffer($data); |
||||
if ($from->WebSocket->frame->isCoalesced()) { |
||||
$frame = $from->WebSocket->frame; |
||||
|
||||
if (false !== $frame->getRsv1() || |
||||
false !== $frame->getRsv2() || |
||||
false !== $frame->getRsv3() |
||||
) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
if (!$frame->isMasked()) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
$opcode = $frame->getOpcode(); |
||||
|
||||
if ($opcode > 2) { |
||||
if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
switch ($opcode) { |
||||
case $frame::OP_CLOSE: |
||||
$closeCode = 0; |
||||
|
||||
$bin = $frame->getPayload(); |
||||
|
||||
if (empty($bin)) { |
||||
return $from->close(); |
||||
} |
||||
|
||||
if (strlen($bin) >= 2) { |
||||
list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); |
||||
} |
||||
|
||||
if (!$this->isValidCloseCode($closeCode)) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) { |
||||
return $from->close($frame::CLOSE_BAD_PAYLOAD); |
||||
} |
||||
|
||||
$frame->unMaskPayload(); |
||||
|
||||
return $from->close($frame); |
||||
break; |
||||
case $frame::OP_PING: |
||||
$from->send($this->newFrame($frame->getPayload(), true, $frame::OP_PONG)); |
||||
break; |
||||
case $frame::OP_PONG: |
||||
break; |
||||
default: |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
break; |
||||
} |
||||
|
||||
$overflow = $from->WebSocket->frame->extractOverflow(); |
||||
|
||||
unset($from->WebSocket->frame, $frame, $opcode); |
||||
|
||||
if (strlen($overflow) > 0) { |
||||
$this->onMessage($from, $overflow); |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
$overflow = $from->WebSocket->frame->extractOverflow(); |
||||
|
||||
if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) { |
||||
return $from->close($frame::CLOSE_PROTOCOL); |
||||
} |
||||
|
||||
$from->WebSocket->message->addFrame($from->WebSocket->frame); |
||||
unset($from->WebSocket->frame); |
||||
} |
||||
|
||||
if ($from->WebSocket->message->isCoalesced()) { |
||||
$parsed = $from->WebSocket->message->getPayload(); |
||||
unset($from->WebSocket->message); |
||||
|
||||
if (!$this->validator->checkEncoding($parsed, 'UTF-8')) { |
||||
return $from->close(Frame::CLOSE_BAD_PAYLOAD); |
||||
} |
||||
|
||||
$from->WebSocket->coalescedCallback->onMessage($from, $parsed); |
||||
} |
||||
|
||||
if (strlen($overflow) > 0) { |
||||
$this->onMessage($from, $overflow); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return RFC6455\Message |
||||
*/ |
||||
public function newMessage() { |
||||
return new Message; |
||||
} |
||||
|
||||
/** |
||||
* @param string|null $payload |
||||
* @param bool|null $final |
||||
* @param int|null $opcode |
||||
* @return RFC6455\Frame |
||||
*/ |
||||
public function newFrame($payload = null, $final = null, $opcode = null) { |
||||
return new Frame($payload, $final, $opcode); |
||||
} |
||||
|
||||
/** |
||||
* Used when doing the handshake to encode the key, verifying client/server are speaking the same language |
||||
* @param string $key |
||||
* @return string |
||||
* @internal |
||||
*/ |
||||
public function sign($key) { |
||||
return base64_encode(sha1($key . static::GUID, true)); |
||||
} |
||||
|
||||
/** |
||||
* Determine if a close code is valid |
||||
* @param int|string |
||||
* @return bool |
||||
*/ |
||||
public function isValidCloseCode($val) { |
||||
if (array_key_exists($val, $this->closeCodes)) { |
||||
return true; |
||||
} |
||||
|
||||
if ($val >= 3000 && $val <= 4999) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Creates a private lookup of valid, private close codes |
||||
*/ |
||||
protected function setCloseCodes() { |
||||
$this->closeCodes[Frame::CLOSE_NORMAL] = true; |
||||
$this->closeCodes[Frame::CLOSE_GOING_AWAY] = true; |
||||
$this->closeCodes[Frame::CLOSE_PROTOCOL] = true; |
||||
$this->closeCodes[Frame::CLOSE_BAD_DATA] = true; |
||||
//$this->closeCodes[Frame::CLOSE_NO_STATUS] = true; |
||||
//$this->closeCodes[Frame::CLOSE_ABNORMAL] = true; |
||||
$this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true; |
||||
$this->closeCodes[Frame::CLOSE_POLICY] = true; |
||||
$this->closeCodes[Frame::CLOSE_TOO_BIG] = true; |
||||
$this->closeCodes[Frame::CLOSE_MAND_EXT] = true; |
||||
$this->closeCodes[Frame::CLOSE_SRV_ERR] = true; |
||||
//$this->closeCodes[Frame::CLOSE_TLS] = true; |
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\RFC6455; |
||||
use Ratchet\AbstractConnectionDecorator; |
||||
use Ratchet\WebSocket\Version\DataInterface; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @property \StdClass $WebSocket |
||||
*/ |
||||
class Connection extends AbstractConnectionDecorator { |
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function send($msg) { |
||||
if (!$this->WebSocket->closing) { |
||||
if (!($msg instanceof DataInterface)) { |
||||
$msg = new Frame($msg); |
||||
} |
||||
|
||||
$this->getConnection()->send($msg->getContents()); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function close($code = 1000) { |
||||
if ($this->WebSocket->closing) { |
||||
return; |
||||
} |
||||
|
||||
if ($code instanceof DataInterface) { |
||||
$this->send($code); |
||||
} else { |
||||
$this->send(new Frame(pack('n', $code), true, Frame::OP_CLOSE)); |
||||
} |
||||
|
||||
$this->getConnection()->close(); |
||||
|
||||
$this->WebSocket->closing = true; |
||||
} |
||||
} |
@ -0,0 +1,451 @@
@@ -0,0 +1,451 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\RFC6455; |
||||
use Ratchet\WebSocket\Version\FrameInterface; |
||||
|
||||
class Frame implements FrameInterface { |
||||
const OP_CONTINUE = 0; |
||||
const OP_TEXT = 1; |
||||
const OP_BINARY = 2; |
||||
const OP_CLOSE = 8; |
||||
const OP_PING = 9; |
||||
const OP_PONG = 10; |
||||
|
||||
const CLOSE_NORMAL = 1000; |
||||
const CLOSE_GOING_AWAY = 1001; |
||||
const CLOSE_PROTOCOL = 1002; |
||||
const CLOSE_BAD_DATA = 1003; |
||||
const CLOSE_NO_STATUS = 1005; |
||||
const CLOSE_ABNORMAL = 1006; |
||||
const CLOSE_BAD_PAYLOAD = 1007; |
||||
const CLOSE_POLICY = 1008; |
||||
const CLOSE_TOO_BIG = 1009; |
||||
const CLOSE_MAND_EXT = 1010; |
||||
const CLOSE_SRV_ERR = 1011; |
||||
const CLOSE_TLS = 1015; |
||||
|
||||
const MASK_LENGTH = 4; |
||||
|
||||
/** |
||||
* The contents of the frame |
||||
* @var string |
||||
*/ |
||||
protected $data = ''; |
||||
|
||||
/** |
||||
* Number of bytes received from the frame |
||||
* @var int |
||||
*/ |
||||
public $bytesRecvd = 0; |
||||
|
||||
/** |
||||
* Number of bytes in the payload (as per framing protocol) |
||||
* @var int |
||||
*/ |
||||
protected $defPayLen = -1; |
||||
|
||||
/** |
||||
* If the frame is coalesced this is true |
||||
* This is to prevent doing math every time ::isCoalesced is called |
||||
* @var boolean |
||||
*/ |
||||
private $isCoalesced = false; |
||||
|
||||
/** |
||||
* The unpacked first byte of the frame |
||||
* @var int |
||||
*/ |
||||
protected $firstByte = -1; |
||||
|
||||
/** |
||||
* The unpacked second byte of the frame |
||||
* @var int |
||||
*/ |
||||
protected $secondByte = -1; |
||||
|
||||
|
||||
/** |
||||
* @param string|null $payload |
||||
* @param bool $final |
||||
* @param int $opcode |
||||
*/ |
||||
public function __construct($payload = null, $final = true, $opcode = 1) { |
||||
if (null === $payload) { |
||||
return; |
||||
} |
||||
|
||||
$this->defPayLen = strlen($payload); |
||||
$this->firstByte = ($final ? 128 : 0) + $opcode; |
||||
$this->secondByte = $this->defPayLen; |
||||
$this->isCoalesced = true; |
||||
|
||||
$ext = ''; |
||||
if ($this->defPayLen > 65535) { |
||||
$ext = pack('NN', 0, $this->defPayLen); |
||||
$this->secondByte = 127; |
||||
} elseif ($this->defPayLen > 125) { |
||||
$ext = pack('n', $this->defPayLen); |
||||
$this->secondByte = 126; |
||||
} |
||||
|
||||
$this->data = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload; |
||||
$this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isCoalesced() { |
||||
if (true === $this->isCoalesced) { |
||||
return true; |
||||
} |
||||
|
||||
try { |
||||
$payload_length = $this->getPayloadLength(); |
||||
$payload_start = $this->getPayloadStartingByte(); |
||||
} catch (\UnderflowException $e) { |
||||
return false; |
||||
} |
||||
|
||||
$this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start; |
||||
|
||||
return $this->isCoalesced; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function addBuffer($buf) { |
||||
$len = strlen($buf); |
||||
|
||||
$this->data .= $buf; |
||||
$this->bytesRecvd += $len; |
||||
|
||||
if ($this->firstByte === -1 && $this->bytesRecvd !== 0) { |
||||
$this->firstByte = ord($this->data[0]); |
||||
} |
||||
|
||||
if ($this->secondByte === -1 && $this->bytesRecvd >= 2) { |
||||
$this->secondByte = ord($this->data[1]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isFinal() { |
||||
if (-1 === $this->firstByte) { |
||||
throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message'); |
||||
} |
||||
|
||||
return 128 === ($this->firstByte & 128); |
||||
} |
||||
|
||||
/** |
||||
* @return boolean |
||||
* @throws \UnderflowException |
||||
*/ |
||||
public function getRsv1() { |
||||
if (-1 === $this->firstByte) { |
||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit'); |
||||
} |
||||
|
||||
return 64 === ($this->firstByte & 64); |
||||
} |
||||
|
||||
/** |
||||
* @return boolean |
||||
* @throws \UnderflowException |
||||
*/ |
||||
public function getRsv2() { |
||||
if (-1 === $this->firstByte) { |
||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit'); |
||||
} |
||||
|
||||
return 32 === ($this->firstByte & 32); |
||||
} |
||||
|
||||
/** |
||||
* @return boolean |
||||
* @throws \UnderflowException |
||||
*/ |
||||
public function getRsv3() { |
||||
if (-1 === $this->firstByte) { |
||||
throw new \UnderflowException('Not enough bytes received to determine reserved bit'); |
||||
} |
||||
|
||||
return 16 == ($this->firstByte & 16); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isMasked() { |
||||
if (-1 === $this->secondByte) { |
||||
throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); |
||||
} |
||||
|
||||
return 128 === ($this->secondByte & 128); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getMaskingKey() { |
||||
if (!$this->isMasked()) { |
||||
return ''; |
||||
} |
||||
|
||||
$start = 1 + $this->getNumPayloadBytes(); |
||||
|
||||
if ($this->bytesRecvd < $start + static::MASK_LENGTH) { |
||||
throw new \UnderflowException('Not enough data buffered to calculate the masking key'); |
||||
} |
||||
|
||||
return substr($this->data, $start, static::MASK_LENGTH); |
||||
} |
||||
|
||||
/** |
||||
* Create a 4 byte masking key |
||||
* @return string |
||||
*/ |
||||
public function generateMaskingKey() { |
||||
$mask = ''; |
||||
|
||||
for ($i = 1; $i <= static::MASK_LENGTH; $i++) { |
||||
$mask .= chr(rand(32, 126)); |
||||
} |
||||
|
||||
return $mask; |
||||
} |
||||
|
||||
/** |
||||
* Apply a mask to the payload |
||||
* @param string|null If NULL is passed a masking key will be generated |
||||
* @throws \OutOfBoundsException |
||||
* @throws \InvalidArgumentException If there is an issue with the given masking key |
||||
* @return Frame |
||||
*/ |
||||
public function maskPayload($maskingKey = null) { |
||||
if (null === $maskingKey) { |
||||
$maskingKey = $this->generateMaskingKey(); |
||||
} |
||||
|
||||
if (static::MASK_LENGTH !== strlen($maskingKey)) { |
||||
throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters"); |
||||
} |
||||
|
||||
if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) { |
||||
throw new \OutOfBoundsException("Masking key MUST be ASCII"); |
||||
} |
||||
|
||||
$this->unMaskPayload(); |
||||
|
||||
$this->secondByte = $this->secondByte | 128; |
||||
$this->data[1] = chr($this->secondByte); |
||||
|
||||
$this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0); |
||||
|
||||
$this->bytesRecvd += static::MASK_LENGTH; |
||||
$this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Remove a mask from the payload |
||||
* @throws \UnderFlowException If the frame is not coalesced |
||||
* @return Frame |
||||
*/ |
||||
public function unMaskPayload() { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException('Frame must be coalesced before applying mask'); |
||||
} |
||||
|
||||
if (!$this->isMasked()) { |
||||
return $this; |
||||
} |
||||
|
||||
$maskingKey = $this->getMaskingKey(); |
||||
|
||||
$this->secondByte = $this->secondByte & ~128; |
||||
$this->data[1] = chr($this->secondByte); |
||||
|
||||
$this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH); |
||||
|
||||
$this->bytesRecvd -= static::MASK_LENGTH; |
||||
$this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Apply a mask to a string or the payload of the instance |
||||
* @param string $maskingKey The 4 character masking key to be applied |
||||
* @param string|null $payload A string to mask or null to use the payload |
||||
* @throws \UnderflowException If using the payload but enough hasn't been buffered |
||||
* @return string The masked string |
||||
*/ |
||||
public function applyMask($maskingKey, $payload = null) { |
||||
if (null === $payload) { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException('Frame must be coalesced to apply a mask'); |
||||
} |
||||
|
||||
$payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); |
||||
} |
||||
|
||||
$applied = ''; |
||||
for ($i = 0, $len = strlen($payload); $i < $len; $i++) { |
||||
$applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH]; |
||||
} |
||||
|
||||
return $applied; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getOpcode() { |
||||
if (-1 === $this->firstByte) { |
||||
throw new \UnderflowException('Not enough bytes received to determine opcode'); |
||||
} |
||||
|
||||
return ($this->firstByte & ~240); |
||||
} |
||||
|
||||
/** |
||||
* Gets the decimal value of bits 9 (10th) through 15 inclusive |
||||
* @return int |
||||
* @throws \UnderflowException If the buffer doesn't have enough data to determine this |
||||
*/ |
||||
protected function getFirstPayloadVal() { |
||||
if (-1 === $this->secondByte) { |
||||
throw new \UnderflowException('Not enough bytes received'); |
||||
} |
||||
|
||||
return $this->secondByte & 127; |
||||
} |
||||
|
||||
/** |
||||
* @return int (7|23|71) Number of bits defined for the payload length in the fame |
||||
* @throws \UnderflowException |
||||
*/ |
||||
protected function getNumPayloadBits() { |
||||
if (-1 === $this->secondByte) { |
||||
throw new \UnderflowException('Not enough bytes received'); |
||||
} |
||||
|
||||
// By default 7 bits are used to describe the payload length |
||||
// These are bits 9 (10th) through 15 inclusive |
||||
$bits = 7; |
||||
|
||||
// Get the value of those bits |
||||
$check = $this->getFirstPayloadVal(); |
||||
|
||||
// If the value is 126 the 7 bits plus the next 16 are used to describe the payload length |
||||
if ($check >= 126) { |
||||
$bits += 16; |
||||
} |
||||
|
||||
// If the value of the initial payload length are is 127 an additional 48 bits are used to describe length |
||||
// Note: The documentation specifies the length is to be 63 bits, but I think that's a typo and is 64 (16+48) |
||||
if ($check === 127) { |
||||
$bits += 48; |
||||
} |
||||
|
||||
return $bits; |
||||
} |
||||
|
||||
/** |
||||
* This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits) |
||||
* @see getNumPayloadBits |
||||
*/ |
||||
protected function getNumPayloadBytes() { |
||||
return (1 + $this->getNumPayloadBits()) / 8; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayloadLength() { |
||||
if ($this->defPayLen !== -1) { |
||||
return $this->defPayLen; |
||||
} |
||||
|
||||
$this->defPayLen = $this->getFirstPayloadVal(); |
||||
if ($this->defPayLen <= 125) { |
||||
return $this->getPayloadLength(); |
||||
} |
||||
|
||||
$byte_length = $this->getNumPayloadBytes(); |
||||
if ($this->bytesRecvd < 1 + $byte_length) { |
||||
$this->defPayLen = -1; |
||||
throw new \UnderflowException('Not enough data buffered to determine payload length'); |
||||
} |
||||
|
||||
$len = 0; |
||||
for ($i = 2; $i <= $byte_length; $i++) { |
||||
$len <<= 8; |
||||
$len += ord($this->data[$i]); |
||||
} |
||||
|
||||
$this->defPayLen = $len; |
||||
|
||||
return $this->getPayloadLength(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayloadStartingByte() { |
||||
return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @todo Consider not checking mask, always returning the payload, masked or not |
||||
*/ |
||||
public function getPayload() { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException('Can not return partial message'); |
||||
} |
||||
|
||||
$payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); |
||||
|
||||
if ($this->isMasked()) { |
||||
$payload = $this->applyMask($this->getMaskingKey(), $payload); |
||||
} |
||||
|
||||
return $payload; |
||||
} |
||||
|
||||
/** |
||||
* Get the raw contents of the frame |
||||
* @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow |
||||
*/ |
||||
public function getContents() { |
||||
return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); |
||||
} |
||||
|
||||
/** |
||||
* Sometimes clients will concatenate more than one frame over the wire |
||||
* This method will take the extra bytes off the end and return them |
||||
* @todo Consider returning new Frame |
||||
* @return string |
||||
*/ |
||||
public function extractOverflow() { |
||||
if ($this->isCoalesced()) { |
||||
$endPoint = $this->getPayloadLength(); |
||||
$endPoint += $this->getPayloadStartingByte(); |
||||
|
||||
if ($this->bytesRecvd > $endPoint) { |
||||
$overflow = substr($this->data, $endPoint); |
||||
$this->data = substr($this->data, 0, $endPoint); |
||||
|
||||
return $overflow; |
||||
} |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
} |
@ -0,0 +1,137 @@
@@ -0,0 +1,137 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\RFC6455; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
|
||||
/** |
||||
* These are checks to ensure the client requested handshake are valid |
||||
* Verification rules come from section 4.2.1 of the RFC6455 document |
||||
* @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s |
||||
*/ |
||||
class HandshakeVerifier { |
||||
/** |
||||
* Given an array of the headers this method will run through all verification methods |
||||
* @param \Guzzle\Http\Message\RequestInterface $request |
||||
* @return bool TRUE if all headers are valid, FALSE if 1 or more were invalid |
||||
*/ |
||||
public function verifyAll(RequestInterface $request) { |
||||
$passes = 0; |
||||
|
||||
$passes += (int)$this->verifyMethod($request->getMethod()); |
||||
$passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); |
||||
$passes += (int)$this->verifyRequestURI($request->getPath()); |
||||
$passes += (int)$this->verifyHost((string)$request->getHeader('Host')); |
||||
$passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade')); |
||||
$passes += (int)$this->verifyConnection((string)$request->getHeader('Connection')); |
||||
$passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key')); |
||||
//$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality |
||||
|
||||
return (7 === $passes); |
||||
} |
||||
|
||||
/** |
||||
* Test the HTTP method. MUST be "GET" |
||||
* @param string |
||||
* @return bool |
||||
*/ |
||||
public function verifyMethod($val) { |
||||
return ('get' === strtolower($val)); |
||||
} |
||||
|
||||
/** |
||||
* Test the HTTP version passed. MUST be 1.1 or greater |
||||
* @param string|int |
||||
* @return bool |
||||
*/ |
||||
public function verifyHTTPVersion($val) { |
||||
return (1.1 <= (double)$val); |
||||
} |
||||
|
||||
/** |
||||
* @param string |
||||
* @return bool |
||||
*/ |
||||
public function verifyRequestURI($val) { |
||||
if ($val[0] != '/') { |
||||
return false; |
||||
} |
||||
|
||||
if (false !== strstr($val, '#')) { |
||||
return false; |
||||
} |
||||
|
||||
if (!extension_loaded('mbstring')) { |
||||
return true; |
||||
} |
||||
|
||||
return mb_check_encoding($val, 'US-ASCII'); |
||||
} |
||||
|
||||
/** |
||||
* @param string|null |
||||
* @return bool |
||||
* @todo Find out if I can find the master socket, ensure the port is attached to header if not 80 or 443 - not sure if this is possible, as I tried to hide it |
||||
* @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? |
||||
*/ |
||||
public function verifyHost($val) { |
||||
return (null !== $val); |
||||
} |
||||
|
||||
/** |
||||
* Verify the Upgrade request to WebSockets. |
||||
* @param string $val MUST equal "websocket" |
||||
* @return bool |
||||
*/ |
||||
public function verifyUpgradeRequest($val) { |
||||
return ('websocket' === strtolower($val)); |
||||
} |
||||
|
||||
/** |
||||
* Verify the Connection header |
||||
* @param string $val MUST equal "Upgrade" |
||||
* @return bool |
||||
*/ |
||||
public function verifyConnection($val) { |
||||
$val = strtolower($val); |
||||
|
||||
if ('upgrade' === $val) { |
||||
return true; |
||||
} |
||||
|
||||
$vals = explode(',', str_replace(', ', ',', $val)); |
||||
|
||||
return (false !== array_search('upgrade', $vals)); |
||||
} |
||||
|
||||
/** |
||||
* This function verifies the nonce is valid (64 big encoded, 16 bytes random string) |
||||
* @param string|null |
||||
* @return bool |
||||
* @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode? |
||||
* @todo Check the spec to see what the encoding of the key could be |
||||
*/ |
||||
public function verifyKey($val) { |
||||
return (16 === strlen(base64_decode((string)$val))); |
||||
} |
||||
|
||||
/** |
||||
* Verify the version passed matches this RFC |
||||
* @param string|int MUST equal 13|"13" |
||||
* @return bool |
||||
* @todo Ran in to a problem here...I'm having HyBi use the RFC files, this breaks it! oops |
||||
*/ |
||||
public function verifyVersion($val) { |
||||
return (13 === (int)$val); |
||||
} |
||||
|
||||
/** |
||||
* @todo Write logic for this method. See section 4.2.1.8 |
||||
*/ |
||||
public function verifyProtocol($val) { |
||||
} |
||||
|
||||
/** |
||||
* @todo Write logic for this method. See section 4.2.1.9 |
||||
*/ |
||||
public function verifyExtensions($val) { |
||||
} |
||||
} |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version\RFC6455; |
||||
use Ratchet\WebSocket\Version\MessageInterface; |
||||
use Ratchet\WebSocket\Version\FrameInterface; |
||||
|
||||
class Message implements MessageInterface, \Countable { |
||||
/** |
||||
* @var \SplDoublyLinkedList |
||||
*/ |
||||
protected $_frames; |
||||
|
||||
public function __construct() { |
||||
$this->_frames = new \SplDoublyLinkedList; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function count() { |
||||
return count($this->_frames); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function isCoalesced() { |
||||
if (count($this->_frames) == 0) { |
||||
return false; |
||||
} |
||||
|
||||
$last = $this->_frames->top(); |
||||
|
||||
return ($last->isCoalesced() && $last->isFinal()); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
* @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message |
||||
*/ |
||||
public function addFrame(FrameInterface $fragment) { |
||||
$this->_frames->push($fragment); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getOpcode() { |
||||
if (count($this->_frames) == 0) { |
||||
throw new \UnderflowException('No frames have been added to this message'); |
||||
} |
||||
|
||||
return $this->_frames->bottom()->getOpcode(); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayloadLength() { |
||||
$len = 0; |
||||
|
||||
foreach ($this->_frames as $frame) { |
||||
try { |
||||
$len += $frame->getPayloadLength(); |
||||
} catch (\UnderflowException $e) { |
||||
// Not an error, want the current amount buffered |
||||
} |
||||
} |
||||
|
||||
return $len; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getPayload() { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException('Message has not been put back together yet'); |
||||
} |
||||
|
||||
$buffer = ''; |
||||
|
||||
foreach ($this->_frames as $frame) { |
||||
$buffer .= $frame->getPayload(); |
||||
} |
||||
|
||||
return $buffer; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function getContents() { |
||||
if (!$this->isCoalesced()) { |
||||
throw new \UnderflowException("Message has not been put back together yet"); |
||||
} |
||||
|
||||
$buffer = ''; |
||||
|
||||
foreach ($this->_frames as $frame) { |
||||
$buffer .= $frame->getContents(); |
||||
} |
||||
|
||||
return $buffer; |
||||
} |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Version; |
||||
use Ratchet\MessageInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
|
||||
/** |
||||
* A standard interface for interacting with the various version of the WebSocket protocol |
||||
*/ |
||||
interface VersionInterface extends MessageInterface { |
||||
/** |
||||
* Given an HTTP header, determine if this version should handle the protocol |
||||
* @param \Guzzle\Http\Message\RequestInterface $request |
||||
* @return bool |
||||
* @throws \UnderflowException If the protocol thinks the headers are still fragmented |
||||
*/ |
||||
function isProtocol(RequestInterface $request); |
||||
|
||||
/** |
||||
* Although the version has a name associated with it the integer returned is the proper identification |
||||
* @return int |
||||
*/ |
||||
function getVersionNumber(); |
||||
|
||||
/** |
||||
* Perform the handshake and return the response headers |
||||
* @param \Guzzle\Http\Message\RequestInterface $request |
||||
* @return \Guzzle\Http\Message\Response |
||||
* @throws \UnderflowException If the message hasn't finished buffering (not yet implemented, theoretically will only happen with Hixie version) |
||||
*/ |
||||
function handshake(RequestInterface $request); |
||||
|
||||
/** |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param \Ratchet\MessageInterface $coalescedCallback |
||||
* @return \Ratchet\ConnectionInterface |
||||
*/ |
||||
function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback); |
||||
|
||||
/** |
||||
* @return MessageInterface |
||||
*/ |
||||
//function newMessage(); |
||||
|
||||
/** |
||||
* @return FrameInterface |
||||
*/ |
||||
//function newFrame(); |
||||
|
||||
/** |
||||
* @param string |
||||
* @param bool |
||||
* @return string |
||||
* @todo Change to use other classes, this will be removed eventually |
||||
*/ |
||||
//function frame($message, $mask = true); |
||||
} |
@ -0,0 +1,90 @@
@@ -0,0 +1,90 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket; |
||||
use Ratchet\WebSocket\Version\VersionInterface; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
|
||||
/** |
||||
* Manage the various versions of the WebSocket protocol |
||||
* This accepts interfaces of versions to enable/disable |
||||
*/ |
||||
class VersionManager { |
||||
/** |
||||
* The header string to let clients know which versions are supported |
||||
* @var string |
||||
*/ |
||||
private $versionString = ''; |
||||
|
||||
/** |
||||
* Storage of each version enabled |
||||
* @var array |
||||
*/ |
||||
protected $versions = array(); |
||||
|
||||
/** |
||||
* Get the protocol negotiator for the request, if supported |
||||
* @param \Guzzle\Http\Message\RequestInterface $request |
||||
* @throws \InvalidArgumentException |
||||
* @return \Ratchet\WebSocket\Version\VersionInterface |
||||
*/ |
||||
public function getVersion(RequestInterface $request) { |
||||
foreach ($this->versions as $version) { |
||||
if ($version->isProtocol($request)) { |
||||
return $version; |
||||
} |
||||
} |
||||
|
||||
throw new \InvalidArgumentException("Version not found"); |
||||
} |
||||
|
||||
/** |
||||
* @param \Guzzle\Http\Message\RequestInterface |
||||
* @return bool |
||||
*/ |
||||
public function isVersionEnabled(RequestInterface $request) { |
||||
foreach ($this->versions as $version) { |
||||
if ($version->isProtocol($request)) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Enable support for a specific version of the WebSocket protocol |
||||
* @param \Ratchet\WebSocket\Version\VersionInterface $version |
||||
* @return VersionManager |
||||
*/ |
||||
public function enableVersion(VersionInterface $version) { |
||||
$this->versions[$version->getVersionNumber()] = $version; |
||||
|
||||
if (empty($this->versionString)) { |
||||
$this->versionString = (string)$version->getVersionNumber(); |
||||
} else { |
||||
$this->versionString .= ", {$version->getVersionNumber()}"; |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Disable support for a specific WebSocket protocol version |
||||
* @param int $versionId The version ID to un-support |
||||
* @return VersionManager |
||||
*/ |
||||
public function disableVersion($versionId) { |
||||
unset($this->versions[$versionId]); |
||||
|
||||
$this->versionString = implode(',', array_keys($this->versions)); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Get a string of version numbers supported (comma delimited) |
||||
* @return string |
||||
*/ |
||||
public function getSupportedVersionString() { |
||||
return $this->versionString; |
||||
} |
||||
} |
@ -0,0 +1,232 @@
@@ -0,0 +1,232 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\Http\HttpServerInterface; |
||||
use Guzzle\Http\Message\RequestInterface; |
||||
use Guzzle\Http\Message\Response; |
||||
use Ratchet\WebSocket\Version; |
||||
use Ratchet\WebSocket\Encoding\ToggleableValidator; |
||||
|
||||
/** |
||||
* The adapter to handle WebSocket requests/responses |
||||
* This is a mediator between the Server and your application to handle real-time messaging through a web browser |
||||
* @link http://ca.php.net/manual/en/ref.http.php |
||||
* @link http://dev.w3.org/html5/websockets/ |
||||
*/ |
||||
class WsServer implements HttpServerInterface { |
||||
/** |
||||
* Manage the various WebSocket versions to support |
||||
* @var VersionManager |
||||
* @note May not expose this in the future, may do through facade methods |
||||
*/ |
||||
public $versioner; |
||||
|
||||
/** |
||||
* Decorated component |
||||
* @var \Ratchet\MessageComponentInterface |
||||
*/ |
||||
public $component; |
||||
|
||||
/** |
||||
* @var \SplObjectStorage |
||||
*/ |
||||
protected $connections; |
||||
|
||||
/** |
||||
* Holder of accepted protocols, implement through WampServerInterface |
||||
*/ |
||||
protected $acceptedSubProtocols = array(); |
||||
|
||||
/** |
||||
* UTF-8 validator |
||||
* @var \Ratchet\WebSocket\Encoding\ValidatorInterface |
||||
*/ |
||||
protected $validator; |
||||
|
||||
/** |
||||
* Flag if we have checked the decorated component for sub-protocols |
||||
* @var boolean |
||||
*/ |
||||
private $isSpGenerated = false; |
||||
|
||||
/** |
||||
* @param \Ratchet\MessageComponentInterface $component Your application to run with WebSockets |
||||
* If you want to enable sub-protocols have your component implement WsServerInterface as well |
||||
*/ |
||||
public function __construct(MessageComponentInterface $component) { |
||||
$this->versioner = new VersionManager; |
||||
$this->validator = new ToggleableValidator; |
||||
|
||||
$this->versioner |
||||
->enableVersion(new Version\RFC6455($this->validator)) |
||||
->enableVersion(new Version\HyBi10($this->validator)) |
||||
->enableVersion(new Version\Hixie76) |
||||
; |
||||
|
||||
$this->component = $component; |
||||
$this->connections = new \SplObjectStorage; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { |
||||
if (null === $request) { |
||||
throw new \UnexpectedValueException('$request can not be null'); |
||||
} |
||||
|
||||
$conn->WebSocket = new \StdClass; |
||||
$conn->WebSocket->request = $request; |
||||
$conn->WebSocket->established = false; |
||||
$conn->WebSocket->closing = false; |
||||
|
||||
$this->attemptUpgrade($conn); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
if ($from->WebSocket->closing) { |
||||
return; |
||||
} |
||||
|
||||
if (true === $from->WebSocket->established) { |
||||
return $from->WebSocket->version->onMessage($this->connections[$from], $msg); |
||||
} |
||||
|
||||
$this->attemptUpgrade($from, $msg); |
||||
} |
||||
|
||||
protected function attemptUpgrade(ConnectionInterface $conn, $data = '') { |
||||
if ('' !== $data) { |
||||
$conn->WebSocket->request->getBody()->write($data); |
||||
} |
||||
|
||||
if (!$this->versioner->isVersionEnabled($conn->WebSocket->request)) { |
||||
return $this->close($conn); |
||||
} |
||||
|
||||
$conn->WebSocket->version = $this->versioner->getVersion($conn->WebSocket->request); |
||||
|
||||
try { |
||||
$response = $conn->WebSocket->version->handshake($conn->WebSocket->request); |
||||
} catch (\UnderflowException $e) { |
||||
return; |
||||
} |
||||
|
||||
if (null !== ($subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) { |
||||
if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader->normalize()))) { |
||||
$response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); |
||||
} |
||||
} |
||||
|
||||
$response->setHeader('X-Powered-By', \Ratchet\VERSION); |
||||
$conn->send((string)$response); |
||||
|
||||
if (101 != $response->getStatusCode()) { |
||||
return $conn->close(); |
||||
} |
||||
|
||||
$upgraded = $conn->WebSocket->version->upgradeConnection($conn, $this->component); |
||||
|
||||
$this->connections->attach($conn, $upgraded); |
||||
|
||||
$upgraded->WebSocket->established = true; |
||||
|
||||
return $this->component->onOpen($upgraded); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onClose(ConnectionInterface $conn) { |
||||
if ($this->connections->contains($conn)) { |
||||
$decor = $this->connections[$conn]; |
||||
$this->connections->detach($conn); |
||||
|
||||
$this->component->onClose($decor); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
if ($conn->WebSocket->established && $this->connections->contains($conn)) { |
||||
$this->component->onError($this->connections[$conn], $e); |
||||
} else { |
||||
$conn->close(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Disable a specific version of the WebSocket protocol |
||||
* @param int $versionId Version ID to disable |
||||
* @return WsServer |
||||
*/ |
||||
public function disableVersion($versionId) { |
||||
$this->versioner->disableVersion($versionId); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Toggle weather to check encoding of incoming messages |
||||
* @param bool |
||||
* @return WsServer |
||||
*/ |
||||
public function setEncodingChecks($opt) { |
||||
$this->validator->on = (boolean)$opt; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param string |
||||
* @return boolean |
||||
*/ |
||||
public function isSubProtocolSupported($name) { |
||||
if (!$this->isSpGenerated) { |
||||
if ($this->component instanceof WsServerInterface) { |
||||
$this->acceptedSubProtocols = array_flip($this->component->getSubProtocols()); |
||||
} |
||||
|
||||
$this->isSpGenerated = true; |
||||
} |
||||
|
||||
return array_key_exists($name, $this->acceptedSubProtocols); |
||||
} |
||||
|
||||
/** |
||||
* @param \Traversable|null $requested |
||||
* @return string |
||||
*/ |
||||
protected function getSubProtocolString(\Traversable $requested = null) { |
||||
if (null !== $requested) { |
||||
foreach ($requested as $sub) { |
||||
if ($this->isSubProtocolSupported($sub)) { |
||||
return $sub; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
|
||||
/** |
||||
* Close a connection with an HTTP response |
||||
* @param \Ratchet\ConnectionInterface $conn |
||||
* @param int $code HTTP status code |
||||
*/ |
||||
protected function close(ConnectionInterface $conn, $code = 400) { |
||||
$response = new Response($code, array( |
||||
'Sec-WebSocket-Version' => $this->versioner->getSupportedVersionString() |
||||
, 'X-Powered-By' => \Ratchet\VERSION |
||||
)); |
||||
|
||||
$conn->send((string)$response); |
||||
$conn->close(); |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket; |
||||
|
||||
/** |
||||
* WebSocket Server Interface |
||||
*/ |
||||
interface WsServerInterface { |
||||
/** |
||||
* If any component in a stack supports a WebSocket sub-protocol return each supported in an array |
||||
* @return array |
||||
* @todo This method may be removed in future version (note that will not break code, just make some code obsolete) |
||||
*/ |
||||
function getSubProtocols(); |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
<?php |
||||
|
||||
require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; |
||||
|
||||
$port = $argc > 1 ? $argv[1] : 8000; |
||||
$impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); |
||||
|
||||
$loop = new $impl; |
||||
$sock = new React\Socket\Server($loop); |
||||
$web = new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer); |
||||
$app = new Ratchet\Http\HttpServer($web); |
||||
$web->setEncodingChecks(false); |
||||
|
||||
$sock->listen($port, '0.0.0.0'); |
||||
|
||||
$server = new Ratchet\Server\IoServer($app, $sock, $loop); |
||||
$server->run(); |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
<?php |
||||
|
||||
require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; |
||||
|
||||
$port = $argc > 1 ? $argv[1] : 8000; |
||||
$impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); |
||||
|
||||
$loop = new $impl; |
||||
$sock = new React\Socket\Server($loop); |
||||
$app = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); |
||||
|
||||
$sock->listen($port, '0.0.0.0'); |
||||
|
||||
$server = new Ratchet\Server\IoServer($app, $sock, $loop); |
||||
$server->run(); |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
{ |
||||
"options": {"failByDrop": false} |
||||
, "outdir": "reports/ab" |
||||
|
||||
, "servers": [ |
||||
{"agent": "Ratchet/0.3 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} |
||||
, {"agent": "Ratchet/0.3 libev", "url": "ws://localhost:8004", "options": {"version": 18}} |
||||
, {"agent": "Ratchet/0.3 streams", "url": "ws://localhost:8002", "options": {"version": 18}} |
||||
, {"agent": "Ratchet/0.3 -utf8", "url": "ws://localhost:8003", "options": {"version": 18}} |
||||
, {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}} |
||||
] |
||||
|
||||
, "cases": ["*"] |
||||
, "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] |
||||
, "exclude-agent-cases": {} |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
{ |
||||
"options": {"failByDrop": false} |
||||
, "outdir": "reports/profile" |
||||
|
||||
, "servers": [ |
||||
{"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}} |
||||
] |
||||
|
||||
, "cases": ["9.7.4"] |
||||
, "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] |
||||
, "exclude-agent-cases": {} |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
{ |
||||
"options": {"failByDrop": false} |
||||
, "outdir": "reports/rfc" |
||||
|
||||
, "servers": [ |
||||
{"agent": "Ratchet", "url": "ws://localhost:8000", "options": {"version": 18}} |
||||
] |
||||
|
||||
, "cases": ["*"] |
||||
, "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] |
||||
, "exclude-agent-cases": {} |
||||
} |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?php |
||||
|
||||
$loader = require __DIR__ . '/../vendor/autoload.php'; |
||||
$loader->addPsr4('Ratchet\\', __DIR__ . '/helpers/Ratchet'); |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
|
||||
abstract class AbstractMessageComponentTestCase extends \PHPUnit_Framework_TestCase { |
||||
protected $_app; |
||||
protected $_serv; |
||||
protected $_conn; |
||||
|
||||
abstract public function getConnectionClassString(); |
||||
abstract public function getDecoratorClassString(); |
||||
abstract public function getComponentClassString(); |
||||
|
||||
public function setUp() { |
||||
$this->_app = $this->getMock($this->getComponentClassString()); |
||||
$decorator = $this->getDecoratorClassString(); |
||||
$this->_serv = new $decorator($this->_app); |
||||
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
|
||||
$this->doOpen($this->_conn); |
||||
} |
||||
|
||||
protected function doOpen($conn) { |
||||
$this->_serv->onOpen($conn); |
||||
} |
||||
|
||||
public function isExpectedConnection() { |
||||
return new \PHPUnit_Framework_Constraint_IsInstanceOf($this->getConnectionClassString()); |
||||
} |
||||
|
||||
public function testOpen() { |
||||
$this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); |
||||
$this->doOpen($this->getMock('\Ratchet\ConnectionInterface')); |
||||
} |
||||
|
||||
public function testOnClose() { |
||||
$this->_app->expects($this->once())->method('onClose')->with($this->isExpectedConnection()); |
||||
$this->_serv->onClose($this->_conn); |
||||
} |
||||
|
||||
public function testOnError() { |
||||
$e = new \Exception('Whoops!'); |
||||
$this->_app->expects($this->once())->method('onError')->with($this->isExpectedConnection(), $e); |
||||
$this->_serv->onError($this->_conn, $e); |
||||
} |
||||
|
||||
public function passthroughMessageTest($value) { |
||||
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $value); |
||||
$this->_serv->onMessage($this->_conn, $value); |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
namespace Ratchet\Mock; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
class Component implements MessageComponentInterface, WsServerInterface { |
||||
public $last = array(); |
||||
|
||||
public $protocols = array(); |
||||
|
||||
public function __construct(ComponentInterface $app = null) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onMessage(ConnectionInterface $from, $msg) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onClose(ConnectionInterface $conn) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function getSubProtocols() { |
||||
return $this->protocols; |
||||
} |
||||
} |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
<?php |
||||
namespace Ratchet\Mock; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
class Connection implements ConnectionInterface { |
||||
public $last = array( |
||||
'send' => '' |
||||
, 'close' => false |
||||
); |
||||
|
||||
public $remoteAddress = '127.0.0.1'; |
||||
|
||||
public function send($data) { |
||||
$this->last[__FUNCTION__] = $data; |
||||
} |
||||
|
||||
public function close() { |
||||
$this->last[__FUNCTION__] = true; |
||||
} |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<?php |
||||
namespace Ratchet\Mock; |
||||
use Ratchet\AbstractConnectionDecorator; |
||||
|
||||
class ConnectionDecorator extends AbstractConnectionDecorator { |
||||
public $last = array( |
||||
'write' => '' |
||||
, 'end' => false |
||||
); |
||||
|
||||
public function send($data) { |
||||
$this->last[__FUNCTION__] = $data; |
||||
|
||||
$this->getConnection()->send($data); |
||||
} |
||||
|
||||
public function close() { |
||||
$this->last[__FUNCTION__] = true; |
||||
|
||||
$this->getConnection()->close(); |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<?php |
||||
namespace Ratchet\Mock; |
||||
use Ratchet\Wamp\WampServerInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\ConnectionInterface; |
||||
|
||||
class WampComponent implements WampServerInterface, WsServerInterface { |
||||
public $last = array(); |
||||
|
||||
public $protocols = array(); |
||||
|
||||
public function getSubProtocols() { |
||||
return $this->protocols; |
||||
} |
||||
|
||||
public function onCall(ConnectionInterface $conn, $id, $procURI, array $params) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onSubscribe(ConnectionInterface $conn, $topic) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onUnSubscribe(ConnectionInterface $conn, $topic) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onOpen(ConnectionInterface $conn) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onClose(ConnectionInterface $conn) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
|
||||
public function onError(ConnectionInterface $conn, \Exception $e) { |
||||
$this->last[__FUNCTION__] = func_get_args(); |
||||
} |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
use Ratchet\ConnectionInterface; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\Wamp\WampServerInterface; |
||||
|
||||
class NullComponent implements MessageComponentInterface, WsServerInterface, WampServerInterface { |
||||
public function onOpen(ConnectionInterface $conn) {} |
||||
|
||||
public function onMessage(ConnectionInterface $conn, $msg) {} |
||||
|
||||
public function onClose(ConnectionInterface $conn) {} |
||||
|
||||
public function onError(ConnectionInterface $conn, \Exception $e) {} |
||||
|
||||
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {} |
||||
|
||||
public function onSubscribe(ConnectionInterface $conn, $topic) {} |
||||
|
||||
public function onUnSubscribe(ConnectionInterface $conn, $topic) {} |
||||
|
||||
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude = array(), array $eligible = array()) {} |
||||
|
||||
public function getSubProtocols() { |
||||
return array(); |
||||
} |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<?php |
||||
namespace Ratchet\Wamp\Stub; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Ratchet\Wamp\WampServerInterface; |
||||
|
||||
interface WsWampServerInterface extends WsServerInterface, WampServerInterface { |
||||
} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
<?php |
||||
namespace Ratchet\WebSocket\Stub; |
||||
use Ratchet\MessageComponentInterface; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
|
||||
interface WsMessageComponentInterface extends MessageComponentInterface, WsServerInterface { |
||||
} |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
<?php |
||||
use Guzzle\Http\Message\Request; |
||||
|
||||
class GuzzleTest extends \PHPUnit_Framework_TestCase { |
||||
protected $_request; |
||||
|
||||
protected $_headers = array( |
||||
'Upgrade' => 'websocket' |
||||
, 'Connection' => 'Upgrade' |
||||
, 'Host' => 'localhost:8080' |
||||
, 'Origin' => 'chrome://newtab' |
||||
, 'Sec-WebSocket-Protocol' => 'one, two, three' |
||||
, 'Sec-WebSocket-Key' => '9bnXNp3ae6FbFFRtPdiPXA==' |
||||
, 'Sec-WebSocket-Version' => '13' |
||||
); |
||||
|
||||
public function setUp() { |
||||
$this->_request = new Request('GET', 'http://localhost', $this->_headers); |
||||
} |
||||
|
||||
public function testGetHeaderString() { |
||||
$this->assertEquals('Upgrade', (string)$this->_request->getHeader('connection')); |
||||
$this->assertEquals('9bnXNp3ae6FbFFRtPdiPXA==', (string)$this->_request->getHeader('Sec-Websocket-Key')); |
||||
} |
||||
|
||||
public function testGetHeaderInteger() { |
||||
$this->assertSame('13', (string)$this->_request->getHeader('Sec-Websocket-Version')); |
||||
$this->assertSame(13, (int)(string)$this->_request->getHeader('Sec-WebSocket-Version')); |
||||
} |
||||
|
||||
public function testGetHeaderObject() { |
||||
$this->assertInstanceOf('Guzzle\Http\Message\Header', $this->_request->getHeader('Origin')); |
||||
$this->assertNull($this->_request->getHeader('Non-existant-header')); |
||||
} |
||||
|
||||
public function testHeaderObjectNormalizeValues() { |
||||
$expected = 1 + substr_count($this->_headers['Sec-WebSocket-Protocol'], ','); |
||||
$protocols = $this->_request->getHeader('Sec-WebSocket-Protocol')->normalize(); |
||||
$count = 0; |
||||
|
||||
foreach ($protocols as $protocol) { |
||||
$count++; |
||||
} |
||||
|
||||
$this->assertEquals($expected, $count); |
||||
$this->assertEquals($expected, count($protocols)); |
||||
} |
||||
|
||||
public function testRequestFactoryCreateSignature() { |
||||
$ref = new \ReflectionMethod('Guzzle\Http\Message\RequestFactory', 'create'); |
||||
$this->assertEquals(2, $ref->getNumberOfRequiredParameters()); |
||||
} |
||||
} |
@ -0,0 +1,147 @@
@@ -0,0 +1,147 @@
|
||||
<?php |
||||
namespace Ratchet; |
||||
use Ratchet\Mock\ConnectionDecorator; |
||||
|
||||
/** |
||||
* @covers Ratchet\AbstractConnectionDecorator |
||||
* @covers Ratchet\ConnectionInterface |
||||
*/ |
||||
class AbstractConnectionDecoratorTest extends \PHPUnit_Framework_TestCase { |
||||
protected $mock; |
||||
protected $l1; |
||||
protected $l2; |
||||
|
||||
public function setUp() { |
||||
$this->mock = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$this->l1 = new ConnectionDecorator($this->mock); |
||||
$this->l2 = new ConnectionDecorator($this->l1); |
||||
} |
||||
|
||||
public function testGet() { |
||||
$var = 'hello'; |
||||
$val = 'world'; |
||||
|
||||
$this->mock->$var = $val; |
||||
|
||||
$this->assertEquals($val, $this->l1->$var); |
||||
$this->assertEquals($val, $this->l2->$var); |
||||
} |
||||
|
||||
public function testSet() { |
||||
$var = 'Chris'; |
||||
$val = 'Boden'; |
||||
|
||||
$this->l1->$var = $val; |
||||
|
||||
$this->assertEquals($val, $this->mock->$var); |
||||
} |
||||
|
||||
public function testSetLevel2() { |
||||
$var = 'Try'; |
||||
$val = 'Again'; |
||||
|
||||
$this->l2->$var = $val; |
||||
|
||||
$this->assertEquals($val, $this->mock->$var); |
||||
} |
||||
|
||||
public function testIsSetTrue() { |
||||
$var = 'PHP'; |
||||
$val = 'Ratchet'; |
||||
|
||||
$this->mock->$var = $val; |
||||
|
||||
$this->assertTrue(isset($this->l1->$var)); |
||||
$this->assertTrue(isset($this->l2->$var)); |
||||
} |
||||
|
||||
public function testIsSetFalse() { |
||||
$var = 'herp'; |
||||
$val = 'derp'; |
||||
|
||||
$this->assertFalse(isset($this->l1->$var)); |
||||
$this->assertFalse(isset($this->l2->$var)); |
||||
} |
||||
|
||||
public function testUnset() { |
||||
$var = 'Flying'; |
||||
$val = 'Monkey'; |
||||
|
||||
$this->mock->$var = $val; |
||||
unset($this->l1->$var); |
||||
|
||||
$this->assertFalse(isset($this->mock->$var)); |
||||
} |
||||
|
||||
public function testUnsetLevel2() { |
||||
$var = 'Flying'; |
||||
$val = 'Monkey'; |
||||
|
||||
$this->mock->$var = $val; |
||||
unset($this->l2->$var); |
||||
|
||||
$this->assertFalse(isset($this->mock->$var)); |
||||
} |
||||
|
||||
public function testGetConnection() { |
||||
$class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator'); |
||||
$method = $class->getMethod('getConnection'); |
||||
$method->setAccessible(true); |
||||
|
||||
$conn = $method->invokeArgs($this->l1, array()); |
||||
|
||||
$this->assertSame($this->mock, $conn); |
||||
} |
||||
|
||||
public function testGetConnectionLevel2() { |
||||
$class = new \ReflectionClass('\\Ratchet\\AbstractConnectionDecorator'); |
||||
$method = $class->getMethod('getConnection'); |
||||
$method->setAccessible(true); |
||||
|
||||
$conn = $method->invokeArgs($this->l2, array()); |
||||
|
||||
$this->assertSame($this->l1, $conn); |
||||
} |
||||
|
||||
public function testWrapperCanStoreSelfInDecorator() { |
||||
$this->mock->decorator = $this->l1; |
||||
|
||||
$this->assertSame($this->l1, $this->l2->decorator); |
||||
} |
||||
|
||||
public function testDecoratorRecursion() { |
||||
$this->mock->decorator = new \stdClass; |
||||
$this->mock->decorator->conn = $this->l1; |
||||
|
||||
$this->assertSame($this->l1, $this->mock->decorator->conn); |
||||
$this->assertSame($this->l1, $this->l1->decorator->conn); |
||||
$this->assertSame($this->l1, $this->l2->decorator->conn); |
||||
} |
||||
|
||||
public function testDecoratorRecursionLevel2() { |
||||
$this->mock->decorator = new \stdClass; |
||||
$this->mock->decorator->conn = $this->l2; |
||||
|
||||
$this->assertSame($this->l2, $this->mock->decorator->conn); |
||||
$this->assertSame($this->l2, $this->l1->decorator->conn); |
||||
$this->assertSame($this->l2, $this->l2->decorator->conn); |
||||
|
||||
// just for fun |
||||
$this->assertSame($this->l2, $this->l2->decorator->conn->decorator->conn->decorator->conn); |
||||
} |
||||
|
||||
public function testWarningGettingNothing() { |
||||
$this->setExpectedException('PHPUnit_Framework_Error'); |
||||
$var = $this->mock->nonExistant; |
||||
} |
||||
|
||||
public function testWarningGettingNothingLevel1() { |
||||
$this->setExpectedException('PHPUnit_Framework_Error'); |
||||
$var = $this->l1->nonExistant; |
||||
} |
||||
|
||||
public function testWarningGettingNothingLevel2() { |
||||
$this->setExpectedException('PHPUnit_Framework_Error'); |
||||
$var = $this->l2->nonExistant; |
||||
} |
||||
} |
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
<?php |
||||
namespace Ratchet\Http\Guzzle\Http\Message; |
||||
use Ratchet\Http\Guzzle\Http\Message\RequestFactory; |
||||
|
||||
/** |
||||
* @covers Ratchet\Http\Guzzle\Http\Message\RequestFactory |
||||
*/ |
||||
class RequestFactoryTest extends \PHPUnit_Framework_TestCase { |
||||
protected $factory; |
||||
|
||||
public function setUp() { |
||||
$this->factory = RequestFactory::getInstance(); |
||||
} |
||||
|
||||
public function testMessageProvider() { |
||||
return array( |
||||
'status' => 'GET / HTTP/1.1' |
||||
, 'headers' => array( |
||||
'Upgrade' => 'WebSocket' |
||||
, 'Connection' => 'Upgrade' |
||||
, 'Host' => 'localhost:8000' |
||||
, 'Sec-WebSocket-Key1' => '> b3lU Z0 fh f 3+83394 6 (zG4' |
||||
, 'Sec-WebSocket-Key2' => ',3Z0X0677 dV-d [159 Z*4' |
||||
) |
||||
, 'body' => "123456\r\n\r\n" |
||||
); |
||||
} |
||||
|
||||
public function combineMessage($status, array $headers, $body = '') { |
||||
$message = $status . "\r\n"; |
||||
|
||||
foreach ($headers as $key => $val) { |
||||
$message .= "{$key}: {$val}\r\n"; |
||||
} |
||||
|
||||
$message .= "\r\n{$body}"; |
||||
|
||||
return $message; |
||||
} |
||||
|
||||
public function testExpectedDataFromGuzzleHeaders() { |
||||
$parts = $this->testMessageProvider(); |
||||
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); |
||||
$object = $this->factory->fromMessage($message); |
||||
|
||||
foreach ($parts['headers'] as $key => $val) { |
||||
$this->assertEquals($val, $object->getHeader($key, true)); |
||||
} |
||||
} |
||||
|
||||
public function testExpectedDataFromNonGuzzleHeaders() { |
||||
$parts = $this->testMessageProvider(); |
||||
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); |
||||
$object = $this->factory->fromMessage($message); |
||||
|
||||
$this->assertNull($object->getHeader('Nope', true)); |
||||
$this->assertNull($object->getHeader('Nope')); |
||||
} |
||||
|
||||
public function testExpectedDataFromNonGuzzleBody() { |
||||
$parts = $this->testMessageProvider(); |
||||
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); |
||||
$object = $this->factory->fromMessage($message); |
||||
|
||||
$this->assertEquals($parts['body'], (string)$object->getBody()); |
||||
} |
||||
} |
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\Http\HttpRequestParser; |
||||
|
||||
/** |
||||
* @covers Ratchet\Http\HttpRequestParser |
||||
*/ |
||||
class HttpRequestParserTest extends \PHPUnit_Framework_TestCase { |
||||
protected $parser; |
||||
|
||||
public function setUp() { |
||||
$this->parser = new HttpRequestParser; |
||||
} |
||||
|
||||
public function headersProvider() { |
||||
return array( |
||||
array(false, "GET / HTTP/1.1\r\nHost: socketo.me\r\n") |
||||
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n") |
||||
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n1") |
||||
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖") |
||||
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie✖\r\n\r\n") |
||||
, array(true, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\nHixie\r\n") |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider headersProvider |
||||
*/ |
||||
public function testIsEom($expected, $message) { |
||||
$this->assertEquals($expected, $this->parser->isEom($message)); |
||||
} |
||||
|
||||
public function testBufferOverflowResponse() { |
||||
$conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
|
||||
$this->parser->maxSize = 20; |
||||
|
||||
$this->assertNull($this->parser->onMessage($conn, "GET / HTTP/1.1\r\n")); |
||||
|
||||
$this->setExpectedException('OverflowException'); |
||||
|
||||
$this->parser->onMessage($conn, "Header-Is: Too Big"); |
||||
} |
||||
|
||||
public function testReturnTypeIsRequest() { |
||||
$conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); |
||||
|
||||
$this->assertInstanceOf('\Guzzle\Http\Message\RequestInterface', $return); |
||||
} |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\AbstractMessageComponentTestCase; |
||||
|
||||
/** |
||||
* @covers Ratchet\Http\HttpServer |
||||
*/ |
||||
class HttpServerTest extends AbstractMessageComponentTestCase { |
||||
public function setUp() { |
||||
parent::setUp(); |
||||
$this->_conn->httpHeadersReceived = true; |
||||
} |
||||
|
||||
public function getConnectionClassString() { |
||||
return '\Ratchet\ConnectionInterface'; |
||||
} |
||||
|
||||
public function getDecoratorClassString() { |
||||
return '\Ratchet\Http\HttpServer'; |
||||
} |
||||
|
||||
public function getComponentClassString() { |
||||
return '\Ratchet\Http\HttpServerInterface'; |
||||
} |
||||
|
||||
public function testOpen() { |
||||
$headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; |
||||
|
||||
$this->_conn->httpHeadersReceived = false; |
||||
$this->_app->expects($this->once())->method('onOpen')->with($this->isExpectedConnection()); |
||||
$this->_serv->onMessage($this->_conn, $headers); |
||||
} |
||||
|
||||
public function testOnMessageAfterHeaders() { |
||||
$headers = "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"; |
||||
$this->_conn->httpHeadersReceived = false; |
||||
$this->_serv->onMessage($this->_conn, $headers); |
||||
|
||||
$message = "Hello World!"; |
||||
$this->_app->expects($this->once())->method('onMessage')->with($this->isExpectedConnection(), $message); |
||||
$this->_serv->onMessage($this->_conn, $message); |
||||
} |
||||
|
||||
public function testBufferOverflow() { |
||||
$this->_conn->expects($this->once())->method('close'); |
||||
$this->_conn->httpHeadersReceived = false; |
||||
|
||||
$this->_serv->onMessage($this->_conn, str_repeat('a', 5000)); |
||||
} |
||||
|
||||
public function testCloseIfNotEstablished() { |
||||
$this->_conn->httpHeadersReceived = false; |
||||
$this->_conn->expects($this->once())->method('close'); |
||||
$this->_serv->onError($this->_conn, new \Exception('Whoops!')); |
||||
} |
||||
|
||||
public function testBufferHeaders() { |
||||
$this->_conn->httpHeadersReceived = false; |
||||
$this->_app->expects($this->never())->method('onOpen'); |
||||
$this->_app->expects($this->never())->method('onMessage'); |
||||
|
||||
$this->_serv->onMessage($this->_conn, "GET / HTTP/1.1"); |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\AbstractMessageComponentTestCase; |
||||
|
||||
/** |
||||
* @covers Ratchet\Http\OriginCheck |
||||
*/ |
||||
class OriginCheckTest extends AbstractMessageComponentTestCase { |
||||
protected $_reqStub; |
||||
|
||||
public function setUp() { |
||||
$this->_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface'); |
||||
$this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost')); |
||||
|
||||
parent::setUp(); |
||||
|
||||
$this->_serv->allowedOrigins[] = 'localhost'; |
||||
} |
||||
|
||||
protected function doOpen($conn) { |
||||
$this->_serv->onOpen($conn, $this->_reqStub); |
||||
} |
||||
|
||||
public function getConnectionClassString() { |
||||
return '\Ratchet\ConnectionInterface'; |
||||
} |
||||
|
||||
public function getDecoratorClassString() { |
||||
return '\Ratchet\Http\OriginCheck'; |
||||
} |
||||
|
||||
public function getComponentClassString() { |
||||
return '\Ratchet\Http\HttpServerInterface'; |
||||
} |
||||
|
||||
public function testCloseOnNonMatchingOrigin() { |
||||
$this->_serv->allowedOrigins = array('socketo.me'); |
||||
$this->_conn->expects($this->once())->method('close'); |
||||
|
||||
$this->_serv->onOpen($this->_conn, $this->_reqStub); |
||||
} |
||||
|
||||
public function testOnMessage() { |
||||
$this->passthroughMessageTest('Hello World!'); |
||||
} |
||||
} |
@ -0,0 +1,140 @@
@@ -0,0 +1,140 @@
|
||||
<?php |
||||
namespace Ratchet\Http; |
||||
use Ratchet\Http\Router; |
||||
use Ratchet\WebSocket\WsServerInterface; |
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException; |
||||
use Symfony\Component\Routing\Matcher\UrlMatcherInterface; |
||||
|
||||
/** |
||||
* @covers Ratchet\Http\Router |
||||
*/ |
||||
class RouterTest extends \PHPUnit_Framework_TestCase { |
||||
protected $_router; |
||||
protected $_matcher; |
||||
protected $_conn; |
||||
protected $_req; |
||||
|
||||
public function setUp() { |
||||
$queryMock = $this->getMock('Guzzle\Http\QueryString'); |
||||
$queryMock |
||||
->expects($this->any()) |
||||
->method('getAll') |
||||
->will($this->returnValue(array())); |
||||
|
||||
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$this->_req = $this->getMock('\Guzzle\Http\Message\RequestInterface'); |
||||
$this->_req |
||||
->expects($this->any()) |
||||
->method('getQuery') |
||||
->will($this->returnValue($queryMock)); |
||||
$this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); |
||||
$this->_matcher |
||||
->expects($this->any()) |
||||
->method('getContext') |
||||
->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))); |
||||
$this->_router = new Router($this->_matcher); |
||||
|
||||
$this->_req->expects($this->any())->method('getPath')->will($this->returnValue('/whatever')); |
||||
} |
||||
|
||||
public function testFourOhFour() { |
||||
$this->_conn->expects($this->once())->method('close'); |
||||
|
||||
$nope = new ResourceNotFoundException; |
||||
$this->_matcher->expects($this->any())->method('match')->will($this->throwException($nope)); |
||||
|
||||
$this->_router->onOpen($this->_conn, $this->_req); |
||||
} |
||||
|
||||
public function testNullRequest() { |
||||
$this->setExpectedException('\UnexpectedValueException'); |
||||
$this->_router->onOpen($this->_conn); |
||||
} |
||||
|
||||
public function testControllerIsMessageComponentInterface() { |
||||
$this->setExpectedException('\UnexpectedValueException'); |
||||
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => new \StdClass))); |
||||
$this->_router->onOpen($this->_conn, $this->_req); |
||||
} |
||||
|
||||
public function testControllerOnOpen() { |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); |
||||
$this->_router->onOpen($this->_conn, $this->_req); |
||||
|
||||
$expectedConn = new \PHPUnit_Framework_Constraint_IsInstanceOf('\Ratchet\ConnectionInterface'); |
||||
$controller->expects($this->once())->method('onOpen')->with($expectedConn, $this->_req); |
||||
|
||||
$this->_matcher->expects($this->any())->method('match')->will($this->returnValue(array('_controller' => $controller))); |
||||
$this->_router->onOpen($this->_conn, $this->_req); |
||||
} |
||||
|
||||
public function testControllerOnMessageBubbles() { |
||||
$message = "The greatest trick the Devil ever pulled was convincing the world he didn't exist"; |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
$controller->expects($this->once())->method('onMessage')->with($this->_conn, $message); |
||||
|
||||
$this->_conn->controller = $controller; |
||||
|
||||
$this->_router->onMessage($this->_conn, $message); |
||||
} |
||||
|
||||
public function testControllerOnCloseBubbles() { |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
$controller->expects($this->once())->method('onClose')->with($this->_conn); |
||||
|
||||
$this->_conn->controller = $controller; |
||||
|
||||
$this->_router->onClose($this->_conn); |
||||
} |
||||
|
||||
public function testControllerOnErrorBubbles() { |
||||
$e= new \Exception('One cannot be betrayed if one has no exceptions'); |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
$controller->expects($this->once())->method('onError')->with($this->_conn, $e); |
||||
|
||||
$this->_conn->controller = $controller; |
||||
|
||||
$this->_router->onError($this->_conn, $e); |
||||
} |
||||
|
||||
public function testRouterGeneratesRouteParameters() { |
||||
/** @var $controller WsServerInterface */ |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
/** @var $matcher UrlMatcherInterface */ |
||||
$this->_matcher->expects($this->any())->method('match')->will( |
||||
$this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) |
||||
); |
||||
$conn = $this->getMock('Ratchet\Mock\Connection'); |
||||
|
||||
$request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', 'ws://random.url'), '', false); |
||||
$request->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); |
||||
|
||||
$request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); |
||||
$request->setUrl('ws://doesnt.matter/'); |
||||
|
||||
$router = new Router($this->_matcher); |
||||
|
||||
$router->onOpen($conn, $request); |
||||
|
||||
$this->assertEquals(array('foo' => 'bar', 'baz' => 'qux'), $request->getQuery()->getAll()); |
||||
} |
||||
|
||||
public function testQueryParams() { |
||||
$controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); |
||||
$this->_matcher->expects($this->any())->method('match')->will( |
||||
$this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) |
||||
); |
||||
|
||||
$conn = $this->getMock('Ratchet\Mock\Connection'); |
||||
$request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', ''), '', false); |
||||
|
||||
$request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); |
||||
$request->setUrl('ws://doesnt.matter?hello=world&foo=nope'); |
||||
|
||||
$router = new Router($this->_matcher); |
||||
$router->onOpen($conn, $request); |
||||
|
||||
$this->assertEquals(array('foo' => 'nope', 'baz' => 'qux', 'hello' => 'world'), $request->getQuery()->getAll()); |
||||
} |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\Server\EchoServer; |
||||
|
||||
class EchoServerTest extends \PHPUnit_Framework_TestCase { |
||||
protected $_conn; |
||||
protected $_comp; |
||||
|
||||
public function setUp() { |
||||
$this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$this->_comp = new EchoServer; |
||||
} |
||||
|
||||
public function testMessageEchod() { |
||||
$message = 'Tillsonburg, my back still aches when I hear that word.'; |
||||
$this->_conn->expects($this->once())->method('send')->with($message); |
||||
$this->_comp->onMessage($this->_conn, $message); |
||||
} |
||||
|
||||
public function testErrorClosesConnection() { |
||||
ob_start(); |
||||
$this->_conn->expects($this->once())->method('close'); |
||||
$this->_comp->onError($this->_conn, new \Exception); |
||||
ob_end_clean(); |
||||
} |
||||
} |
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
<?php |
||||
namespace Ratchet\Application\Server; |
||||
use Ratchet\Server\FlashPolicy; |
||||
|
||||
/** |
||||
* @covers Ratchet\Server\FlashPolicy |
||||
*/ |
||||
class FlashPolicyTest extends \PHPUnit_Framework_TestCase { |
||||
|
||||
protected $_policy; |
||||
|
||||
public function setUp() { |
||||
$this->_policy = new FlashPolicy(); |
||||
} |
||||
|
||||
public function testPolicyRender() { |
||||
$this->_policy->setSiteControl('all'); |
||||
$this->_policy->addAllowedAccess('example.com', '*'); |
||||
$this->_policy->addAllowedAccess('dev.example.com', '*'); |
||||
|
||||
$this->assertInstanceOf('SimpleXMLElement', $this->_policy->renderPolicy()); |
||||
} |
||||
|
||||
public function testInvalidPolicyReader() { |
||||
$this->setExpectedException('UnexpectedValueException'); |
||||
$this->_policy->renderPolicy(); |
||||
} |
||||
|
||||
public function testInvalidDomainPolicyReader() { |
||||
$this->setExpectedException('UnexpectedValueException'); |
||||
$this->_policy->setSiteControl('all'); |
||||
$this->_policy->addAllowedAccess('dev.example.*', '*'); |
||||
$this->_policy->renderPolicy(); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider siteControl |
||||
*/ |
||||
public function testSiteControlValidation($accept, $permittedCrossDomainPolicies) { |
||||
$this->assertEquals($accept, $this->_policy->validateSiteControl($permittedCrossDomainPolicies)); |
||||
} |
||||
|
||||
public static function siteControl() { |
||||
return array( |
||||
array(true, 'all') |
||||
, array(true, 'none') |
||||
, array(true, 'master-only') |
||||
, array(false, 'by-content-type') |
||||
, array(false, 'by-ftp-filename') |
||||
, array(false, '') |
||||
, array(false, 'all ') |
||||
, array(false, 'asdf') |
||||
, array(false, '@893830') |
||||
, array(false, '*') |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider URI |
||||
*/ |
||||
public function testDomainValidation($accept, $domain) { |
||||
$this->assertEquals($accept, $this->_policy->validateDomain($domain)); |
||||
} |
||||
|
||||
public static function URI() { |
||||
return array( |
||||
array(true, '*') |
||||
, array(true, 'example.com') |
||||
, array(true, 'exam-ple.com') |
||||
, array(true, '*.example.com') |
||||
, array(true, 'www.example.com') |
||||
, array(true, 'dev.dev.example.com') |
||||
, array(true, 'http://example.com') |
||||
, array(true, 'https://example.com') |
||||
, array(true, 'http://*.example.com') |
||||
, array(false, 'exam*ple.com') |
||||
, array(true, '127.0.255.1') |
||||
, array(true, 'localhost') |
||||
, array(false, 'www.example.*') |
||||
, array(false, 'www.exa*le.com') |
||||
, array(false, 'www.example.*com') |
||||
, array(false, '*.example.*') |
||||
, array(false, 'gasldf*$#a0sdf0a8sdf') |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider ports |
||||
*/ |
||||
public function testPortValidation($accept, $ports) { |
||||
$this->assertEquals($accept, $this->_policy->validatePorts($ports)); |
||||
} |
||||
|
||||
public static function ports() { |
||||
return array( |
||||
array(true, '*') |
||||
, array(true, '80') |
||||
, array(true, '80,443') |
||||
, array(true, '507,516-523') |
||||
, array(true, '507,516-523,333') |
||||
, array(true, '507,516-523,507,516-523') |
||||
, array(false, '516-') |
||||
, array(true, '516-523,11') |
||||
, array(false, '516,-523,11') |
||||
, array(false, 'example') |
||||
, array(false, 'asdf,123') |
||||
, array(false, '--') |
||||
, array(false, ',,,') |
||||
, array(false, '838*') |
||||
); |
||||
} |
||||
|
||||
public function testAddAllowedAccessOnlyAcceptsValidPorts() { |
||||
$this->setExpectedException('UnexpectedValueException'); |
||||
|
||||
$this->_policy->addAllowedAccess('*', 'nope'); |
||||
} |
||||
|
||||
public function testSetSiteControlThrowsException() { |
||||
$this->setExpectedException('UnexpectedValueException'); |
||||
|
||||
$this->_policy->setSiteControl('nope'); |
||||
} |
||||
|
||||
public function testErrorClosesConnection() { |
||||
$conn = $this->getMock('\\Ratchet\\ConnectionInterface'); |
||||
$conn->expects($this->once())->method('close'); |
||||
|
||||
$this->_policy->onError($conn, new \Exception); |
||||
} |
||||
|
||||
public function testOnMessageSendsString() { |
||||
$this->_policy->addAllowedAccess('*', '*'); |
||||
|
||||
$conn = $this->getMock('\\Ratchet\\ConnectionInterface'); |
||||
$conn->expects($this->once())->method('send')->with($this->isType('string')); |
||||
|
||||
$this->_policy->onMessage($conn, ' '); |
||||
} |
||||
|
||||
public function testOnOpenExists() { |
||||
$this->assertTrue(method_exists($this->_policy, 'onOpen')); |
||||
$conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$this->_policy->onOpen($conn); |
||||
} |
||||
|
||||
public function testOnCloseExists() { |
||||
$this->assertTrue(method_exists($this->_policy, 'onClose')); |
||||
$conn = $this->getMock('\Ratchet\ConnectionInterface'); |
||||
$this->_policy->onClose($conn); |
||||
} |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?php |
||||
namespace Ratchet\Application\Server; |
||||
use Ratchet\Server\IoConnection; |
||||
|
||||
/** |
||||
* @covers Ratchet\Server\IoConnection |
||||
*/ |
||||
class IoConnectionTest extends \PHPUnit_Framework_TestCase { |
||||
protected $sock; |
||||
protected $conn; |
||||
|
||||
public function setUp() { |
||||
$this->sock = $this->getMock('\\React\\Socket\\ConnectionInterface'); |
||||
$this->conn = new IoConnection($this->sock); |
||||
} |
||||
|
||||
public function testCloseBubbles() { |
||||
$this->sock->expects($this->once())->method('end'); |
||||
$this->conn->close(); |
||||
} |
||||
|
||||
public function testSendBubbles() { |
||||
$msg = '6 hour rides are productive'; |
||||
|
||||
$this->sock->expects($this->once())->method('write')->with($msg); |
||||
$this->conn->send($msg); |
||||
} |
||||
|
||||
public function testSendReturnsSelf() { |
||||
$this->assertSame($this->conn, $this->conn->send('fluent interface')); |
||||
} |
||||
} |
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\Server\IoServer; |
||||
use React\EventLoop\StreamSelectLoop; |
||||
use React\Socket\Server; |
||||
|
||||
/** |
||||
* @covers Ratchet\Server\IoServer |
||||
*/ |
||||
class IoServerTest extends \PHPUnit_Framework_TestCase { |
||||
protected $server; |
||||
|
||||
protected $app; |
||||
|
||||
protected $port; |
||||
|
||||
protected $reactor; |
||||
|
||||
public function setUp() { |
||||
$this->app = $this->getMock('\\Ratchet\\MessageComponentInterface'); |
||||
|
||||
$loop = new StreamSelectLoop; |
||||
$this->reactor = new Server($loop); |
||||
$this->reactor->listen(0); |
||||
|
||||
$this->port = $this->reactor->getPort(); |
||||
$this->server = new IoServer($this->app, $this->reactor, $loop); |
||||
} |
||||
|
||||
public function testOnOpen() { |
||||
$this->app->expects($this->once())->method('onOpen')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface')); |
||||
|
||||
$client = stream_socket_client("tcp://localhost:{$this->port}"); |
||||
|
||||
$this->server->loop->tick(); |
||||
|
||||
//$this->assertTrue(is_string($this->app->last['onOpen'][0]->remoteAddress)); |
||||
//$this->assertTrue(is_int($this->app->last['onOpen'][0]->resourceId)); |
||||
} |
||||
|
||||
public function testOnData() { |
||||
$msg = 'Hello World!'; |
||||
|
||||
$this->app->expects($this->once())->method('onMessage')->with( |
||||
$this->isInstanceOf('\\Ratchet\\ConnectionInterface') |
||||
, $msg |
||||
); |
||||
|
||||
$client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
||||
socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1); |
||||
socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096); |
||||
socket_set_block($client); |
||||
socket_connect($client, 'localhost', $this->port); |
||||
|
||||
$this->server->loop->tick(); |
||||
|
||||
socket_write($client, $msg); |
||||
$this->server->loop->tick(); |
||||
|
||||
socket_shutdown($client, 1); |
||||
socket_shutdown($client, 0); |
||||
socket_close($client); |
||||
|
||||
$this->server->loop->tick(); |
||||
} |
||||
|
||||
public function testOnClose() { |
||||
$this->app->expects($this->once())->method('onClose')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface')); |
||||
|
||||
$client = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); |
||||
socket_set_option($client, SOL_SOCKET, SO_REUSEADDR, 1); |
||||
socket_set_option($client, SOL_SOCKET, SO_SNDBUF, 4096); |
||||
socket_set_block($client); |
||||
socket_connect($client, 'localhost', $this->port); |
||||
|
||||
$this->server->loop->tick(); |
||||
|
||||
socket_shutdown($client, 1); |
||||
socket_shutdown($client, 0); |
||||
socket_close($client); |
||||
|
||||
$this->server->loop->tick(); |
||||
} |
||||
|
||||
public function testFactory() { |
||||
$this->assertInstanceOf('\\Ratchet\\Server\\IoServer', IoServer::factory($this->app, 0)); |
||||
} |
||||
|
||||
public function testNoLoopProvidedError() { |
||||
$this->setExpectedException('RuntimeException'); |
||||
|
||||
$io = new IoServer($this->app, $this->reactor); |
||||
$io->run(); |
||||
} |
||||
|
||||
public function testOnErrorPassesException() { |
||||
$conn = $this->getMock('\\React\\Socket\\ConnectionInterface'); |
||||
$conn->decor = $this->getMock('\\Ratchet\\ConnectionInterface'); |
||||
$err = new \Exception("Nope"); |
||||
|
||||
$this->app->expects($this->once())->method('onError')->with($conn->decor, $err); |
||||
|
||||
$this->server->handleError($err, $conn); |
||||
} |
||||
|
||||
public function onErrorCalledWhenExceptionThrown() { |
||||
$this->markTestIncomplete("Need to learn how to throw an exception from a mock"); |
||||
|
||||
$conn = $this->getMock('\\React\\Socket\\ConnectionInterface'); |
||||
$this->server->handleConnect($conn); |
||||
|
||||
$e = new \Exception; |
||||
$this->app->expects($this->once())->method('onMessage')->with($this->isInstanceOf('\\Ratchet\\ConnectionInterface'), 'f')->will($e); |
||||
$this->app->expects($this->once())->method('onError')->with($this->instanceOf('\\Ratchet\\ConnectionInterface', $e)); |
||||
|
||||
$this->server->handleData('f', $conn); |
||||
} |
||||
} |
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
<?php |
||||
namespace Ratchet\Server; |
||||
use Ratchet\Server\IpBlackList; |
||||
|
||||
/** |
||||
* @covers Ratchet\Server\IpBlackList |
||||
*/ |
||||
class IpBlackListTest extends \PHPUnit_Framework_TestCase { |
||||
protected $blocker; |
||||
protected $mock; |
||||
|
||||
public function setUp() { |
||||
$this->mock = $this->getMock('\\Ratchet\\MessageComponentInterface'); |
||||
$this->blocker = new IpBlackList($this->mock); |
||||
} |
||||
|
||||
public function testOnOpen() { |
||||
$this->mock->expects($this->exactly(3))->method('onOpen'); |
||||
|
||||
$conn1 = $this->newConn(); |
||||
$conn2 = $this->newConn(); |
||||
$conn3 = $this->newConn(); |
||||
|
||||
$this->blocker->onOpen($conn1); |
||||
$this->blocker->onOpen($conn3); |
||||
$this->blocker->onOpen($conn2); |
||||
} |
||||
|
||||
public function testBlockDoesNotTriggerOnOpen() { |
||||
$conn = $this->newConn(); |
||||
|
||||
$this->blocker->blockAddress($conn->remoteAddress); |
||||
|
||||
$this->mock->expects($this->never())->method('onOpen'); |
||||
|
||||
$ret = $this->blocker->onOpen($conn); |
||||
} |
||||
|
||||
public function testBlockDoesNotTriggerOnClose() { |
||||
$conn = $this->newConn(); |
||||
|
||||
$this->blocker->blockAddress($conn->remoteAddress); |
||||
|
||||
$this->mock->expects($this->never())->method('onClose'); |
||||
|
||||
$ret = $this->blocker->onOpen($conn); |
||||
} |
||||
|
||||
public function testOnMessageDecoration() { |
||||
$conn = $this->newConn(); |
||||
$msg = 'Hello not being blocked'; |
||||
|
||||
$this->mock->expects($this->once())->method('onMessage')->with($conn, $msg); |
||||
|
||||
$this->blocker->onMessage($conn, $msg); |
||||
} |
||||
|
||||
public function testOnCloseDecoration() { |
||||
$conn = $this->newConn(); |
||||
|
||||
$this->mock->expects($this->once())->method('onClose')->with($conn); |
||||
|
||||
$this->blocker->onClose($conn); |
||||
} |
||||
|
||||
public function testBlockClosesConnection() { |
||||
$conn = $this->newConn(); |
||||
$this->blocker->blockAddress($conn->remoteAddress); |
||||
|
||||
$conn->expects($this->once())->method('close'); |
||||
|
||||
$this->blocker->onOpen($conn); |
||||
} |
||||
|
||||
public function testAddAndRemoveWithFluentInterfaces() { |
||||
$blockOne = '127.0.0.1'; |
||||
$blockTwo = '192.168.1.1'; |
||||
$unblock = '75.119.207.140'; |
||||
|
||||
$this->blocker |
||||
->blockAddress($unblock) |
||||
->blockAddress($blockOne) |
||||
->unblockAddress($unblock) |
||||
->blockAddress($blockTwo) |
||||
; |
||||
|
||||
$this->assertEquals(array($blockOne, $blockTwo), $this->blocker->getBlockedAddresses()); |
||||
} |
||||
|
||||
public function testDecoratorPassesErrors() { |
||||
$conn = $this->newConn(); |
||||
$e = new \Exception('I threw an error'); |
||||
|
||||
$this->mock->expects($this->once())->method('onError')->with($conn, $e); |
||||
|
||||
$this->blocker->onError($conn, $e); |
||||
} |
||||
|
||||
public function addressProvider() { |
||||
return array( |
||||
array('127.0.0.1', '127.0.0.1') |
||||
, array('localhost', 'localhost') |
||||
, array('fe80::1%lo0', 'fe80::1%lo0') |
||||
, array('127.0.0.1', '127.0.0.1:6392') |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider addressProvider |
||||
*/ |
||||
public function testFilterAddress($expected, $input) { |
||||
$this->assertEquals($expected, $this->blocker->filterAddress($input)); |
||||
} |
||||
|
||||
public function testUnblockingSilentlyFails() { |
||||
$this->assertInstanceOf('\\Ratchet\\Server\\IpBlackList', $this->blocker->unblockAddress('localhost')); |
||||
} |
||||
|
||||
protected function newConn() { |
||||
$conn = $this->getMock('\\Ratchet\\ConnectionInterface'); |
||||
$conn->remoteAddress = '127.0.0.1'; |
||||
|
||||
return $conn; |
||||
} |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<?php |
||||
namespace Ratchet\Session\Serialize; |
||||
use Ratchet\Session\Serialize\PhpHandler; |
||||
|
||||
/** |
||||
* @covers Ratchet\Session\Serialize\PhpHandler |
||||
*/ |
||||
class PhpHandlerTest extends \PHPUnit_Framework_TestCase { |
||||
protected $_handler; |
||||
|
||||
public function setUp() { |
||||
$this->_handler = new PhpHandler; |
||||
} |
||||
|
||||
public function serializedProvider() { |
||||
return array( |
||||
array( |
||||
'_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}' |
||||
, array( |
||||
'_sf2_attributes' => array( |
||||
'hello' => 'world' |
||||
, 'last' => 1332872102 |
||||
) |
||||
, '_sf2_flashes' => array() |
||||
) |
||||
) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider serializedProvider |
||||
*/ |
||||
public function testUnserialize($in, $expected) { |
||||
$this->assertEquals($expected, $this->_handler->unserialize($in)); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider serializedProvider |
||||
*/ |
||||
public function testSerialize($serialized, $original) { |
||||
$this->assertEquals($serialized, $this->_handler->serialize($original)); |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue