PHP Ratchet Websocket Symfony, stash data in session? - php

I'm doing websocket server to game and i have problem to stash information about users. I write event listener which run special event according to first incoming packet in message. Everything works OK but i don't have idea how stash the data about clients...
I'm try to use session but all time, my variables not stored in session.
Command to run server ( i'm try memcache and PDOSession storage ):
<?php
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Server starting');
$communication = $this->getContainer()->get('uo_server.server_communication');
$pdo = new \PDO('mysql:host=127.0.01;dbname=uo-session', 'root', null);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$dbOptions = array(
'db_table' => 'sessions',
'db_id_col' => 'sess_id',
'db_data_col' => 'sess_data',
'db_time_col' => 'sess_time',
'db_lifetime_col' => 'sess_lifetime',
'lock_mode' => 0
);
$sessionProvider = new PdoSessionHandler($pdo, $dbOptions);
$server = IoServer::factory(
new SessionProvider($communication, $sessionProvider),
2593,
'127.0.0.1');
$server->run();
}
Message component looks like:
function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->packetDispatcher = $container->get('uo_server.packets_event_dispatcher');
$this->connections = new ArrayCollection();
}
/**
* 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->connections->add($conn);
/**
* #var Session $session
*/
$session = $conn->Session;
$session->setId('session_'.$conn->resourceId);
$session->start();
}
/**
* 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)
{
$conn->Session->save();
if($this->connections->contains($conn)) {
$this->connections->removeElement($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)
{
$conn->Session->save();
// TODO: Implement onError() method.
}
/**
* 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 $conn, $msg)
{
$this->packetDispatcher->dispatchEvent($conn, $msg);
$conn->Session->save();
}
Packet dispatcher is simply:
public function dispatchEvent(ConnectionInterface $sender, $msg)
{
$data = unpack('C*',$msg);
$eventPacket = "0x".DataConverter::convertDecimalToHex($data[1]);
$event = isset($this->packetsEvents[$eventPacket]) ? $this->packetsEvents[$eventPacket] : null;
if ($event !== null) {
$incomingEvent = new MainEvent($sender, $msg);
if ($this->dispatcher->hasListeners($event)) {
$this->dispatcher->dispatch($event, $incomingEvent);
} else {
Logger::Log('Event :'.$event.' is not implement yet!');
}
}
}
at least the example login event:
public function onLogin(MainEvent $event)
{
$this->event = $event;
$packetReader = new PacketReader($this->event->getMessage());
$login = $packetReader->getByteFrom(2, 30);
$password = $packetReader->getByteFrom(32, 30); //todo some byte on the end?
if($this->accountFinder->checkAccountAccess($login, $password)) {
Logger::Log('try to log:'.$login . '/' . $password.';');
$account = $this->accountFinder->getAccount($login, $password);
return $this->loginSuccess($account);
}
if($this->serverInfo['auto_account'] && $this->accountFinder->checkAccountExist($login) === false) {
Logger::Log('account not found, but autoaccounting is on. Account created;');
$account = $this->accountFinder->createUser($login, $password);
return $this->loginSuccess($account);
}
Logger::Log('login for credientals: '.$login.'/'.$password.' failure;');
$this->loginFailure($login);
}
private function loginSuccess(Account $account)
{
$ip = explode(".", $this->serverInfo['host']);
$packet = new Packet;
$packet
->prependPacket("A8")
->appendPacket("FF")
->appendPacket(DataConverter::convertDecimalToHex(1), 4)
->appendPacket(DataConverter::convertDecimalToHex(1), 4)
->appendPacket(DataConverter::convertStringToHex($this->serverInfo['name']), 64, Packet::PAD_FROM_RIGHT)
->appendPacket(DataConverter::convertDecimalToHex($this->serverInfo['full']), 2) //todo full is not from PARAM!!!
->appendPacket(DataConverter::convertDecimalToHex($this->serverInfo['timezone']), 2);
for ($i=3; $i>=0; $i--) {
$packet->appendPacket(DataConverter::convertDecimalToHex($ip[$i]), 2);
}
$currentLenght = $packet->getPacketSize();
$packet->injectPacket(DataConverter::convertDecimalToHex($currentLenght + 2), 4, 2);
$this->event->setToSession('accountId', $account->getId());
$this->event->getClientSession()->save();
$this->event->getClient()->send($packet->getPacket());
return true;
}
/**
* TODO FAILURE FROM BANS ETC.
* #return bool
*/
private function loginFailure($login)
{
$reason = 0;
if ($this->accountFinder->checkAccountExist($login)) {
$reason = 3;
}
$packet = new Packet;
$packet
->prependPacket("82")
->appendPacket(DataConverter::convertDecimalToHex($reason), 2);
$this->event->getClient()->send($packet->getPacket());
return true;
}
When i try to read the session in next event, it's not exist. Looks like new session start, but files and session in database not exist (its not created). It's possible to storage data like this ? If not, what alternatives i had?
link to repo with full code:
REPO

Related

Laravel Pusher array_merge: Expected parameter 2 to be an array, null given

i'm following a tutorial from pusher to display notification on the website. Everything has been in line with the tutorial, however this particular error showed up when i try to access the notification on localhost:8000/test i have no clue on how to fix it.
the error message
expected result : notification send message
output : array_merge() error
related tutorial : https://pusher.com/tutorials/web-notifications-laravel-pusher-channels
related file : C:\xampp\htdocs\inventory-prototype\vendor\pusher\pusher-php-server\src\Pusher.php:518
here's my Events/ItemAdd :
class ItemAdd implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($user)
{
$this->user = $user;
$this->message = '{ $user } added an item';
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return ['item-add'];
}
}
here's my web.php:
Route::get('test', function () {
dd(event(new App\Events\ItemAdd('Someone')));
return "Event has been sent!";
});
vendor/pusher/src/Pusher.php -> Trigger
/**
* Trigger an event by providing event name and payload.
* Optionally provide a socket ID to exclude a client (most likely the sender).
*
* #param array|string $channels A channel name or an array of channel names to publish the event on.
* #param string $event
* #param mixed $data Event data
* #param array $params [optional]
* #param bool $already_encoded [optional]
*
* #throws PusherException Throws PusherException if $channels is an array of size 101 or above or $socket_id is invalid
* #throws ApiErrorException Throws ApiErrorException if the Channels HTTP API responds with an error
*
* #return object
*/
public function trigger($channels, $event, $data, $params = array(), $already_encoded = false)
{
if (is_string($channels) === true) {
$channels = array($channels);
}
$this->validate_channels($channels);
if (isset($params['socket_id'])) {
$this->validate_socket_id($params['socket_id']);
}
$has_encrypted_channel = false;
foreach ($channels as $chan) {
if (PusherCrypto::is_encrypted_channel($chan)) {
$has_encrypted_channel = true;
}
}
if ($has_encrypted_channel) {
if (count($channels) > 1) {
// For rationale, see limitations of end-to-end encryption in the README
throw new PusherException('You cannot trigger to multiple channels when using encrypted channels');
} else {
$data_encoded = $this->crypto->encrypt_payload($channels[0], $already_encoded ? $data : json_encode($data));
}
} else {
$data_encoded = $already_encoded ? $data : json_encode($data);
}
$query_params = array();
$path = $this->settings['base_path'].'/events';
// json_encode might return false on failure
if (!$data_encoded) {
$this->log('Failed to perform json_encode on the the provided data: {error}', array(
'error' => print_r($data, true),
), LogLevel::ERROR);
}
$post_params = array();
$post_params['name'] = $event;
$post_params['data'] = $data_encoded;
$post_params['channels'] = array_values($channels);
$all_params = array_merge($post_params, $params);
$post_value = json_encode($all_params);
$query_params['body_md5'] = md5($post_value);
$ch = $this->create_curl($this->channels_url_prefix(), $path, 'POST', $query_params);
$this->log('trigger POST: {post_value}', compact('post_value'));
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
$response = $this->exec_curl($ch);
if ($response['status'] !== 200) {
throw new ApiErrorException($response['body'], $response['status']);
}
$result = json_decode($response['body']);
if (property_exists($result, 'channels')) {
$result->channels = get_object_vars($result->channels);
}
return $result;
}
any help will be appreciated
i say whatever, i just downgraded to pusher 4.1, on composer.json look for pusher and change the version to 4.1 in case anybody on earth other than me get the same error.
This error was resolved in the pusher-http-php library v5.0.1 and Laravel v8.29.0. https://github.com/pusher/pusher-http-php/issues/288
You can find the solution to this problem in this comment by ben-pusher to issue error array_merge - laravel 8 - php74 #278:
You may need to use composer require pusher/pusher-php-server ^4.1- support for v5.0.0 of this library hasn't been added to Laravel yet.

Why is ldap_errno() feeded the connection here?

I have a process in lumen project (Laravel 6.2) where a user is identified through an LDAP.
The code looks like this:
<?php
namespace App\Http\Helpers;
// Currently unused
// use App\User;
// use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Log;
class LDAP
{
private $connection, $password;
protected $domain, $username, $ldap_address, $ldap_port;
/**
* Constructs the ldap connector with data used for the connection and
* bind process.
*/
function __construct()
{
$this->domain = env("LDAP_DOMAIN");
$this->username = env("LDAP_USERNAME");
$this->password = env("LDAP_PASSWORD");
$this->ldap_address = env("LDAP_ADDRESS");
$this->ldap_port = env("LDAP_PORT");
}
/**
* Establishes a connection to the ldap server and saves it in
* #var Resource $connection.
*
* #return true
* On success
* #return false
* On failure
*/
private function connect()
{
$this->connection = ldap_connect($this->ldap_address, $this->ldap_port);
if($this->connection)
{
Log::info("Connection established");
ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
$bind = ldap_bind($this->connection, $this->domain . "\\" . $this->username, $this->password);
if($bind)
{
Log::info("Bind valid");
return true;
}
else
{
Log::info("Bind failed");
return false;
}
}
else
{
Log::info("Connection failed");
return false;
}
}
private function disconnect()
{
ldap_unbind($this->connection);
}
/**
* Searches for a specific person in the LDAP-Directory and returns important
* data from this person which will be used later in the application.
*
* #param String $person
* The person to search for
* #return Array $result
* The persons data
*/
public function getUser($username, $password)
{
try
{
$is_connected = $this->connect();
if(!$is_connected)
{
$this->disconnect();
return false;
}
$dn = "OU=Benutzer,OU=sdfsfd,DC=sfdsfsf,DC=de";
$fields = "(|(samaccountname=*$username*))";
$search = ldap_search($this->connection, $dn, $fields);
$result = ldap_get_entries($this->connection, $search);
if($result)
{
$bind = ldap_bind($this->connection, $this->domain . "\\" . $username, $password);
if($bind && strlen($password) > 0)
{
return mb_convert_encoding($result, 'UTF-8');
}
else
{
return "Invalid credentials!";
}
}
else
{
return "User does not exist!";
}
}
catch(\Exception $e)
{
$errno = ldap_errno($this->connection);
if ($errno) {
$ret = array("ldap_error" => $errno, "message" => ldap_err2str($errno));
}else{
$ret = array("exception_code" => $e->getCode(), "message" => $e->getMessage());
}
return $ret;
}
finally
{
$this->disconnect();
}
}
}
Now, we faced some issues when handling errors from the ldap_bind().
The error code thrown by the ldap functions couldnt be evaluated by Lumen, so we had to catch them and evaluate it manually through the ldap_errno functionality.
What puzzles me is that the $this->connection is passed to the ldap_errno() function. Why isnt it the $bind?
After all, its the bind which failed, not the connect. AFAIK the ldap_connect() doesnt even establish a connection, but instead verifies whether the credentials are plausible.
However, it works ^^ But why? What is happening in ldap_errno that the connection is passed to it, not the bind?
Because ldap_connect returns an internal handle that is identifying the "connection". ldap_errno and ldap_error then return information regarding the last error on that "connection".
So when you call them after an ldap_bind (which returns true or false depending on the outcome) you need the connection that this happened on, not the result of the bind.
Please note that "connection" does not necessarily mean that the connection to the server has already been established.

stream_socket_accept() and fgetc stalls "randomly" in an otherwise working websocket server

I am running a webssocket server in php that does its job pretty well. Except one thing:
Sometimes stream_socket_accept() will stall for 60 seconds. This can happen seconds after the server has been started, it can also happen hours after the server has been started. I can not reproduce the behavior myself.
Sometimes it stalls at the call stream_socket_accept(), sometimes it stalls when reading the header from the client directly after stream_socket_accept has returned.
What's more: default_socket_timeout is set system wide to 10 seconds, and php.ini shows this value.
Even with stream_socket_accept($socket, 0); it will stall. The timeout given is simply ignored.
My questions:
Why does it stall in the first place? When the listener indicates a new connection, stream_socket_accept should not stall, should it?
Why the the fgetc stall on the very first byte of a connection (when retrieving the header of an incoming connection, right after stream_socket_accept()?
Why does it stall exactly 60 seconds (the standard default_timeout for sockets) when I definitely changed this to 10 seconds (showing in phpinfo()).
I am running out of ideas.
ANY idea is highly appreciated.
Here is the full code of the socket (which also does some agent managing logic wich works).
My hope is that somebody spots something.
<?php
// STALLING HAPPENS SOMETIMES IN LINE 52 fgetc()
// AND IN LINE 271 stream_socket_accept()
class WS {
const
//! UUID magic string
Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
//! Packet size
Packet=65536;
//#{ Mask bits for first byte of header
const
Text=0x01,
Binary=0x02,
Close=0x08,
Ping=0x09,
Pong=0x0a,
OpCode=0x0f,
Finale=0x80;
//#}
//#{ Mask bits for second byte of header
const
Length=0x7f;
//#}
}
//! RFC6455 server socket
class Server {
protected
$addr,
$ctx,
$wait,
$sockets,
$agents=[],
$events=[];
/**
* Allocate stream socket
* #return NULL
* #param $socket resource
**/
function alloc($socket) {
trace("Fetching http header...");
// if header does not start with "GET"
// immediately close connection
foreach( ['G','E','T'] as $get) {
$character=fgetc($socket);
$metadata=stream_get_meta_data($socket);
// this MUST NOT BE REWRITTEN!
// unread_bytes can not be checked against 0
if ($character==$get && !feof($socket) && $metadata['unread_bytes'] > 0)
continue;
else {
trace("Error: Header does not start with GET – connection closed");
stream_socket_shutdown($socket,STREAM_SHUT_RDWR);
return;
}
}
$str="GET";
do {
$str.=fgetc($socket);
$metadata=stream_get_meta_data($socket);
} while (!feof($socket) && $metadata['unread_bytes'] > 0);
// Get WebSocket headers
$hdrs=[];
$CRLF="\r\n";
$verb=NULL;
$uri=NULL;
foreach (explode($CRLF,$str) as $line)
if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/',
trim($line),$match)) {
$verb=$match[1];
$uri=$match[2];
}
else
if (preg_match('/^(.+): (.+)/',trim($line),$match))
// Standardize header
$hdrs[
strtr(
ucwords(
strtolower(
strtr($match[1],'-',' ')
)
),' ','-'
)
]=$match[2];
if (empty($hdrs['Upgrade']) &&
empty($hdrs['Sec-Websocket-Key'])) {
// Not a WebSocket request
$this->write(
$socket,
$str='HTTP/1.1 400 Bad Request'.$CRLF.
'Connection: close'.$CRLF.$CRLF
);
stream_socket_shutdown($socket,STREAM_SHUT_RDWR);
// 1 #fclose($socket);
return;
}
// Handshake
$bytes=$this->write(
$socket,
$str='HTTP/1.1 101 Switching Protocols'.$CRLF.
'Upgrade: websocket'.$CRLF.
'Connection: Upgrade'.$CRLF.
'Sec-WebSocket-Accept: '.
base64_encode(
sha1(
$hdrs['Sec-Websocket-Key'].
WS::Magic,
TRUE
)
).$CRLF.$CRLF
);
if (is_int($bytes)) {
// Connect agent to server
$this->sockets[]=$socket;
$this->agents[(int)$socket]=
new Agent($this,$socket,$verb,$uri,$hdrs);
}
else
stream_socket_shutdown($socket,STREAM_SHUT_RDWR);
}
/**
* Free stream socket
* #return bool
* #param $socket resource
**/
function free($socket) {
unset($this->sockets[array_search($socket,$this->sockets)]);
unset($this->agents[(int)$socket]);
stream_socket_shutdown($socket,STREAM_SHUT_WR);
// 1 #fclose($socket);
}
/**
* Read from stream socket
* #return string|FALSE
* #param $socket resource
**/
function read($socket) {
return is_string($str=#fread($socket,WS::Packet)) && strlen($str)?
$str:
FALSE;
}
/**
* Write to stream socket
* #return int|FALSE
* #param $socket resource
* #param $str string
**/
function write($socket,$str) {
for ($i=0,$bytes=0;$i<strlen($str);$i+=$bytes) {
if (($bytes=#fwrite($socket,substr($str,$i))) &&
#fflush($socket))
continue;
return FALSE;
}
return $bytes;
}
/**
* Return socket agents
* #return array
* #param $uri string
***/
function agents($uri=NULL) {
return array_filter(
$this->agents,
function($val) use($uri) {
return $uri?($val->uri()==$uri):TRUE;
}
);
}
/**
* Return event handlers
* #return array
**/
function events() {
return $this->events;
}
/**
* Bind function to event handler
* #return object
* #param $event string
* #param $func callable
**/
function on($event,$func) {
$this->events[$event]=$func;
return $this;
}
/**
* Execute the server process
* #return object
**/
function run() {
$fw=\Base::instance();
// Activate WebSocket listener
$listen=stream_socket_server(
$this->addr,$errno,$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
$this->ctx
);
// stream_set_timeout($listen,0);
stream_set_read_buffer($listen,WS::Packet);
stream_set_write_buffer($listen,WS::Packet);
$socket=socket_import_stream($listen);
socket_set_option(
$socket,
SOL_SOCKET,
SO_REUSEADDR,
1
);
socket_set_option(
$socket,
SOL_SOCKET,
SO_LINGER,
['l_onoff'=>1,'l_linger'=>1]
);
register_shutdown_function(function() use($listen) {
foreach ($this->sockets as $socket)
if ($socket!=$listen)
$this->free($socket);
stream_socket_shutdown($listen,STREAM_SHUT_RDWR);
#fclose($listen);
if (isset($this->events['stop']) &&
is_callable($func=$this->events['stop']))
$func($this);
});
if ($errstr)
user_error($errstr,E_USER_ERROR);
if (isset($this->events['start']) &&
is_callable($func=$this->events['start']))
$func($this);
$this->sockets=[$listen];
$empty=[];
$wait=$this->wait;
while (TRUE) {
$active=$this->sockets;
$mark=microtime(TRUE);
trace("Waiting for socket action...");
$count=#stream_select(
$active,$empty,$empty,(int)$wait,round(1e6*($wait-(int)$wait))
);
if (is_bool($count) && $wait) {
if (isset($this->events['error']) &&
is_callable($func=$this->events['error']))
$func($this);
die;
}
if ($count) {
// Process active connections
foreach ($active as $socket) {
if (!is_resource($socket))
continue;
if ($socket==$listen) {
trace("New connection pending...");
if ($socket=stream_socket_accept($listen)) {
$this->alloc($socket);
trace("alloc() finished");
}
else {
trace("Connection failed...");
continue;
}
}
else {
$id=(int)$socket;
if (isset($this->agents[$id]) &&
$raw=$this->agents[$id]->fetch()) {
list($op,$data)=$raw;
// Dispatch
switch ($op & WS::OpCode) {
case WS::Text:
$data=trim($data);
case WS::Binary:
case WS::Pong:
if (isset($this->events['receive']) &&
is_callable($func=$this->events['receive']))
$func($this->agents[$id],$op,$data);
break;
case WS::Ping:
$this->agents[$id]->send(WS::Pong);
break;
default:
if (isset($this->events['invalid']) &&
is_callable($func=$this->events['invalid']))
$func($this->agents[$id],$op,$data);
case WS::Close:
$this->free($socket);
break;
}
}
}
}
$wait-=microtime(TRUE)-$mark;
while ($wait<1e-6) {
$wait+=$this->wait;
$count=0;
}
}
if (!$count) {
$mark=microtime(TRUE);
foreach ($this->sockets as $socket) {
if (!is_resource($socket))
continue;
$id=(int)$socket;
if ($socket!=$listen &&
isset($this->agents[$id]) &&
is_string($this->agents[$id]->send(WS::Ping)) &&
isset($this->events['idle']) &&
is_callable($func=$this->events['idle']))
$func($this->agents[$id]);
}
$wait=$this->wait-microtime(TRUE)+$mark;
}
}
}
/**
* Instantiate object
* #return object
* #param $addr string
* #param $ctx resource
* #param $wait int
**/
function __construct($addr,$ctx=NULL,$wait=60) {
$this->addr=$addr;
$this->ctx=$ctx?:stream_context_create();
$this->wait=$wait;
$this->events=[];
}
}
//! RFC6455 remote socket
class Agent {
protected
$server,
$id,
$socket,
$flag,
$verb,
$uri,
$headers,
$events,
$buffer='';
/**
* Return server instance
* #return object
**/
function server() {
return $this->server;
}
/**
* Return socket ID
* #return string
**/
function id() {
return $this->id;
}
/**
* Return request method
* #return string
**/
function verb() {
return $this->verb;
}
/**
* Return request URI
* #return string
**/
function uri() {
return $this->uri;
}
/**
* Return socket headers
* #return string
**/
function headers() {
return $this->headers;
}
/**
* Frame and transmit payload
* #return string|FALSE
* #param $socket resource
* #param $op int
* #param $payload string
**/
function send($op,$data='') {
$mask=WS::Finale | $op & WS::OpCode;
$len=strlen($data);
$str='';
if ($len<126)
$str=pack('CC',$mask,$len);
else
if ($len>125 && $len<65536)
$str=pack('CCn',$mask,126,$len);
else
if ($len>65535)
$str=pack('CCNN',$mask,127,$len);
$str.=$data;
$server=$this->server();
if (is_bool($server->write($this->socket,$str))) {
$server->free($this->socket);
return FALSE;
}
if (!in_array($op,[WS::Pong,WS::Close]) &&
isset($this->events['send']) &&
is_callable($func=$this->events['send']))
$func($this,$op,$data);
return $data;
}
/**
* Retrieve and unmask payload
* #return array|FALSE
**/
function fetch() {
// Unmask payload
$server=$this->server();
if (is_bool($str=$server->read($this->socket))) {
$server->free($this->socket);
return FALSE;
}
$buf=($this->buffer.=$str);
$op=ord($buf[0]) & WS::OpCode;
$len=ord($buf[1]) & WS::Length;
$pos=2;
if ($len==126) {
$len=ord($buf[2])*256+ord($buf[3]);
$pos+=2;
}
else
if ($len==127) {
for ($i=0,$len=0;$i<8;$i++)
$len=$len*256+ord($buf[$i+2]);
$pos+=8;
}
for ($i=0,$mask=[];$i<4;$i++)
$mask[$i]=ord($buf[$pos+$i]);
$pos+=4;
if (strlen($buf)<$len+$pos)
return FALSE;
$this->buffer='';
for ($i=0,$data='';$i<$len;$i++)
$data.=chr(ord($buf[$pos+$i])^$mask[$i%4]);
return [$op,$data];
}
/**
* Destroy object
* #return NULL
**/
function __destruct() {
if (isset($this->events['disconnect']) &&
is_callable($func=$this->events['disconnect']))
$func($this);
}
/**
* Instantiate object
* #return object
* #param $server object
* #param $socket resource
* #param $verb string
* #param $uri string
* #param $hdrs array
**/
function __construct($server,$socket,$verb,$uri,array $hdrs) {
$this->server=$server;
$this->id=stream_socket_get_name($socket,TRUE);
$this->socket=$socket;
$this->verb=$verb;
$this->uri=$uri;
$this->headers=$hdrs;
$this->events=$server->events();
if (isset($this->events['connect']) &&
is_callable($func=$this->events['connect']))
$func($this);
}
}
/**
* Simple console logger
* #return NULL
* #param $line string
**/
function trace($line) {
echo "\r".date('H:i:s').' '.$line.PHP_EOL;
}
/**
* Process handler for graceful exit (routed to registered shutdown handler)
* #return NULL
**/
function kill($signal) {
die;
}
pcntl_signal(SIGINT,'kill');
pcntl_signal(SIGTERM,'kill');
if (PHP_SAPI!='cli') {
// Prohibit direct HTTP access
header('HTTP/1.1 404 Not Found');
die;
}
chdir(__DIR__);
require('lib/base.php');
error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE|E_WARNING|E_USER_WARNING));
// Load .ini files
$fw=Base::instance();
$fw->
config('app/ini/config.ini')->
config('app/ini/dev.ini');
if (!is_file($pid='ws.pid') ||
!is_dir('/proc/'.file_get_contents($pid))) {
// Override any error handler specified in .ini files
ini_set('error_log','/dev/null');
$fw->DEBUG=2;
$fw->ONERROR=function($fw) {
trace($fw->get('ERROR.text'));
foreach (explode("\n",trim($fw->get('ERROR.trace'))) as $line)
trace($line);
};
$fw->VERBOSE=(bool)preg_grep('/[\/-]v/',$argv);
// Instantiate the server
$ws=new Server(
$fw->get('SITE.websocket'),
stream_context_create([
'ssl'=>$fw->get('SSL')+[
'allow_self_signed'=>TRUE,
'verify_peer'=>FALSE
]
])
);
// Intercept OpenSSL errors
$err=FALSE;
while (TRUE)
if ($msg=openssl_error_string()) {
$err=TRUE;
trace($msg);
}
else
break;
if ($err)
die;
$ws->
on('start',function($server) use($fw) {
trace('WebSocket server started ('.$fw->get('SITE.websocket').')');
file_put_contents('ws.pid',getmypid());
})->
on('error',function($server) use($fw) {
if ($err=socket_last_error()) {
trace(socket_strerror($err));
socket_clear_error();
}
if ($err=error_get_last())
trace($err['message']);
})->
on('stop',function($server) use($fw) {
trace('Shutting down ('.$fw->get('SITE.websocket').')');
#unlink('ws.pid');
})->
on('connect',function($agent) use($fw) {
trace(
'(0x00'.$agent->uri().') '.$agent->id().' connected '.
'<'.(count($agent->server()->agents())+1).'>'
);
if ($fw->VERBOSE) {
$hdrs=$agent->headers();
trace(
$hdrs['User-Agent'].' '.
'[v'.$hdrs['Sec-Websocket-Version'].']'
);
}
$agent->hash=dechex(crc32(file_get_contents(__FILE__)));
$agent->feature=[];
$agent->query='';
$agent->session=[];
})->
on('disconnect',function($agent) use($fw) {
trace('(0x08'.$agent->uri().') '.$agent->id().' disconnected');
if ($err=socket_last_error()) {
trace(socket_strerror($err));
socket_clear_error();
}
if (preg_match('/^\/(.+)/',$agent->uri(),$match)) {
$class='WebSocket\\'.$match[1];
if (isset($agent->feature[$class])) {
$obj=$agent->feature[$class];
foreach ($agent->feature as $key=>$obj)
if (is_callable([$obj,'disconnect']))
$fw->call([$obj,'disconnect'],[$fw,$agent]);
}
}
})->
on('idle',function($agent) use($fw) {
foreach ($agent->feature as $key=>$obj)
if (is_callable([$obj,'idle']))
$fw->call([$obj,'idle'],[$fw,$agent]);
})->
on('receive',function($agent,$op,$data) use($fw) {
switch($op) {
case WS::Pong:
$text='pong';
break;
case WS::Text:
$data=trim($data);
case WS::Binary:
$text='data';
break;
default:
$text='unknown';
break;
}
trace(
'(0x'.str_pad(dechex($op),2,'0',STR_PAD_LEFT).
$agent->uri().') '.$agent->id().' '.$text.' received'
);
if ($op==WS::Text && $data) {
if ($fw->VERBOSE)
trace($data);
$in=json_decode($data,TRUE);
if (json_last_error()==JSON_ERROR_NONE &&
preg_match('/^\/(.+)/',$agent->uri(),$match)) {
$class='WebSocket\\'.$match[1];
if (isset($agent->feature[$class])) {
if (isset($in['query']))
$agent->query=$in['query'];
if (isset($in['session']))
foreach ($in['session'] as $key=>$val)
$agent->session[$key]=$val;
$obj=$agent->feature[$class];
if (isset($in['func']) &&
is_callable([$obj,$in['func']]))
$fw->call([$obj,$in['func']],[$fw,$agent]);
return;
}
else
if (isset($in['nonce']) &&
isset($agent->headers()['Cookie']) &&
preg_match(
'/PHPSESSID=(\w+)/',
$agent->headers()['Cookie'],
$match
) &&
Bcrypt::instance()->
verify($match[1],'$2y$12$'.$in['nonce'])) {
if (isset($in['session']))
foreach ($in['session'] as $key=>$val)
$agent->session[$key]=$val;
if (empty($agent->feature[$class]))
$agent->feature[$class]=new $class($fw,$agent);
return;
}
else
trace(
'(0x00'.$agent->uri().') '.$agent->id().' '.
'authentication failed');
}
}
})->
on('send',function($agent,$op,$data) use($fw) {
switch($op) {
case WS::Ping:
$text='ping';
break;
case WS::Text:
$data=trim($data);
case WS::Binary:
$text='data';
break;
default:
$text='unknown';
break;
}
trace(
'(0x'.str_pad(dechex($op),2,'0',STR_PAD_LEFT).
$agent->uri().') '.$agent->id().' '.$text.' sent'
);
if ($op==WS::Text && $fw->VERBOSE)
trace($data);
})->
on('invalid',function($agent,$op,$data) use($fw) {
trace(
'(0x'.str_pad(dechex($op),2,'0',STR_PAD_LEFT).
$agent->uri().') '.$agent->id().' invalid opcode'
);
})->
run();
}
else
trace('A socket server instance is already running!');
This is partly an answer to the question:
The timeout was ignored because php-cli uses a different php.ini, of course. I completely forgot this. a simple ini_set did the trick.

Required (included) mysql connect script working in one script but not in another. PHP

I have an extended login class which extends the standard mysqli class in php
which is the following:
<?php
require_once("Config.php");
define(MYSQL_HOST, "host");
define(MYSQL_USER, "user");
define(MYSQL_PW, "password");
define(MYSQL_DB, "db");
/**
* MysqlDB default wrapper, extend this if more specific needs are meant. Contains some helper functions for Select, Insert and Update.
* #author Jerrpan
* #version 1
*
*/
class DB extends mysqli
{
/**
* Connects to db specified in Config.php file
* #throws MysqlConnectError
* On failed connection
*/
function __Construct() {
parent::__construct(MYSQL_HOST, MYSQL_USER, MYSQL_PW, MYSQL_DB);
if($this->connect_error) {
throw new MysqlConnectError($this->connect_error); }
}
/**
* Finds out if db is up or not
*/
private function isCon() {
return $this->ping();
}
/**
* Executes an Select statment
* #param Assoc Array $data
* Array(
* "column" => "column1, column2",
* "table" => "table1, table2",
* "where" => "column1 = value",
* "join" => "INNERJOIN table ON column1 = column2",
* "orderby" => "id DESC",
* "limit" => 100
* )
* #throws MysqlConnectError
* On lost mysql connection
* #throws MysqlInvalidQuery
* On invalid query
* #return void|stmt()
* On success an stmt object for further useage like storing and fetching.
*/
public function Select($data) {
if(!$this->isCon()) { throw new MysqlConnectError("Connection shut down"); }
foreach($data as $key => $value) {
if($value{0} == ",") { throw new MysqlInvalidQuery("Start or stop char cant be ,"); }
if($value{strlen($value)} == ",") { throw new MysqlInvalidQuery("Start or stop char cant be ,"); }
}
if(isset($data["column"])) {
$column = " SELECT {$data["column"]} ";
} else {
throw new MysqlInvalidQuery("You have to specify columns");
}
if(isset($data["table"])) {
$table = " FROM {$data["table"]} ";
} else {
throw new MysqlInvalidQuery("You have to specify tables");
}
if(isset($data["where"])) {
$where = " WHERE {$data["where"]} ";
}
if(isset($data["join"])) {
$innerjoin = " {$data["innerjoin"]} ";
}
if(isset($data["orderby"])) {
$orderby = " {$data["orderby"]} ";
}
if(isset($data["limit"])) {
$limit = " LIMIT {$data["limit"]} ";
}
$stmt = $this->query("$column $table $where $innerjoin $orderby $limit");
if(!$stmt) { throw new MysqlInvalidQuery("Invalid query"); return;}
return $stmt;
}
/**
* Executes an insert statement
* #param String $table
* The table to insert data into, can only be one
* #param Assoc Array $data
* The data to insert as Colum => Value
* #throws MysqlConnectError
* On lost db connection
* #throws MysqlInvalidQuery
* On invalid query
* #return boolean|void
* True on success
*/
public function Insert($table, $data) {
$ins_table;
$ins_value;
if(!$this->isCon()) { throw new MysqlConnectError("Connection shut down"); }
if(strstr($table, ",") != false) { throw new MysqlInvalidQuery("Several tables cant be inserted in one query"); }
foreach($data as $key => $value) {
$ins_table .= "$key,";
$ins_value .= "$value,";
}
$ins_table = substr($ins_table, 0, -1);
$ins_value = substr($ins_value, 0, -1);
$this->query("INSERT INTO $table($ins_table) VALUES($ins_value)");
if($this->affected_rows == 1) { return true; } else { throw new MysqlInvalidQuery("Invalid query"); }
}
/**
* Executes an update statement
* #param String $table
* The table to update, can not be more than one
* #param Assoc Array $data
* The data to update the rows with in the context Column => Value
* #param String $where
* The where statement without the WHERE
* #param number $limit
* The limit, the default limit is 100
* #throws MysqlConnectError
* On lost connection
* #throws MysqlInvalidQuery
* On an invalid query
* #return boolean|void
* On success true, on no affected rows false
*/
public function Update($table, $data, $where, $limit = 100) {
$ins;
if(!$this->isCon()) { throw new MysqlConnectError("Connection shut down"); }
foreach($data as $key => $value) {
$ins .= " $key = $value , ";
}
$ins = substr($ins, 0, -2);
$stmt = $this->query("UPDATE $table SET $ins WHERE $where LIMIT $limit" );
if(!$stmt) { throw new MysqlInvalidQuery("Invalid query"); }
if($this->affected_rows > 0) { return true; }
return false;
}
}
class MysqlConnectError extends Exception {}
class MysqlInvalidQuery extends Exception {}
Which is then used by adding require_once('path/to/mysqlclass.php') to another script and create a new object of DB().
And so long everything is working fine. However when I try to
require_once('../path/to/mysqlpath.php) (notice ../)
I get the following error:
mysqli::mysqli(): php_network_getaddresses: getaddrinfo failed: Name or service not known in etc...
I am completely struck with this, so what is the cause of this problem
and how can I fix it?
After you require_once('../path/to/mysqlpath.php) please check the content of
MYSQL_HOST.
In general the error php_network_getaddresses: getaddrinfo failed means,
that your server is unable to resolve the domain name example.com to an ip address.
So either the dns server is unreachable or there's a misconfiguration in your webserver's dns client or the HOST name is wrong.

CakePHP switch database (using same datasource) on the fly?

I have tried to build a small function that would be use in controller to switch database on the fly, i must use only one datasource.
On my database.php :
function __construct() {
$server = Configure::read('Server');
if(!empty($server['database'])) $this->local['database'] = $server['database'];
$this->default = $this->{$server['datasource']};
}
Is used to switch database depending on server config. It is working great.
I tried to build up this :
/**
* Connects to specified database
*
* #param array $config Server config to use {datasource:?, database:?}
* #return array db->config on success, false on failure
* #access public
*/
function dbConnect($config = array()) {
ClassRegistry::init('ConnectionManager');
//debug($config['datasource']);
//$dbInstance =& ConnectionManager::getInstance();
//$dbInstance->config->{$config['datasource']}['database'] = $config['database'];
$db =& ConnectionManager::getDataSource($config['datasource']);
$db->disconnect();
$db->cacheSources = false;
$db->config['database'] = $config['database'];
$db->config['persistent'] = false;
debug($db->config);
$db->connect();
if(!$db->isConnected()) {
$this->error('!$db->isConnected()');
return false;
}
return $db->config;
}
But sadly, everything seems to work but i alwas get data from the same DB using $this->Player->find('list') for instance. I tried $this->Player->cacheQueries = false; with no more success.
Made it work using this (create a new connection on the fly) :
$newDbConfig = $this->dbConnect($serverConfig);
$this->Model->useDbConfig = $newDbConfig['name'];
$this->Model->cacheQueries = false;
With :
/**
* Connects to specified database
*
* #param array $config Server config to use {datasource:?, database:?}
* #return array db->config on success, false on failure
* #access public
*/
function dbConnect($config = array()) {
ClassRegistry::init('ConnectionManager');
$nds = $config['datasource'] . '_' . $config['database'];
$db =& ConnectionManager::getDataSource($config['datasource']);
$db->setConfig(array('name' => $nds, 'database' => $config['database'], 'persistent' => false));
if($ds = ConnectionManager::create($nds, $db->config)) return $db->config;
return false;
}
I just wanted to tell that nowadays it's really simple to change current model's datasource (database connection):
For example in my PersonsController index:
public function index() {
//change to a different datasource which is already written in App/Config/database.php-file
$this->Person->setDataSource('testdb2');
$persons = $this->Person->find('all');
$this->set('db1persons', $persons);
//change to a different database
$this->Person->setDataSource('testdb');
$persons = $this->Person->find('all');
$this->set('db2persons', $persons);
}
As you can see the key here is to use $this->Model->setDataSource()

Categories