header('x-forwarded-proto', '')); if ($proto === '') { $proto = strtolower((string) $request->header('x-forwarded-protocol', '')); } $isHttps = $proto === 'https' || strtolower((string) $request->header('x-forwarded-ssl', '')) === 'on' || strtolower((string) $request->header('x-scheme', '')) === 'https'; $scheme = $isHttps ? 'wss' : 'ws'; $host = trim((string) $request->header('host', '')); if ($host === '') { $host = trim((string) $request->header('x-forwarded-host', '')); } if ($host !== '') { return $scheme . '://' . $host . '/ws/'; } } return 'ws://127.0.0.1:3131/ws/'; } /** * 在基础 ws_url 上拼接握手鉴权 Query(**输出统一为连字符**:auth-token、user-token、admin-ws-token)。 * * $tokens 的键可使用标准名或旧名下划线别名(auth_token 等),拼到 URL 时一律转为连字符。 * * @param array $tokens 如 auth-token、user-token、admin-ws-token * @param array $extraQuery 如 device_id、lang */ public static function appendTokensToWsUrl(string $wsUrl, array $tokens, array $extraQuery = []): string { $wsUrl = trim($wsUrl); if ($wsUrl === '') { return $wsUrl; } $authVal = self::pickTokenFromMap($tokens, [ GameWebSocketAuthHelper::QUERY_AUTH_TOKEN, 'auth_token', 'authToken', ]); $userVal = self::pickTokenFromMap($tokens, [ GameWebSocketAuthHelper::QUERY_USER_TOKEN, 'user_token', 'userToken', 'token', ]); $adminVal = self::pickTokenFromMap($tokens, [ GameWebSocketAuthHelper::QUERY_ADMIN_WS_TOKEN, 'admin_ws_token', 'adminWsToken', ]); $canonical = []; if ($authVal !== '') { $canonical[GameWebSocketAuthHelper::QUERY_AUTH_TOKEN] = $authVal; } if ($userVal !== '') { $canonical[GameWebSocketAuthHelper::QUERY_USER_TOKEN] = $userVal; } if ($adminVal !== '') { $canonical[GameWebSocketAuthHelper::QUERY_ADMIN_WS_TOKEN] = $adminVal; } foreach ($extraQuery as $k => $v) { if (!is_string($k) || $k === '') { continue; } if (!is_scalar($v)) { continue; } $s = trim((string) $v); if ($s !== '') { $canonical[$k] = $s; } } if ($canonical === []) { return $wsUrl; } $pairs = []; foreach ($canonical as $key => $val) { $pairs[] = $key . '=' . rawurlencode($val); } $sep = str_contains($wsUrl, '?') ? '&' : '?'; return $wsUrl . $sep . implode('&', $pairs); } /** * @param array $tokens * @param list $aliases */ private static function pickTokenFromMap(array $tokens, array $aliases): string { foreach ($aliases as $key) { if (!isset($tokens[$key])) { continue; } $val = $tokens[$key]; if (!is_string($val)) { continue; } $s = trim($val); if ($s !== '') { return $s; } } return ''; } private static function isLoopbackWsUrl(string $url): bool { $host = parse_url($url, PHP_URL_HOST); if (!is_string($host) || $host === '') { return false; } $host = strtolower($host); return in_array($host, ['127.0.0.1', 'localhost', '::1'], true); } private static function isLoopbackRequestHost(Request $request): bool { $host = strtolower(trim((string) $request->host(true))); if ($host === '') { $host = strtolower(trim((string) $request->header('host', ''))); } if ($host === '') { return false; } $hostOnly = preg_split('/:/', $host)[0] ?? $host; return in_array($hostOnly, ['127.0.0.1', 'localhost', '::1'], true); } }