host = $host; $this->port = $port; $this->path = $path; $this->origin = $origin; $this->key = $this->generateToken(self::TOKEN_LENGHT); } /** * Disconnect on destruct */ function __destruct() { $this->disconnect(); } /** * Connect client to server * * @return $this */ public function connect() { $this->socket = new \swoole_client(SWOOLE_SOCK_TCP); if (!$this->socket->connect($this->host, $this->port)) { return false; } $this->socket->send($this->createHeader()); return $this->recv(); } public function getSocket() { return $this->socket; } /** * Disconnect from server */ public function disconnect() { $this->connected = false; $this->socket->close(); } public function close($code = self::CLOSE_NORMAL, $reason = '') { $data = pack('n', $code) . $reason; return $this->socket->send(swoole_websocket_server::pack($data, self::OPCODE_CONNECTION_CLOSE, true)); } public function recv() { $data = $this->socket->recv(); if ($data === false) { echo "Error: {$this->socket->errMsg}"; return false; } $this->buffer .= $data; $recv_data = $this->parseData($this->buffer); if ($recv_data) { $this->buffer = ''; return $recv_data; } else { return false; } } /** * @param string $data * @param string $type * @param bool $masked * @return bool */ public function send($data, $type = 'text', $masked = false) { switch($type) { case 'text': $_type = WEBSOCKET_OPCODE_TEXT; break; case 'binary': case 'bin': $_type = WEBSOCKET_OPCODE_BINARY; break; case 'ping': $_type = WEBSOCKET_OPCODE_PING; break; default: return false; } return $this->socket->send(swoole_websocket_server::pack($data, $_type, true, $masked)); } /** * Parse received data * * @param $response */ private function parseData($response) { if (!$this->connected) { $response = $this->parseIncomingRaw($response); if (isset($response['Sec-Websocket-Accept']) && base64_encode(pack('H*', sha1($this->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) === $response['Sec-Websocket-Accept'] ) { $this->connected = true; return true; } else { throw new \Exception("error response key."); } } $frame = swoole_websocket_server::unpack($response); if ($frame) { return $this->returnData ? $frame->data : $frame; } else { throw new \Exception("swoole_websocket_server::unpack failed."); } } /** * Create header for websocket client * * @return string */ private function createHeader() { $host = $this->host; if ($host === '127.0.0.1' || $host === '0.0.0.0') { $host = 'localhost'; } return "GET {$this->path} HTTP/1.1" . "\r\n" . "Origin: {$this->origin}" . "\r\n" . "Host: {$host}:{$this->port}" . "\r\n" . "Sec-WebSocket-Key: {$this->key}" . "\r\n" . "User-Agent: PHPWebSocketClient/" . self::VERSION . "\r\n" . "Upgrade: websocket" . "\r\n" . "Connection: Upgrade" . "\r\n" . "Sec-WebSocket-Protocol: wamp" . "\r\n" . "Sec-WebSocket-Version: 13" . "\r\n" . "\r\n"; } /** * Parse raw incoming data * * @param $header * * @return array */ private function parseIncomingRaw($header) { $retval = array(); $content = ""; $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); foreach ($fields as $field) { if (preg_match('/([^:]+): (.+)/m', $field, $match)) { $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function ($matches) { return strtoupper($matches[0]); }, strtolower(trim($match[1]))); if (isset($retval[$match[1]])) { $retval[$match[1]] = array($retval[$match[1]], $match[2]); } else { $retval[$match[1]] = trim($match[2]); } } else { if (preg_match('!HTTP/1\.\d (\d)* .!', $field)) { $retval["status"] = $field; } else { $content .= $field . "\r\n"; } } } $retval['content'] = $content; return $retval; } /** * Generate token * * @param int $length * * @return string */ private function generateToken($length) { $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"ยง$%&/()=[]{}'; $useChars = array(); // select some random chars: for ($i = 0; $i < $length; $i++) { $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; } // Add numbers array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); shuffle($useChars); $randomString = trim(implode('', $useChars)); $randomString = substr($randomString, 0, self::TOKEN_LENGHT); return base64_encode($randomString); } /** * Generate token * * @param int $length * * @return string */ public function generateAlphaNumToken($length) { $characters = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); srand((float)microtime() * 1000000); $token = ''; do { shuffle($characters); $token .= $characters[mt_rand(0, (count($characters) - 1))]; } while (strlen($token) < $length); return $token; } }