public function handleRouterStop(RouterStopEvent $event) { if ($this->session) { $this->session->shutdown(); } $this->internalClient->onClose("router stopped"); }
/** * Handle open transport * * @param \Thruway\Transport\TransportInterface $transport */ public function onOpen(TransportInterface $transport) { $session = new Session($transport, $this->manager); // give the session the loop, just in case it wants to set a timer or something $session->setLoop($this->getLoop()); // TODO: add a little more detail to this (what kind and address maybe?) Logger::info($this, "New Session started " . json_encode($transport->getTransportDetails()) . ""); $this->sessions->attach($transport, $session); }
/** * @depends testJoin * * @param \Thruway\Session $session */ public function testGoodbyeMessage(\Thruway\Session $session) { $realm = $session->getRealm(); $sessions = $realm->managerGetSessions(); $this->assertEquals(1, count($sessions)); $goodbyeMessage = new \Thruway\Message\GoodbyeMessage([], 'some_test_reason'); $realm->handleGoodbyeMessage(new \Thruway\Event\MessageEvent($session, $goodbyeMessage)); $sessions = $realm->managerGetSessions(); $this->assertEquals(0, count($sessions)); }
function __construct($topic, Session $session, $options = null) { $this->topic = $topic; $this->session = $session; $this->options = new \stdClass(); $this->id = Session::getUniqueId(); }
public function testMakingCallIncrementsCallCount() { $mockSession = $this->getMockBuilder('\\Thruway\\Session')->setConstructorArgs([new \Thruway\Transport\DummyTransport()])->getMock(); $this->assertEquals(0, $this->_registration->getCurrentCallCount()); $callMsg = new \Thruway\Message\CallMessage(\Thruway\Session::getUniqueId(), new \stdClass(), 'test_procedure'); $call = new \Thruway\Call($mockSession, $callMsg); $this->_registration->processCall($call); $this->assertEquals(1, $this->_registration->getCurrentCallCount()); }
public function testQueueProcessAfterNonMultiYield() { $seq = 0; // there will be two callees //// Mocking $realm = $this->getMockBuilder('\\Thruway\\Realm')->setConstructorArgs(["theRealm"])->setMethods(["publishMeta"])->getMock(); $sessionMockBuilder = $this->getMockBuilder('\\Thruway\\Session')->setMethods(['sendMessage'])->disableOriginalConstructor(); $callee0Session = $sessionMockBuilder->getMock(); $callee1Session = $sessionMockBuilder->getMock(); $callerSession = $sessionMockBuilder->getMock(); $callee0Session->setRealm($realm); $callee1Session->setRealm($realm); $callerSession->setRealm($realm); //// End of Mocking $realm->expects($this->exactly(2))->method("publishMeta")->with($this->equalTo('thruway.metaevent.procedure.congestion'), $this->equalTo([["name" => "qpanmy_proc0"]])); $invocationIDs = []; $callee0Session->expects($this->exactly(6))->method("sendMessage")->withConsecutive([$this->isInstanceOf('\\Thruway\\Message\\RegisteredMessage')], [$this->callback(function ($value) use(&$invocationIDs) { $this->assertInstanceOf('\\Thruway\\Message\\InvocationMessage', $value); $invocationIDs[0] = $value->getRequestId(); return true; })], [$this->callback(function ($value) use(&$invocationIDs) { $this->assertInstanceOf('\\Thruway\\Message\\InvocationMessage', $value); $invocationIDs[1] = $value->getRequestId(); return true; })], [$this->isInstanceOf('\\Thruway\\Message\\RegisteredMessage')], [$this->callback(function ($value) use(&$invocationIDs) { $this->assertInstanceOf('\\Thruway\\Message\\InvocationMessage', $value); $invocationIDs[2] = $value->getRequestId(); return true; })], [$this->callback(function ($value) use(&$invocationIDs) { // invocation after yield $this->assertInstanceOf('\\Thruway\\Message\\InvocationMessage', $value); $invocationIDs[3] = $value->getRequestId(); return true; })]); // create a dealer $dealer = new \Thruway\Role\Dealer(); // register proc0 as multi $registerMsg = new \Thruway\Message\RegisterMessage(Session::getUniqueId(), ["thruway_multiregister" => true], "qpanmy_proc0"); $dealer->onMessage($callee0Session, $registerMsg); $callMsg = new \Thruway\Message\CallMessage(Session::getUniqueId(), [], "qpanmy_proc0"); $dealer->onMessage($callerSession, $callMsg); $callMsg->setRequestId(Session::getUniqueId()); $dealer->onMessage($callerSession, $callMsg); $dealer->onMessage($callee0Session, new \Thruway\Message\YieldMessage($invocationIDs[0], [])); $dealer->onMessage($callee0Session, new \Thruway\Message\YieldMessage($invocationIDs[1], [])); // there are now zero calls on proc0 $registerMsg = new \Thruway\Message\RegisterMessage(Session::getUniqueId(), [], "qpanmy_proc1"); $callProc1Msg = new \Thruway\Message\CallMessage(Session::getUniqueId(), [], "qpanmy_proc1"); $dealer->onMessage($callee0Session, $registerMsg); $dealer->onMessage($callerSession, $callProc1Msg); // this should cause congestion and queuing because it should be busy with proc1 $callMsg->setRequestId(Session::getUniqueId()); $dealer->onMessage($callerSession, $callMsg); // yield on proc1 - this should cause proc0 to process queue $dealer->onMessage($callee0Session, new \Thruway\Message\YieldMessage($invocationIDs[2], [])); }
/** * Check to see if an action is authorized on a specific uri given the * context of the session attempting the action * * actionMsg should be an instance of: register, call, subscribe, or publish messages * * @param Session $session * @param ActionMessageInterface $actionMsg * @throws \Exception * @return boolean */ public function isAuthorizedTo(Session $session, ActionMessageInterface $actionMsg) { // authorization $action = $actionMsg->getActionName(); $uri = $actionMsg->getUri(); $authenticationDetails = $session->getAuthenticationDetails(); // admin can do anything - pretty important // if this isn't here - then we can't setup any other rules if ($authenticationDetails->hasAuthRole('admin')) { return true; } if (!$this->isReady()) { return false; } $rolesToCheck = ["default"]; if (count($authenticationDetails->getAuthRoles()) > 0) { $rolesToCheck = array_merge($rolesToCheck, $authenticationDetails->getAuthRoles()); } return $this->isAuthorizedByRolesActionAndUri($rolesToCheck, $action, $uri); }
/** * Constructor * * @param \Thruway\Session $callerSession * @param \Thruway\Message\CallMessage $callMessage * @param Registration $registration */ public function __construct(Session $callerSession, CallMessage $callMessage, Registration $registration = null) { $this->callMessage = $callMessage; $this->callerSession = $callerSession; $this->invocationMessage = null; $this->calleeSession = null; $this->isProgressive = false; $this->setRegistration($registration); $this->callStart = microtime(true); $this->invocationRequestId = Session::getUniqueId(); }
/** * Constructor * * @param \Thruway\Transport\TransportInterface $transport * @param \Thruway\Manager\ManagerInterface $manager */ public function __construct(TransportInterface $transport, ManagerInterface $manager = null) { $this->transport = $transport; $this->state = static::STATE_PRE_HELLO; $this->sessionId = Session::getUniqueId(); $this->realm = null; $this->messagesSent = 0; $this->sessionStart = new \DateTime(); $this->authenticationDetails = null; $this->pendingCallCount = 0; if ($manager === null) { $manager = new ManagerDummy(); } $this->setManager($manager); }
/** * Constructor * * @param \Thruway\Session $session * @param string $procedureName */ public function __construct(Session $session, $procedureName) { $this->id = Session::getUniqueId(); $this->session = $session; $this->procedureName = $procedureName; $this->allowMultipleRegistrations = false; $this->discloseCaller = false; $this->calls = []; $this->registeredAt = new \DateTime(); $this->invocationCount = 0; $this->busyTime = 0; $this->invocationAverageTime = 0; $this->maxSimultaneousCalls = 0; $this->lastCallStartedAt = null; $this->lastIdledAt = $this->registeredAt; $this->busyStart = null; $this->completedCallTimeTotal = 0; }
public function testDoNotExcludeMe() { $transport = $this->getMockBuilder('\\Thruway\\Transport\\TransportInterface')->getMock(); $transport->expects($this->any())->method("getTransportDetails")->will($this->returnValue("")); $session = $this->getMockBuilder('\\Thruway\\Session')->setMethods(["sendMessage"])->setConstructorArgs([$transport])->getMock(); $broker = new \Thruway\Role\Broker(); $subscribeMsg = new \Thruway\Message\SubscribeMessage('\\Thruway\\Session', [], "test_subscription"); /** @var \Thruway\Message\SubscribedMessage $subscribedMsg */ $subscribedMsg = null; $session->expects($this->exactly(3))->method("sendMessage")->withConsecutive([$this->callback(function ($msg) use(&$subscribedMsg) { $this->isInstanceOf('\\Thruway\\Message\\SubscribedMessage'); $subscribedMsg = $msg; return true; })], [$this->isInstanceOf('\\Thruway\\Message\\PublishedMessage')], [$this->isInstanceOf('\\Thruway\\Message\\EventMessage')]); $broker->onMessage($session, $subscribeMsg); $subscriptionId = $subscribedMsg->getSubscriptionId(); $publishMsg = new \Thruway\Message\PublishMessage(\Thruway\Session::getUniqueId(), ['exclude_me' => false, 'acknowledge' => true], 'test_subscription'); $broker->onMessage($session, $publishMsg); }
/** * Remove call * * @param \Thruway\Call $callToRemove */ public function removeCall($callToRemove) { /* @var $call \Thruway\Call */ foreach ($this->calls as $i => $call) { if ($callToRemove === $call) { array_splice($this->calls, $i, 1); $this->session->decPendingCallCount(); $callEnd = microtime(true); // average call time $callsInAverage = $this->invocationCount - count($this->calls) - 1; // add this call time into the total $this->completedCallTimeTotal += $callEnd - $call->getCallStart(); $callsInAverage++; $this->invocationAverageTime = (double) $this->completedCallTimeTotal / $callsInAverage; if (count($this->calls) == 0) { $this->lastIdledAt = new \DateTime(); if ($this->busyStart !== null) { $this->busyTime = $this->busyTime + ($callEnd - $this->busyStart); $this->busyStart = null; } } } } }
private function processInvocationError(Session $session, ErrorMessage $msg) { $call = $this->getCallByRequestId($msg->getRequestId()); if (!$call) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); $this->manager->error('No call for invocation error message: ' . $msg->getRequestId()); // TODO: do we send a message back to the callee? $errorMsg->setErrorURI('wamp.error.no_such_procedure'); $session->sendMessage($errorMsg); return false; } $this->calls->detach($call); $errorMsg = ErrorMessage::createErrorMessageFromMessage($call->getCallMessage()); $errorMsg->setErrorURI($msg->getErrorURI()); $errorMsg->setArguments($msg->getArguments()); $errorMsg->setArgumentsKw($msg->getArgumentsKw()); $call->getCallerSession()->sendMessage($errorMsg); }
/** * Process on session leave * * @param \Thruway\Session $session */ public function leave(Session $session) { Logger::debug($this, "Leaving realm {$session->getRealm()->getRealmName()}"); $this->sessions->detach($session); }
public function testCallBeforeWelcome() { $realm = new \Thruway\Realm("realm1"); $session = $this->getMockBuilder('\\Thruway\\Session')->disableOriginalConstructor()->setMethods(["sendMessage", "shutdown", "abort"])->getMock(); $session->expects($this->once())->method("abort")->with($this->isInstanceOf("stdClass"), $this->equalTo("wamp.error.not_authorized")); $realm->onMessage($session, new \Thruway\Message\CallMessage(\Thruway\Session::getUniqueId(), [], 'some_procedure')); }
/** * @param $topicName * @param $callback */ public function subscribe(ClientSession $session, $topicName, $callback) { $requestId = Session::getUniqueId(); $options = new \stdClass(); $subscription = ["topic_name" => $topicName, "callback" => $callback, "request_id" => $requestId, "options" => $options]; array_push($this->subscriptions, $subscription); $subscribeMsg = new SubscribeMessage($requestId, $options, $topicName); $session->sendMessage($subscribeMsg); }
public function testLeave() { $session = $this->getMockBuilder('\\Thruway\\Session')->disableOriginalConstructor()->getMock(); $session->expects($this->once())->method("sendMessage")->with($this->isInstanceOf('\\Thruway\\Message\\RegisteredMessage')); $registerMsg = new \Thruway\Message\RegisterMessage(\Thruway\Session::getUniqueId(), [], 'test_procedure'); $this->_proc->processRegister($session, $registerMsg); $this->assertEquals(1, count($this->_proc->getRegistrations())); $this->_proc->leave($session); $this->assertEquals(0, count($this->_proc->getRegistrations())); }
public function handleRouterStop(RouterStopEvent $event) { if ($this->session) { $this->session->shutdown(); } }
/** * @inheritdoc */ public function createNewSession(TransportInterface $transport) { $session = new Session($transport); $session->setLoop($this->getLoop()); return $session; }
/** * Process subscribe message * * @param \Thruway\Session $session * @param \Thruway\Message\SubscribeMessage $msg * @throws \Exception */ protected function processSubscribe(Session $session, SubscribeMessage $msg) { // get a subscription group "hash" /** @var MatcherInterface $matcher */ $matcher = $this->getMatcherForMatchType($msg->getMatchType()); if ($matcher === null) { Logger::alert($this, "no matching match type for \"" . $msg->getMatchType() . "\" for URI \"" . $msg->getUri() . "\""); return; } if (!$matcher->uriIsValid($msg->getUri(), $msg->getOptions())) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); $session->sendMessage($errorMsg->setErrorURI('wamp.error.invalid_uri')); return; } $matchHash = $matcher->getMatchHash($msg->getUri(), $msg->getOptions()); if (!isset($this->subscriptionGroups[$matchHash])) { $this->subscriptionGroups[$matchHash] = new SubscriptionGroup($matcher, $msg->getUri(), $msg->getOptions()); } /** @var SubscriptionGroup $subscriptionGroup */ $subscriptionGroup = $this->subscriptionGroups[$matchHash]; $subscription = $subscriptionGroup->processSubscribe($session, $msg); $registry = $this->getStateHandlerRegistry(); if ($registry !== null) { $registry->processSubscriptionAdded($subscription); } }
/** * processCancel processes cancel message from the caller. * Return true if the Call should be removed from active calls * * @param Session $session * @param CancelMessage $msg * @return bool */ public function processCancel(Session $session, CancelMessage $msg) { if ($this->getCallerSession() !== $session) { Logger::warning($this, "session attempted to cancel call they did not own."); return false; } if ($this->getCalleeSession() === null) { // this call has not been sent to a callee yet (it is in a queue) // we can just kill it and say it was canceled $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg, "wamp.error.canceled"); $details = $errorMsg->getDetails() ?: (object) []; $details->_thruway_removed_from_queue = true; $session->sendMessage($errorMsg); return true; } $details = (object) []; if ($this->getCalleeSession()->getHelloMessage() instanceof HelloMessage) { $details = $this->getCalleeSession()->getHelloMessage()->getDetails(); } $calleeSupportsCancel = false; if (isset($details->roles->callee->features->call_canceling) && is_scalar($details->roles->callee->features->call_canceling)) { $calleeSupportsCancel = (bool) $details->roles->callee->features->call_canceling; } if (!$calleeSupportsCancel) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); $errorMsg->setErrorURI('wamp.error.not_supported'); $session->sendMessage($errorMsg); return false; } $this->setCancelMessage($msg); $this->canceling = true; $calleeSession = $this->getCalleeSession(); $interruptMessage = new InterruptMessage($this->getInvocationRequestId(), (object) []); $calleeSession->sendMessage($interruptMessage); $this->setInterruptMessage($interruptMessage); if (isset($msg->getOptions()->mode) && is_scalar($msg->getOptions()->mode) && $msg->getOptions()->mode == "killnowait") { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg, "wamp.error.canceled"); $session->sendMessage($errorMsg); return true; } return false; }
/** * This allows the AuthenticationManager to clean out auth methods that were registered by * sessions that are dieing. Otherwise the method could be hijacked by another client in the * thruway.auth realm. * * @param \Thruway\Session $session */ public function onSessionClose(Session $session) { if ($session->getRealm() && $session->getRealm()->getRealmName() == "thruway.auth") { // session is closing in the auth domain // check and see if there are any registrations that came from this session $sessionId = $session->getSessionId(); foreach ($this->authMethods as $methodName => $method) { if (isset($method['session_id']) && $method['session_id'] == $sessionId) { unset($this->authMethods[$methodName]); } } } }
/** * process subscribe * * @param \Thruway\ClientSession $session * @param string $topicName * @param callable $callback * @param $options * @return Promise */ public function subscribe(ClientSession $session, $topicName, $callback, $options) { $requestId = Session::getUniqueId(); $options = (object) $options; $deferred = new Deferred(); $subscription = ["topic_name" => $topicName, "callback" => $callback, "request_id" => $requestId, "options" => $options, "deferred" => $deferred]; array_push($this->subscriptions, $subscription); $subscribeMsg = new SubscribeMessage($requestId, $options, $topicName); $session->sendMessage($subscribeMsg); return $deferred->promise(); }
/** * process call * * @param \Thruway\ClientSession $session * @param string $procedureName * @param mixed $arguments * @param mixed $argumentsKw * @param mixed $options * @return \React\Promise\Promise */ public function call(ClientSession $session, $procedureName, $arguments = null, $argumentsKw = null, $options = null) { //This promise gets resolved in Caller::processResult $futureResult = new Deferred(); $requestId = Session::getUniqueId(); $this->callRequests[$requestId] = ["procedure_name" => $procedureName, "future_result" => $futureResult]; if (!(is_array($options) && Message::isAssoc($options))) { if ($options !== null) { Logger::warning($this, "Options don't appear to be the correct type."); } $options = new \stdClass(); } $callMsg = new CallMessage($requestId, $options, $procedureName, $arguments, $argumentsKw); $session->sendMessage($callMsg); return $futureResult->promise(); }
/** * @param Session $session * @param UnsubscribeMessage $msg * @return UnsubscribedMessage */ public function processUnsubscribe(Session $session, UnsubscribeMessage $msg) { $subscription = $this->getSubscriptionById($msg->getSubscriptionId()); if (!$subscription || !isset($this->topics[$subscription->getTopic()])) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); $session->sendMessage($errorMsg->setErrorURI('wamp.error.no_such_subscription')); } $topicName = $subscription->getTopic(); $subscribers = $this->topics[$topicName]; /* @var $subscriber Session */ foreach ($this->topics[$topicName] as $key => $subscriber) { if ($subscriber == $session) { unset($subscribers[$key]); } } $this->subscriptions->detach($subscription); $session->sendMessage(new UnsubscribedMessage($msg->getRequestId())); }
/** * Process call * * @param \Thruway\Session $session * @param Call $call * @throws \Exception * @return bool | Call */ public function processCall(Session $session, Call $call) { // find a registration to call if (count($this->registrations) == 0) { $session->sendMessage(ErrorMessage::createErrorMessageFromMessage($call->getCallMessage(), 'wamp.error.no_such_procedure')); return false; } // just send it to the first one if we don't allow multiple registrations if (!$this->getAllowMultipleRegistrations()) { $this->registrations[0]->processCall($call); } else { $this->callQueue->enqueue($call); $this->processQueue(); } return true; }
/** * @param CallMessage $msg * @param Registration $registration * @return static */ static function createMessageFrom(CallMessage $msg, Registration $registration) { $requestId = Session::getUniqueId(); $details = new \stdClass(); return new static($requestId, $registration->getId(), $details, $msg->getArguments(), $msg->getArgumentsKw()); }
/** * Process InvocationError * * @param \Thruway\Session $session * @param \Thruway\Message\ErrorMessage $msg */ private function processInvocationError(Session $session, ErrorMessage $msg) { //$call = $this->getCallByRequestId($msg->getRequestId()); $call = $this->callInvocationIndex[$msg->getRequestId()]; if (!$call) { $errorMsg = ErrorMessage::createErrorMessageFromMessage($msg); Logger::error($this, 'No call for invocation error message: ' . $msg->getRequestId()); // TODO: do we send a message back to the callee? $errorMsg->setErrorURI('wamp.error.no_such_procedure'); $session->sendMessage($errorMsg); return; } if ($call->getCalleeSession() !== $session) { Logger::error($this, "Attempted Invocation Error from session that does not own the call"); return; } $call->getRegistration()->removeCall($call); $this->removeCall($call); $errorMsg = ErrorMessage::createErrorMessageFromMessage($call->getCallMessage()); $errorMsg->setErrorURI($msg->getErrorURI()); $errorMsg->setArguments($msg->getArguments()); $errorMsg->setArgumentsKw($msg->getArgumentsKw()); // not sure if this detail should pass through $errorMsg->setDetails($msg->getDetails()); $call->getCallerSession()->sendMessage($errorMsg); }
/** * process unregister * * @param \Thruway\ClientSession $session * @param string $Uri * @throws \Exception * @return \React\Promise\Promise|false */ public function unregister(ClientSession $session, $Uri) { // TODO: maybe add an option to wait for pending calls to finish $registration = null; foreach ($this->registrations as $k => $r) { if (isset($r['procedure_name'])) { if ($r['procedure_name'] == $Uri) { $registration =& $this->registrations[$k]; break; } } } if ($registration === null) { Logger::warning($this, "registration not found: " . $Uri); return false; } // we remove the callback from the client here // because we don't want the client to respond to any more calls $registration['callback'] = null; $futureResult = new Deferred(); if (!isset($registration["registration_id"])) { // this would happen if the registration was never acknowledged by the router // we should remove the registration and resolve any pending deferreds Logger::error($this, "Registration ID is not set while attempting to unregister " . $Uri); // reject the pending registration $registration['futureResult']->reject(); // TODO: need to figure out what to do in this off chance // We should still probably return a promise here that just rejects // there is an issue with the pending registration too that // the router may have a "REGISTERED" in transit and may still think that is // good to go - so maybe still send the unregister? } $requestId = Session::getUniqueId(); // save the request id so we can find this in the registration // list to call the deferred and remove it from the list $registration['unregister_request_id'] = $requestId; $registration['unregister_deferred'] = $futureResult; $unregisterMsg = new UnregisterMessage($requestId, $registration['registration_id']); $session->sendMessage($unregisterMsg); return $futureResult->promise(); }
/** * Publish meta * * @param string $topicName * @param mixed $arguments * @param mixed $argumentsKw * @param mixed $options */ public function publishMeta($topicName, $arguments, $argumentsKw = null, $options = null) { if ($this->metaSession === null) { // setup a new metaSession $s = new Session(new DummyTransport()); $this->metaSession = $s; } $this->getBroker()->onMessage($this->metaSession, new PublishMessage(Session::getUniqueId(), $options, $topicName, $arguments, $argumentsKw)); }