2016-09-20 21:27:41 +08:00
< ? php
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* This file is part of workerman .
*
* Licensed under The MIT License
* For full copyright and license information , please see the MIT - LICENSE . txt
* Redistributions of files must retain the above copyright notice .
*
* @ author walkor < walkor @ workerman . net >
* @ copyright walkor < walkor @ workerman . net >
* @ link http :// www . workerman . net /
* @ license http :// www . opensource . org / licenses / mit - license . php MIT License
2015-04-04 21:46:31 +08:00
*/
2016-09-20 21:27:41 +08:00
namespace Workerman\Protocols ;
2015-04-04 21:46:31 +08:00
use Workerman\Connection\ConnectionInterface ;
2016-09-20 21:27:41 +08:00
use Workerman\Worker ;
2015-04-04 21:46:31 +08:00
2016-09-20 21:27:41 +08:00
/**
* WebSocket protocol .
*/
2015-04-04 21:46:31 +08:00
class Websocket implements \Workerman\Protocols\ProtocolInterface
{
/**
2016-09-20 21:27:41 +08:00
* Minimum head length of websocket protocol .
*
2015-04-04 21:46:31 +08:00
* @ var int
*/
2016-09-20 21:27:41 +08:00
const MIN_HEAD_LEN = 2 ;
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Websocket blob type .
*
* @ var string
2015-04-04 21:46:31 +08:00
*/
const BINARY_TYPE_BLOB = " \x81 " ;
/**
2016-09-20 21:27:41 +08:00
* Websocket arraybuffer type .
*
* @ var string
2015-04-04 21:46:31 +08:00
*/
const BINARY_TYPE_ARRAYBUFFER = " \x82 " ;
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Check the integrity of the package .
*
* @ param string $buffer
* @ param ConnectionInterface $connection
* @ return int
2015-04-04 21:46:31 +08:00
*/
public static function input ( $buffer , ConnectionInterface $connection )
{
2016-09-20 21:27:41 +08:00
// Receive length.
2015-04-04 21:46:31 +08:00
$recv_len = strlen ( $buffer );
2016-09-20 21:27:41 +08:00
// We need more data.
if ( $recv_len < self :: MIN_HEAD_LEN ) {
2015-04-04 21:46:31 +08:00
return 0 ;
}
2016-09-20 21:27:41 +08:00
// Has not yet completed the handshake.
if ( empty ( $connection -> websocketHandshake )) {
2015-04-04 21:46:31 +08:00
return self :: dealHandshake ( $buffer , $connection );
}
2016-09-20 21:27:41 +08:00
// Buffer websocket frame data.
if ( $connection -> websocketCurrentFrameLength ) {
// We need more frame data.
if ( $connection -> websocketCurrentFrameLength > $recv_len ) {
// Return 0, because it is not clear the full packet length, waiting for the frame of fin=1.
2015-04-04 21:46:31 +08:00
return 0 ;
}
2016-09-20 21:27:41 +08:00
} else {
$data_len = ord ( $buffer [ 1 ]) & 127 ;
$firstbyte = ord ( $buffer [ 0 ]);
$is_fin_frame = $firstbyte >> 7 ;
$opcode = $firstbyte & 0xf ;
switch ( $opcode ) {
2015-04-04 21:46:31 +08:00
case 0x0 :
break ;
2016-09-20 21:27:41 +08:00
// Blob type.
2015-04-04 21:46:31 +08:00
case 0x1 :
break ;
2016-09-20 21:27:41 +08:00
// Arraybuffer type.
2015-04-04 21:46:31 +08:00
case 0x2 :
break ;
2016-09-20 21:27:41 +08:00
// Close package.
2015-04-04 21:46:31 +08:00
case 0x8 :
2016-09-20 21:27:41 +08:00
// Try to emit onWebSocketClose callback.
if ( isset ( $connection -> onWebSocketClose )) {
try {
call_user_func ( $connection -> onWebSocketClose , $connection );
} catch ( \Exception $e ) {
Worker :: log ( $e );
exit ( 250 );
} catch ( \Error $e ) {
Worker :: log ( $e );
exit ( 250 );
}
} // Close connection.
else {
2015-04-04 21:46:31 +08:00
$connection -> close ();
}
return 0 ;
2016-09-20 21:27:41 +08:00
// Ping package.
2015-04-04 21:46:31 +08:00
case 0x9 :
2016-09-20 21:27:41 +08:00
// Try to emit onWebSocketPing callback.
if ( isset ( $connection -> onWebSocketPing )) {
try {
call_user_func ( $connection -> onWebSocketPing , $connection );
} catch ( \Exception $e ) {
Worker :: log ( $e );
exit ( 250 );
} catch ( \Error $e ) {
Worker :: log ( $e );
exit ( 250 );
}
} // Send pong package to client.
else {
2015-04-04 21:46:31 +08:00
$connection -> send ( pack ( 'H*' , '8a00' ), true );
}
2016-09-20 21:27:41 +08:00
// Consume data from receive buffer.
if ( ! $data_len ) {
2015-04-04 21:46:31 +08:00
$connection -> consumeRecvBuffer ( self :: MIN_HEAD_LEN );
2016-09-20 21:27:41 +08:00
if ( $recv_len > self :: MIN_HEAD_LEN ) {
return self :: input ( substr ( $buffer , self :: MIN_HEAD_LEN ), $connection );
}
2015-04-04 21:46:31 +08:00
return 0 ;
}
break ;
2016-09-20 21:27:41 +08:00
// Pong package.
2015-04-04 21:46:31 +08:00
case 0xa :
2016-09-20 21:27:41 +08:00
// Try to emit onWebSocketPong callback.
if ( isset ( $connection -> onWebSocketPong )) {
try {
call_user_func ( $connection -> onWebSocketPong , $connection );
} catch ( \Exception $e ) {
Worker :: log ( $e );
exit ( 250 );
} catch ( \Error $e ) {
Worker :: log ( $e );
exit ( 250 );
}
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
// Consume data from receive buffer.
if ( ! $data_len ) {
2015-04-04 21:46:31 +08:00
$connection -> consumeRecvBuffer ( self :: MIN_HEAD_LEN );
2016-09-20 21:27:41 +08:00
if ( $recv_len > self :: MIN_HEAD_LEN ) {
return self :: input ( substr ( $buffer , self :: MIN_HEAD_LEN ), $connection );
}
2015-04-04 21:46:31 +08:00
return 0 ;
}
break ;
2016-09-20 21:27:41 +08:00
// Wrong opcode.
2015-04-04 21:46:31 +08:00
default :
2016-09-20 21:27:41 +08:00
echo " error opcode $opcode and close websocket connection. Buffer: " . bin2hex ( $buffer ) . " \n " ;
2015-04-04 21:46:31 +08:00
$connection -> close ();
return 0 ;
}
2016-09-20 21:27:41 +08:00
// Calculate packet length.
$head_len = 6 ;
2015-04-04 21:46:31 +08:00
if ( $data_len === 126 ) {
$head_len = 8 ;
2016-09-20 21:27:41 +08:00
if ( $head_len > $recv_len ) {
2015-04-04 21:46:31 +08:00
return 0 ;
}
2016-09-20 21:27:41 +08:00
$pack = unpack ( 'nn/ntotal_len' , $buffer );
2015-04-04 21:46:31 +08:00
$data_len = $pack [ 'total_len' ];
2016-09-20 21:27:41 +08:00
} else {
if ( $data_len === 127 ) {
$head_len = 14 ;
if ( $head_len > $recv_len ) {
return 0 ;
}
$arr = unpack ( 'n/N2c' , $buffer );
$data_len = $arr [ 'c1' ] * 4294967296 + $arr [ 'c2' ];
2015-04-04 21:46:31 +08:00
}
}
$current_frame_length = $head_len + $data_len ;
2016-09-20 21:27:41 +08:00
if ( $is_fin_frame ) {
2015-04-04 21:46:31 +08:00
return $current_frame_length ;
2016-09-20 21:27:41 +08:00
} else {
2015-04-04 21:46:31 +08:00
$connection -> websocketCurrentFrameLength = $current_frame_length ;
}
}
2016-09-20 21:27:41 +08:00
// Received just a frame length data.
if ( $connection -> websocketCurrentFrameLength === $recv_len ) {
2015-04-04 21:46:31 +08:00
self :: decode ( $buffer , $connection );
$connection -> consumeRecvBuffer ( $connection -> websocketCurrentFrameLength );
$connection -> websocketCurrentFrameLength = 0 ;
return 0 ;
2016-09-20 21:27:41 +08:00
} // The length of the received data is greater than the length of a frame.
elseif ( $connection -> websocketCurrentFrameLength < $recv_len ) {
2015-04-04 21:46:31 +08:00
self :: decode ( substr ( $buffer , 0 , $connection -> websocketCurrentFrameLength ), $connection );
$connection -> consumeRecvBuffer ( $connection -> websocketCurrentFrameLength );
2016-09-20 21:27:41 +08:00
$current_frame_length = $connection -> websocketCurrentFrameLength ;
2015-04-04 21:46:31 +08:00
$connection -> websocketCurrentFrameLength = 0 ;
2016-09-20 21:27:41 +08:00
// Continue to read next frame.
2015-04-04 21:46:31 +08:00
return self :: input ( substr ( $buffer , $current_frame_length ), $connection );
2016-09-20 21:27:41 +08:00
} // The length of the received data is less than the length of a frame.
else {
2015-04-04 21:46:31 +08:00
return 0 ;
}
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Websocket encode .
*
* @ param string $buffer
* @ param ConnectionInterface $connection
2015-04-04 21:46:31 +08:00
* @ return string
*/
public static function encode ( $buffer , ConnectionInterface $connection )
{
2016-09-20 21:27:41 +08:00
if ( ! is_scalar ( $buffer )) {
throw new \Exception ( " You can't send( " . gettype ( $buffer ) . " ) to client, you need to convert it to a string. " );
}
2015-04-04 21:46:31 +08:00
$len = strlen ( $buffer );
2016-09-20 21:27:41 +08:00
if ( empty ( $connection -> websocketType )) {
$connection -> websocketType = self :: BINARY_TYPE_BLOB ;
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
$first_byte = $connection -> websocketType ;
2016-09-20 21:27:41 +08:00
if ( $len <= 125 ) {
$encode_buffer = $first_byte . chr ( $len ) . $buffer ;
} else {
if ( $len <= 65535 ) {
$encode_buffer = $first_byte . chr ( 126 ) . pack ( " n " , $len ) . $buffer ;
} else {
$encode_buffer = $first_byte . chr ( 127 ) . pack ( " xxxxN " , $len ) . $buffer ;
}
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
// Handshake not completed so temporary buffer websocket data waiting for send.
if ( empty ( $connection -> websocketHandshake )) {
if ( empty ( $connection -> tmpWebsocketData )) {
$connection -> tmpWebsocketData = '' ;
}
$connection -> tmpWebsocketData .= $encode_buffer ;
// Return empty string.
return '' ;
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
return $encode_buffer ;
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Websocket decode .
*
* @ param string $buffer
* @ param ConnectionInterface $connection
2015-04-04 21:46:31 +08:00
* @ return string
*/
public static function decode ( $buffer , ConnectionInterface $connection )
{
$len = $masks = $data = $decoded = null ;
$len = ord ( $buffer [ 1 ]) & 127 ;
if ( $len === 126 ) {
$masks = substr ( $buffer , 4 , 4 );
2016-09-20 21:27:41 +08:00
$data = substr ( $buffer , 8 );
2015-04-04 21:46:31 +08:00
} else {
2016-09-20 21:27:41 +08:00
if ( $len === 127 ) {
$masks = substr ( $buffer , 10 , 4 );
$data = substr ( $buffer , 14 );
} else {
$masks = substr ( $buffer , 2 , 4 );
$data = substr ( $buffer , 6 );
}
2015-04-04 21:46:31 +08:00
}
for ( $index = 0 ; $index < strlen ( $data ); $index ++ ) {
$decoded .= $data [ $index ] ^ $masks [ $index % 4 ];
}
2016-09-20 21:27:41 +08:00
if ( $connection -> websocketCurrentFrameLength ) {
2015-04-04 21:46:31 +08:00
$connection -> websocketDataBuffer .= $decoded ;
return $connection -> websocketDataBuffer ;
2016-09-20 21:27:41 +08:00
} else {
if ( $connection -> websocketDataBuffer !== '' ) {
$decoded = $connection -> websocketDataBuffer . $decoded ;
$connection -> websocketDataBuffer = '' ;
}
2015-04-04 21:46:31 +08:00
return $decoded ;
}
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Websocket handshake .
*
* @ param string $buffer
* @ param \Workerman\Connection\TcpConnection $connection
2015-04-04 21:46:31 +08:00
* @ return int
*/
protected static function dealHandshake ( $buffer , $connection )
{
2016-09-20 21:27:41 +08:00
// HTTP protocol.
if ( 0 === strpos ( $buffer , 'GET' )) {
// Find \r\n\r\n.
2015-04-04 21:46:31 +08:00
$heder_end_pos = strpos ( $buffer , " \r \n \r \n " );
2016-09-20 21:27:41 +08:00
if ( ! $heder_end_pos ) {
2015-04-04 21:46:31 +08:00
return 0 ;
}
2016-09-20 21:27:41 +08:00
$header_length = $heder_end_pos + 4 ;
// Get Sec-WebSocket-Key.
2015-04-04 21:46:31 +08:00
$Sec_WebSocket_Key = '' ;
2016-09-20 21:27:41 +08:00
if ( preg_match ( " /Sec-WebSocket-Key: *(.*?) \r \n /i " , $buffer , $match )) {
2015-04-04 21:46:31 +08:00
$Sec_WebSocket_Key = $match [ 1 ];
2016-09-20 21:27:41 +08:00
} else {
$connection -> send ( " HTTP/1.1 400 Bad Request \r \n \r \n <b>400 Bad Request</b><br>Sec-WebSocket-Key not found.<br>This is a WebSocket service and can not be accessed via HTTP.<br>See <a href= \" http://wiki.workerman.net/Error1 \" >http://wiki.workerman.net/Error1</a> " ,
true );
2015-04-04 21:46:31 +08:00
$connection -> close ();
return 0 ;
}
2016-09-20 21:27:41 +08:00
// Calculation websocket key.
$new_key = base64_encode ( sha1 ( $Sec_WebSocket_Key . " 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 " , true ));
// Handshake response data.
$handshake_message = " HTTP/1.1 101 Switching Protocols \r \n " ;
$handshake_message .= " Upgrade: websocket \r \n " ;
$handshake_message .= " Sec-WebSocket-Version: 13 \r \n " ;
$handshake_message .= " Connection: Upgrade \r \n " ;
$handshake_message .= " Server: workerman/ " . Worker :: VERSION . " \r \n " ;
$handshake_message .= " Sec-WebSocket-Accept: " . $new_key . " \r \n \r \n " ;
// Mark handshake complete..
2015-04-04 21:46:31 +08:00
$connection -> websocketHandshake = true ;
2016-09-20 21:27:41 +08:00
// Websocket data buffer.
2015-04-04 21:46:31 +08:00
$connection -> websocketDataBuffer = '' ;
2016-09-20 21:27:41 +08:00
// Current websocket frame length.
2015-04-04 21:46:31 +08:00
$connection -> websocketCurrentFrameLength = 0 ;
2016-09-20 21:27:41 +08:00
// Current websocket frame data.
2015-04-04 21:46:31 +08:00
$connection -> websocketCurrentFrameBuffer = '' ;
2016-09-20 21:27:41 +08:00
// Consume handshake data.
$connection -> consumeRecvBuffer ( $header_length );
// Send handshake response.
$connection -> send ( $handshake_message , true );
// There are data waiting to be sent.
if ( ! empty ( $connection -> tmpWebsocketData )) {
$connection -> send ( $connection -> tmpWebsocketData , true );
$connection -> tmpWebsocketData = '' ;
}
2015-04-04 21:46:31 +08:00
// blob or arraybuffer
2016-09-20 21:27:41 +08:00
if ( empty ( $connection -> websocketType )) {
$connection -> websocketType = self :: BINARY_TYPE_BLOB ;
}
// Try to emit onWebSocketConnect callback.
if ( isset ( $connection -> onWebSocketConnect )) {
2015-04-04 21:46:31 +08:00
self :: parseHttpHeader ( $buffer );
2016-09-20 21:27:41 +08:00
try {
2015-04-04 21:46:31 +08:00
call_user_func ( $connection -> onWebSocketConnect , $connection , $buffer );
2016-09-20 21:27:41 +08:00
} catch ( \Exception $e ) {
Worker :: log ( $e );
exit ( 250 );
} catch ( \Error $e ) {
Worker :: log ( $e );
exit ( 250 );
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
if ( ! empty ( $_SESSION ) && class_exists ( '\GatewayWorker\Lib\Context' )) {
$connection -> session = \GatewayWorker\Lib\Context :: sessionEncode ( $_SESSION );
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
$_GET = $_SERVER = $_SESSION = $_COOKIE = array ();
2015-04-04 21:46:31 +08:00
}
2016-09-20 21:27:41 +08:00
if ( strlen ( $buffer ) > $header_length ) {
return self :: input ( substr ( $buffer , $header_length ), $connection );
}
2015-04-04 21:46:31 +08:00
return 0 ;
2016-09-20 21:27:41 +08:00
} // Is flash policy-file-request.
elseif ( 0 === strpos ( $buffer , '<polic' )) {
$policy_xml = '<?xml version="1.0"?><cross-domain-policy><site-control permitted-cross-domain-policies="all"/><allow-access-from domain="*" to-ports="*"/></cross-domain-policy>' . " \0 " ;
2015-04-04 21:46:31 +08:00
$connection -> send ( $policy_xml , true );
$connection -> consumeRecvBuffer ( strlen ( $buffer ));
return 0 ;
}
2016-09-20 21:27:41 +08:00
// Bad websocket handshake request.
$connection -> send ( " HTTP/1.1 400 Bad Request \r \n \r \n <b>400 Bad Request</b><br>Invalid handshake data for websocket. " ,
true );
2015-04-04 21:46:31 +08:00
$connection -> close ();
return 0 ;
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
/**
2016-09-20 21:27:41 +08:00
* Parse http header .
*
2015-04-04 21:46:31 +08:00
* @ param string $buffer
* @ return void
*/
protected static function parseHttpHeader ( $buffer )
{
2016-09-20 21:27:41 +08:00
// Parse headers.
list ( $http_header , ) = explode ( " \r \n \r \n " , $buffer , 2 );
$header_data = explode ( " \r \n " , $http_header );
if ( $_SERVER ) {
$_SERVER = array ();
}
list ( $_SERVER [ 'REQUEST_METHOD' ], $_SERVER [ 'REQUEST_URI' ], $_SERVER [ 'SERVER_PROTOCOL' ]) = explode ( ' ' ,
$header_data [ 0 ]);
2015-04-04 21:46:31 +08:00
unset ( $header_data [ 0 ]);
2016-09-20 21:27:41 +08:00
foreach ( $header_data as $content ) {
2015-04-04 21:46:31 +08:00
// \r\n\r\n
2016-09-20 21:27:41 +08:00
if ( empty ( $content )) {
2015-04-04 21:46:31 +08:00
continue ;
}
2016-09-20 21:27:41 +08:00
list ( $key , $value ) = explode ( ':' , $content , 2 );
$key = str_replace ( '-' , '_' , strtoupper ( $key ));
$value = trim ( $value );
$_SERVER [ 'HTTP_' . $key ] = $value ;
switch ( $key ) {
2015-04-04 21:46:31 +08:00
// HTTP_HOST
2016-09-20 21:27:41 +08:00
case 'HOST' :
$tmp = explode ( ':' , $value );
2015-04-04 21:46:31 +08:00
$_SERVER [ 'SERVER_NAME' ] = $tmp [ 0 ];
2016-09-20 21:27:41 +08:00
if ( isset ( $tmp [ 1 ])) {
2015-04-04 21:46:31 +08:00
$_SERVER [ 'SERVER_PORT' ] = $tmp [ 1 ];
}
break ;
2016-09-20 21:27:41 +08:00
// cookie
case 'COOKIE' :
2015-04-04 21:46:31 +08:00
parse_str ( str_replace ( '; ' , '&' , $_SERVER [ 'HTTP_COOKIE' ]), $_COOKIE );
break ;
}
}
2016-09-20 21:27:41 +08:00
2015-04-04 21:46:31 +08:00
// QUERY_STRING
$_SERVER [ 'QUERY_STRING' ] = parse_url ( $_SERVER [ 'REQUEST_URI' ], PHP_URL_QUERY );
2016-09-20 21:27:41 +08:00
if ( $_SERVER [ 'QUERY_STRING' ]) {
2015-04-04 21:46:31 +08:00
// $GET
parse_str ( $_SERVER [ 'QUERY_STRING' ], $_GET );
2016-09-20 21:27:41 +08:00
} else {
2015-04-04 21:46:31 +08:00
$_SERVER [ 'QUERY_STRING' ] = '' ;
}
}
}