diff --git a/src/framework/src/Contract/SessionStorageInterface.php b/src/framework/src/Contract/SessionStorageInterface.php index 82e59f2cd..302674ad7 100644 --- a/src/framework/src/Contract/SessionStorageInterface.php +++ b/src/framework/src/Contract/SessionStorageInterface.php @@ -28,7 +28,6 @@ public function read(string $sessionId): string; * @param string $sessionId The session id. * @param string $sessionData The encoded session data. This data is a serialized * string and passing it as this parameter. - * Please note sessions use an alternative serialization method. * * @return bool * The return value (usually TRUE on success, FALSE on failure). diff --git a/src/framework/src/Session/ArrayStorage.php b/src/framework/src/Session/ArrayStorage.php new file mode 100644 index 000000000..e3d7c9c27 --- /dev/null +++ b/src/framework/src/Session/ArrayStorage.php @@ -0,0 +1,97 @@ + session data, ...] + * + * @var array + */ + private $map = []; + + /** + * Read session data + * + * @param string $sessionId The session id to read data for. + * + * @return string + * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + */ + public function read(string $sessionId): string + { + return $this->map[$sessionId] ?? ''; + } + + /** + * Write session data + * + * @param string $sessionId The session id. + * @param string $sessionData The encoded session data. This data is a serialized + * string and passing it as this parameter. + * Please note sessions use an alternative serialization method. + * + * @return bool + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + */ + public function write(string $sessionId, string $sessionData): bool + { + $this->map[$sessionId] = $sessionData; + + return true; + } + + /** + * Destroy a session + * + * @param string $sessionId The session ID being destroyed. + * + * @return bool + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + */ + public function destroy(string $sessionId): bool + { + if (isset($this->map[$sessionId])) { + unset($this->map[$sessionId]); + return true; + } + + return false; + } + + /** + * Whether the session exists + * + * @param string $sessionId + * + * @return bool + */ + public function exists(string $sessionId): bool + { + return isset($this->map[$sessionId]); + } + + /** + * Clear all session + * + * @return bool + */ + public function clear(): bool + { + $this->map = []; + return true; + } +} diff --git a/src/framework/src/Session/Session.php b/src/framework/src/Session/Session.php index 351ae22fa..a932c32aa 100644 --- a/src/framework/src/Session/Session.php +++ b/src/framework/src/Session/Session.php @@ -2,9 +2,9 @@ namespace Swoft\Session; +use Swoft; use Swoft\Co; use Swoft\Contract\SessionInterface; -use Swoft\Exception\SessionException; use Swoft\WebSocket\Server\Connection; /** @@ -12,8 +12,12 @@ * * @since 2.0 */ -class Session +final class Session { + // The global session manager and storage bean name + public const ManagerBean = 'gSessionManager'; + public const StorageBean = 'gSessionStorage'; + /** * The map for coroutineID to SessionID * @@ -22,21 +26,12 @@ class Session private static $idMap = []; /** - * Session connection list - * - * @var SessionInterface[] - * - * @example - * [ - * // Such as webSocket connection - * 'fd' => SessionInterface, - * 'fd2' => SessionInterface, - * 'fd3' => SessionInterface, - * // Such as http session - * 'sess id' => SessionInterface, - * ] + * @return SessionManager */ - private static $sessions = []; + public static function getManager(): SessionManager + { + return Swoft::getBean(self::ManagerBean); + } /***************************************************************************** * SID and CID relationship manage @@ -94,20 +89,21 @@ public static function getBoundedSid(): string */ public static function has(string $sid): bool { - return isset(self::$sessions[$sid]); + return self::getManager()->has($sid); } /** * Get session by FD * * @param string $sid If not specified, return the current corresponding session + * * @return SessionInterface|Connection */ public static function get(string $sid = ''): ?SessionInterface { $sid = $sid ?: self::getBoundedSid(); - return self::$sessions[$sid] ?? null; + return self::getManager()->get($sid); } /** @@ -118,28 +114,23 @@ public static function get(string $sid = ''): ?SessionInterface public static function current(): SessionInterface { $sid = self::getBoundedSid(); - if (isset(self::$sessions[$sid])) { - return self::$sessions[$sid]; - } - throw new SessionException('session information has been lost of the SID: ' . $sid); + return self::getManager()->mustGet($sid); } /** * Get connection by FD. if not found will throw exception. + * NOTICE: recommend use Session::current() instead of the method. * * @param string $sid + * * @return SessionInterface|Connection */ public static function mustGet(string $sid = ''): SessionInterface { $sid = $sid ?: self::getBoundedSid(); - if (isset(self::$sessions[$sid])) { - return self::$sessions[$sid]; - } - - throw new SessionException('session information has been lost of the SID: ' . $sid); + return self::getManager()->mustGet($sid); } /** @@ -150,26 +141,23 @@ public static function mustGet(string $sid = ''): SessionInterface */ public static function set(string $sid, SessionInterface $session): void { - self::$sessions[$sid] = $session; + // self::$sessions[$sid] = $session; + self::getManager()->set($sid, $session); // Bind cid => sid(fd) self::bindCo($sid); } /** - * Destroy session + * Destroy session by sessionId * * @param string $sid If empty, destroy current CID relationship session + * + * @return bool */ - public static function destroy(string $sid = ''): void + public static function destroy(string $sid): bool { - $sid = $sid ?: self::getBoundedSid(); - - if (isset(self::$sessions[$sid])) { - // Clear self data. - self::$sessions[$sid]->clear(); - unset(self::$sessions[$sid]); - } + return self::getManager()->destroy($sid); } /** @@ -177,7 +165,19 @@ public static function destroy(string $sid = ''): void */ public static function clear(): void { - self::$idMap = self::$sessions = []; + self::$idMap = []; + + self::getManager()->clear(); + } + + /** + * Clear all caches + */ + public static function clearCaches(): void + { + self::$idMap = []; + + self::getManager()->clearCaches(); } /** @@ -189,10 +189,12 @@ public static function getIdMap(): array } /** + * Only get all sessions in current worker memory. + * * @return SessionInterface[] */ public static function getSessions(): array { - return self::$sessions; + return self::getManager()->getCaches(); } } diff --git a/src/framework/src/Session/SessionManager.php b/src/framework/src/Session/SessionManager.php new file mode 100644 index 000000000..d125bb09a --- /dev/null +++ b/src/framework/src/Session/SessionManager.php @@ -0,0 +1,224 @@ +storage property is not empty. + if (!$this->storage) { + $this->storage = new ArrayStorage(); + // $this->arrayStorage = true; + } + } + + /** + * @param string $sessionId + * + * @return bool + */ + public function has(string $sessionId): bool + { + if (isset($this->caches[$sessionId])) { + return true; + } + + return $this->storage->exists($sessionId); + } + + /** + * @param string $sessionId The session Id. eg: swoole.fd, http session id + * @param SessionInterface $session + * + * @return bool + */ + public function set(string $sessionId, SessionInterface $session): bool + { + return $this->getStorage()->write($sessionId, $session->toString()); + } + + /** + * @param string $sessionId The session Id. eg: swoole.fd, http session id + * + * @return SessionInterface|null + */ + public function get(string $sessionId): ?SessionInterface + { + // Read from caches + if (isset($this->caches[$sessionId])) { + return $this->caches[$sessionId]; + } + + // Read from storage + $sessionData = $this->getStorage()->read($sessionId); + + if ($sessionData) { + /** @var SessionInterface $class */ + $class = $this->sessionClass; + $data = JsonHelper::decode($sessionData, true); + $sess = $class::newFromArray($data); + + $this->caches[$sessionId] = $sess; + return $sess; + } + + return null; + } + + /** + * @param string $sessionId + * + * @return SessionInterface + */ + public function mustGet(string $sessionId): SessionInterface + { + if ($session = $this->get($sessionId)) { + return $session; + } + + // throw new UnexpectedValueException('The session is not exists! sessionId is ' . $sessionId); + throw new SessionException('session information has been lost of the SID: ' . $sessionId); + } + + /** + * @param string $sessionId + * + * @return bool + */ + public function destroy(string $sessionId): bool + { + if (isset($this->caches[$sessionId])) { + // Clear self data + $this->caches[$sessionId]->clear(); + unset($this->caches[$sessionId]); + } + + return $this->storage->destroy($sessionId); + } + + /** + * @return bool + */ + public function clear(): bool + { + $this->caches = []; + + return $this->storage->clear(); + } + + /** + * @param string $sessionId + * + * @return bool + */ + public function hasCache(string $sessionId): bool + { + return isset($this->caches[$sessionId]); + } + + /** + * @return bool + */ + public function clearCaches(): bool + { + $this->caches = []; + return true; + } + + /** + * @return int + */ + public function countCaches(): int + { + return count($this->caches); + } + + /** + * @return SessionInterface[] + */ + public function getCaches(): array + { + return $this->caches; + } + + /** + * @return SessionStorageInterface + */ + public function getStorage(): SessionStorageInterface + { + return $this->storage; + } + + /** + * @param SessionStorageInterface $storage + */ + public function setStorage(SessionStorageInterface $storage): void + { + $this->storage = $storage; + } + + /** + * @return string + */ + public function getSessionClass(): string + { + return $this->sessionClass; + } + + /** + * @param string $sessionClass + */ + public function setSessionClass(string $sessionClass): void + { + if (!$sessionClass) { + return; + } + + // Check class + if (!class_exists($sessionClass) || !is_subclass_of($sessionClass, SessionInterface::class)) { + throw new InvalidArgumentException('The session class must be implemented ' . SessionInterface::class); + } + + $this->sessionClass = $sessionClass; + } +} diff --git a/src/framework/src/Session/SwooleStorage.php b/src/framework/src/Session/SwooleStorage.php new file mode 100644 index 000000000..dc62b9fa2 --- /dev/null +++ b/src/framework/src/Session/SwooleStorage.php @@ -0,0 +1,171 @@ +create(); + } + + /** + * create table + */ + public function create(): void + { + $this->db = new Table($this->tableSize); + + // add columns + $this->db->column(self::KEY_FIELD, Table::TYPE_STRING, 48); + $this->db->column(self::VAL_FIELD, Table::TYPE_STRING, $this->valueSize); + + // Create it + $this->db->create(); + } + + /** + * Read session data + * + * @param string $sessionId The session id to read data for. + * + * @return string + * Returns an encoded string of the read data. + * If nothing was read, it must return an empty string. + * Note this value is returned internally to PHP for processing. + */ + public function read(string $sessionId): string + { + if ($data = $this->db->get($sessionId, self::VAL_FIELD)) { + return $data; + } + + return ''; + } + + /** + * Write session data + * + * @param string $sessionId The session id. + * @param string $sessionData The encoded session data. This data is a serialized + * string and passing it as this parameter. + * + * @return bool + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + */ + public function write(string $sessionId, string $sessionData): bool + { + return $this->db->set($sessionId, [ + self::KEY_FIELD => $sessionId, + self::VAL_FIELD => $sessionData, + ]); + } + + /** + * Destroy a session + * + * @param string $sessionId The session ID being destroyed. + * + * @return bool + * The return value (usually TRUE on success, FALSE on failure). + * Note this value is returned internally to PHP for processing. + */ + public function destroy(string $sessionId): bool + { + return $this->db->del($sessionId); + } + + /** + * Whether the session exists + * + * @param string $sessionId + * + * @return bool + */ + public function exists(string $sessionId): bool + { + return $this->db->exist($sessionId); + } + + /** + * clear table data + */ + public function clear(): bool + { + foreach ($this->db as $row) { + $this->db->del($row[self::KEY_FIELD]); + } + + return true; + } + + /** + * @return Table + */ + public function getDb(): Table + { + return $this->db; + } + + /** + * @return int + */ + public function getTableSize(): int + { + return $this->tableSize; + } + + /** + * @param int $tableSize + */ + public function setTableSize(int $tableSize): void + { + $this->tableSize = $tableSize; + } + + /** + * @return int + */ + public function getValueSize(): int + { + return $this->valueSize; + } + + /** + * @param int $valueSize + */ + public function setValueSize(int $valueSize): void + { + $this->valueSize = $valueSize; + } +} diff --git a/src/http-server/src/HttpDispatcher.php b/src/http-server/src/HttpDispatcher.php index cae844281..edee580fe 100644 --- a/src/http-server/src/HttpDispatcher.php +++ b/src/http-server/src/HttpDispatcher.php @@ -146,7 +146,6 @@ private function afterRequest(Response $response): void * @param Request $request * * @return Request - * @throws SwoftException */ private function matchRouter(Request $request): Request { diff --git a/src/server/src/Server.php b/src/server/src/Server.php index a856eddf9..d6a774044 100644 --- a/src/server/src/Server.php +++ b/src/server/src/Server.php @@ -830,8 +830,6 @@ public function startWithDaemonize(): void /** * Quick restart - * - * @throws Swoft\Exception\SwoftException */ public function restart(): void { diff --git a/src/websocket-server/src/ConnectionManager.php b/src/websocket-server/src/ConnectionManager.php new file mode 100644 index 000000000..f2daf4361 --- /dev/null +++ b/src/websocket-server/src/ConnectionManager.php @@ -0,0 +1,8 @@ +