543 lines
20 KiB
PHP
543 lines
20 KiB
PHP
<?php
|
||
|
||
use Swoole\Coroutine\Http\Server;
|
||
use Swoole\Coroutine\Http\Client;
|
||
use function Swoole\Coroutine\run;
|
||
|
||
// 常量定义
|
||
const CACHE_DIR_NAME = '.cache';
|
||
const DEFAULT_PORT = 9001;
|
||
const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/api';
|
||
const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24小时
|
||
const CACHE_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1小时
|
||
const API_TIMEOUT_MS = 5000;
|
||
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36';
|
||
|
||
// HTTP状态码
|
||
const HTTP_STATUS = [
|
||
'OK' => 200,
|
||
'NO_CONTENT' => 204,
|
||
'REDIRECT' => 302,
|
||
'NOT_MODIFIED' => 304,
|
||
'BAD_REQUEST' => 400,
|
||
'NOT_FOUND' => 404,
|
||
'INTERNAL_SERVER_ERROR' => 500,
|
||
'BAD_GATEWAY' => 502,
|
||
];
|
||
|
||
// 初始化变量
|
||
$cacheDir = __DIR__ . '/' . CACHE_DIR_NAME;
|
||
$pathIndex = [];
|
||
$port = DEFAULT_PORT;
|
||
$apiEndpoint = DEFAULT_API_ENDPOINT;
|
||
|
||
// 访问计数器
|
||
$viewsInfo = [
|
||
'request' => 0,
|
||
'cacheHit' => 0,
|
||
'apiCall' => 0,
|
||
'cacheCall' => 0,
|
||
'cacheReadError' => 0,
|
||
'fetchApiError' => 0,
|
||
'fetchApiWarning' => 0,
|
||
];
|
||
|
||
// 解析命令行参数
|
||
function parseArguments() {
|
||
global $port, $apiEndpoint;
|
||
|
||
$options = getopt('', ['port:', 'api:']);
|
||
|
||
if (isset($options['port'])) {
|
||
$parsedPort = intval($options['port']);
|
||
if ($parsedPort > 0) {
|
||
$port = $parsedPort;
|
||
}
|
||
}
|
||
|
||
if (isset($options['api'])) {
|
||
$apiEndpoint = $options['api'];
|
||
}
|
||
}
|
||
|
||
// 初始化应用
|
||
function initializeApp() {
|
||
global $cacheDir;
|
||
|
||
parseArguments();
|
||
|
||
if (!file_exists($cacheDir)) {
|
||
try {
|
||
mkdir($cacheDir, 0777, true);
|
||
echo "Cache directory created: {$cacheDir}\n";
|
||
} catch (Exception $e) {
|
||
echo "Error creating cache directory {$cacheDir}: " . $e->getMessage() . "\n";
|
||
exit(1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 发送错误响应
|
||
function sendErrorResponse($res, int $statusCode, string $message) {
|
||
if (!$res->isWritable()) {
|
||
return;
|
||
}
|
||
$res->status($statusCode);
|
||
$res->header('Content-Type', 'text/plain;charset=UTF-8');
|
||
$res->end($message);
|
||
}
|
||
|
||
// 处理favicon请求
|
||
function handleFavicon($req, $res) {
|
||
$res->status(HTTP_STATUS['NO_CONTENT']);
|
||
$res->end();
|
||
}
|
||
|
||
// 处理endpoint请求
|
||
function handleEndpoint($req, $res, array $queryParams) {
|
||
global $apiEndpoint, $port, $cacheDir, $pathIndex, $viewsInfo;
|
||
|
||
if (isset($queryParams['api'])) {
|
||
$urlRegex = '/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/';
|
||
if (preg_match($urlRegex, $queryParams['api'])) {
|
||
$apiEndpoint = $queryParams['api'];
|
||
echo "API endpoint updated to: {$apiEndpoint}\n";
|
||
}
|
||
}
|
||
|
||
$res->status(HTTP_STATUS['OK']);
|
||
$res->header('Content-Type', 'application/json; charset=utf-8');
|
||
$res->end(json_encode([
|
||
'code' => HTTP_STATUS['OK'],
|
||
'data' => [
|
||
'api' => $apiEndpoint,
|
||
'port' => $port,
|
||
'cacheDir' => $cacheDir,
|
||
'pathIndexCount' => count($pathIndex),
|
||
'viewsInfo' => $viewsInfo
|
||
]
|
||
]));
|
||
}
|
||
|
||
// 处理API重定向
|
||
function handleApiRedirect($res, array $apiData) {
|
||
$res->status(HTTP_STATUS['REDIRECT']);
|
||
$res->header('Location', $apiData['data']['url']);
|
||
$res->end();
|
||
}
|
||
|
||
// 检查缓存头并返回是否为304
|
||
function checkCacheHeaders($req, string $cacheMetaFile) {
|
||
try {
|
||
$metaContent = file_get_contents($cacheMetaFile);
|
||
$cacheData = json_decode($metaContent, true);
|
||
$ifNoneMatch = isset($req->header['if-none-match']) ? $req->header['if-none-match'] : null;
|
||
$ifModifiedSince = isset($req->header['if-modified-since']) ? $req->header['if-modified-since'] : null;
|
||
|
||
// 检查ETag
|
||
if ($ifNoneMatch && isset($cacheData['uniqid']) && $ifNoneMatch === $cacheData['uniqid']) {
|
||
return ['cacheData' => $cacheData, 'isNotModified' => true];
|
||
}
|
||
|
||
// 检查If-Modified-Since
|
||
if ($ifModifiedSince && isset($cacheData['headers']['last-modified'])) {
|
||
try {
|
||
$lastModifiedDate = strtotime($cacheData['headers']['last-modified']);
|
||
$ifModifiedSinceDate = strtotime($ifModifiedSince);
|
||
|
||
if ($lastModifiedDate <= $ifModifiedSinceDate) {
|
||
return ['cacheData' => $cacheData, 'isNotModified' => true];
|
||
}
|
||
} catch (Exception $e) {
|
||
echo "Error parsing date for cache header check ({$cacheMetaFile}): " . $e->getMessage() . "\n";
|
||
}
|
||
}
|
||
|
||
return ['cacheData' => $cacheData, 'isNotModified' => false];
|
||
} catch (Exception $e) {
|
||
echo "Error reading or parsing cache meta file {$cacheMetaFile} in checkCacheHeaders: " . $e->getMessage() . "\n";
|
||
return ['cacheData' => null, 'isNotModified' => false];
|
||
}
|
||
}
|
||
|
||
// 检查缓存是否有效
|
||
function isCacheValid(string $cacheMetaFile, string $cacheContentFile) {
|
||
if (!file_exists($cacheMetaFile) || !file_exists($cacheContentFile)) {
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
$metaContent = file_get_contents($cacheMetaFile);
|
||
$cacheData = json_decode($metaContent, true);
|
||
|
||
return isset($cacheData['expiration']) && is_numeric($cacheData['expiration']) && $cacheData['expiration'] > time() * 1000;
|
||
} catch (Exception $e) {
|
||
echo "Error reading or parsing cache meta file {$cacheMetaFile} for validation: " . $e->getMessage() . "\n";
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 从API获取数据
|
||
function fetchApiData(string $reqPath, string $token, string $sign) {
|
||
global $apiEndpoint;
|
||
|
||
$queryParams = http_build_query([
|
||
'type' => $reqPath,
|
||
'sign' => $sign
|
||
]);
|
||
|
||
$apiUrl = "{$apiEndpoint}?{$queryParams}";
|
||
$parsedApiUrl = parse_url($apiUrl);
|
||
|
||
$client = new Client($parsedApiUrl['host'], $parsedApiUrl['port'] ?? ($parsedApiUrl['scheme'] === 'https' ? 443 : 80), $parsedApiUrl['scheme'] === 'https');
|
||
$client->setHeaders([
|
||
'Accept' => 'application/json; charset=utf-8',
|
||
'User-Agent' => USER_AGENT,
|
||
'token' => $token
|
||
]);
|
||
$client->set(['timeout' => API_TIMEOUT_MS / 1000]);
|
||
|
||
$path = isset($parsedApiUrl['path']) ? $parsedApiUrl['path'] : '/';
|
||
if (isset($parsedApiUrl['query'])) {
|
||
$path .= '?' . $parsedApiUrl['query'];
|
||
}
|
||
|
||
$client->get($path);
|
||
|
||
if ($client->statusCode >= 400) {
|
||
echo "API request to {$apiUrl} failed with status {$client->statusCode}: {$client->body}\n";
|
||
$errorPayload = ['code' => $client->statusCode, 'message' => "API Error: {$client->statusCode}"];
|
||
|
||
try {
|
||
$parsedError = json_decode($client->body, true);
|
||
if ($parsedError && isset($parsedError['message'])) {
|
||
$errorPayload['message'] = $parsedError['message'];
|
||
}
|
||
} catch (Exception $e) {
|
||
// Ignore if response is not JSON
|
||
}
|
||
|
||
return $errorPayload;
|
||
}
|
||
|
||
try {
|
||
return json_decode($client->body, true);
|
||
} catch (Exception $e) {
|
||
echo "Error parsing JSON response from {$apiUrl}: " . $e->getMessage() . ", {$client->body}\n";
|
||
throw new Exception("Failed to parse API response: " . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
// 从缓存中读取数据并返回
|
||
function serveFromCache(array $cacheData, string $cacheContentFile, string $cacheMetaFile, $res) {
|
||
global $viewsInfo;
|
||
|
||
if (!$cacheData) {
|
||
echo "serveFromCache called with null cacheData for {$cacheContentFile}\n";
|
||
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Cache metadata unavailable.');
|
||
return;
|
||
}
|
||
|
||
$viewsInfo['cacheCall']++;
|
||
|
||
try {
|
||
$fileContent = file_get_contents($cacheContentFile);
|
||
|
||
if ($fileContent === false) {
|
||
throw new Exception("Failed to read cache file");
|
||
}
|
||
|
||
$baseHeaders = [
|
||
'Cloud-Type' => $cacheData['cloudtype'] ?? 'unknown',
|
||
'Cloud-Expiration' => $cacheData['expiration'] ?? 0,
|
||
'ETag' => $cacheData['uniqid'] ?? '',
|
||
'Cache-Control' => 'public, max-age=31536000',
|
||
'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT',
|
||
'Accept-Ranges' => 'bytes',
|
||
'Connection' => 'keep-alive',
|
||
'Date' => gmdate('D, d M Y H:i:s') . ' GMT',
|
||
'Last-Modified' => isset($cacheData['headers']['last-modified']) ? $cacheData['headers']['last-modified'] : gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT',
|
||
];
|
||
|
||
$isVideo = isset($cacheData['path']) && is_string($cacheData['path']) && strpos($cacheData['path'], '.mp4') !== false;
|
||
$contentType = isset($cacheData['headers']['Content-Type']) ? $cacheData['headers']['Content-Type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream');
|
||
|
||
$responseHeaders = array_merge($baseHeaders, [
|
||
'Content-Type' => $contentType,
|
||
]);
|
||
|
||
foreach ($responseHeaders as $key => $value) {
|
||
$res->header($key, $value);
|
||
}
|
||
|
||
$res->status(HTTP_STATUS['OK']);
|
||
$res->end($fileContent);
|
||
} catch (Exception $e) {
|
||
$viewsInfo['cacheReadError']++;
|
||
echo "Error reading from cache {$cacheContentFile}: " . $e->getMessage() . "\n";
|
||
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], "Failed to read from cache: " . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
// 从真实URL获取数据并写入缓存
|
||
function fetchAndServe(array $data, string $tempCacheContentFile, string $cacheContentFile, string $cacheMetaFile, $res) {
|
||
global $viewsInfo;
|
||
|
||
$parsedUrl = parse_url($data['realUrl']);
|
||
$client = new Client($parsedUrl['host'], $parsedUrl['port'] ?? ($parsedUrl['scheme'] === 'https' ? 443 : 80), $parsedUrl['scheme'] === 'https');
|
||
$client->setHeaders([
|
||
'User-Agent' => USER_AGENT
|
||
]);
|
||
|
||
$path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/';
|
||
if (isset($parsedUrl['query'])) {
|
||
$path .= '?' . $parsedUrl['query'];
|
||
}
|
||
|
||
$client->get($path);
|
||
|
||
if ($client->statusCode !== 200) {
|
||
echo "Error fetching from {$data['realUrl']}: HTTP status {$client->statusCode}\n";
|
||
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Failed to fetch content from source");
|
||
return;
|
||
}
|
||
|
||
$isVideo = isset($data['path']) && is_string($data['path']) && strpos($data['path'], '.mp4') !== false;
|
||
|
||
// 检查content-length
|
||
$contentLength = isset($client->headers['content-length']) ? $client->headers['content-length'] : null;
|
||
if ($contentLength) {
|
||
// contentLength小于2KB且与缓存文件大小不一致时,重新获取
|
||
if ($contentLength < 2048 && isset($data['headers']['content-length']) && $data['headers']['content-length'] !== $contentLength) {
|
||
echo "Warning: content-length is different for the response from: {$data['realUrl']}\n";
|
||
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Content-Length mismatch for {$data['realUrl']}");
|
||
return;
|
||
}
|
||
// 更新data到缓存cacheMetaFile
|
||
file_put_contents($cacheMetaFile, json_encode($data));
|
||
} else {
|
||
echo "Warning: content-length is undefined for the response from: {$data['realUrl']}\n";
|
||
}
|
||
|
||
// 写入临时缓存文件
|
||
file_put_contents($tempCacheContentFile, $client->body);
|
||
|
||
// 重命名临时文件为正式缓存文件
|
||
try {
|
||
$targetDir = dirname($cacheContentFile);
|
||
if (!file_exists($targetDir)) {
|
||
mkdir($targetDir, 0777, true);
|
||
}
|
||
rename($tempCacheContentFile, $cacheContentFile);
|
||
echo "Successfully cached: {$cacheContentFile}\n";
|
||
} catch (Exception $e) {
|
||
echo "Error renaming temp cache file {$tempCacheContentFile} to {$cacheContentFile}: " . $e->getMessage() . "\n";
|
||
// 如果重命名失败,尝试删除临时文件以避免混乱
|
||
if (file_exists($tempCacheContentFile)) {
|
||
unlink($tempCacheContentFile);
|
||
}
|
||
}
|
||
|
||
$baseHeaders = [
|
||
'Cloud-Type' => $data['cloudtype'] ?? 'unknown',
|
||
'Cloud-Expiration' => $data['expiration'] ?? 0,
|
||
'ETag' => $data['uniqid'] ?? '',
|
||
'Cache-Control' => 'public, max-age=31536000',
|
||
'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT',
|
||
'Accept-Ranges' => 'bytes',
|
||
'Connection' => 'keep-alive',
|
||
'Date' => gmdate('D, d M Y H:i:s') . ' GMT',
|
||
'Last-Modified' => isset($data['headers']['last-modified']) ?
|
||
$data['headers']['last-modified'] :
|
||
gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT',
|
||
];
|
||
|
||
$responseHeaders = array_merge($baseHeaders, [
|
||
'Content-Type' => isset($client->headers['content-type']) ? $client->headers['content-type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream'),
|
||
], $data['headers']);
|
||
|
||
foreach ($responseHeaders as $key => $value) {
|
||
$res->header($key, $value);
|
||
}
|
||
|
||
$res->status(HTTP_STATUS['OK']);
|
||
$res->end($client->body);
|
||
}
|
||
|
||
// 尝试从过期缓存提供服务或返回错误
|
||
function tryServeFromStaleCacheOrError(string $uniqidhex, $res, string $errorMessage = null) {
|
||
global $pathIndex, $cacheDir;
|
||
|
||
if (isset($pathIndex[$uniqidhex])) {
|
||
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
|
||
$cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content';
|
||
|
||
if (file_exists($cacheMetaFile) && file_exists($cacheContentFile)) {
|
||
echo "API call failed or returned non-200. Serving stale cache for {$uniqidhex}\n";
|
||
|
||
try {
|
||
$cacheData = json_decode(file_get_contents($cacheMetaFile), true);
|
||
serveFromCache($cacheData, $cacheContentFile, $cacheMetaFile, $res);
|
||
return;
|
||
} catch (Exception $e) {
|
||
echo "Error parsing stale meta file {$cacheMetaFile}: " . $e->getMessage() . "\n";
|
||
// 如果过期缓存也损坏,则返回通用错误
|
||
}
|
||
}
|
||
}
|
||
|
||
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], $errorMessage ?: 'Bad Gateway');
|
||
}
|
||
|
||
// 处理主请求
|
||
function handleMainRequest($req, $res) {
|
||
global $pathIndex, $cacheDir, $viewsInfo;
|
||
|
||
$url = preg_replace('/\/{2,}/', '/', $req->server['request_uri']);
|
||
$parsedUrl = parse_url($url);
|
||
$queryParams = [];
|
||
|
||
if (isset($parsedUrl['query'])) {
|
||
parse_str($parsedUrl['query'], $queryParams);
|
||
}
|
||
|
||
$sign = $queryParams['sign'] ?? '';
|
||
$pathParts = explode('/', trim($parsedUrl['path'], '/'));
|
||
$reqPath = $pathParts[0] ?? '';
|
||
$token = implode('/', array_slice($pathParts, 1));
|
||
|
||
if ($reqPath === 'favicon.ico') {
|
||
return handleFavicon($req, $res);
|
||
}
|
||
|
||
if ($reqPath === 'endpoint') {
|
||
return handleEndpoint($req, $res, $queryParams);
|
||
}
|
||
|
||
if (!$token && $reqPath) {
|
||
$token = $reqPath;
|
||
$reqPath = 'app'; // 默认为'app',如果只提供了一个路径段
|
||
}
|
||
|
||
$allowedPaths = ['avatar', 'go', 'bbs', 'www', 'url', 'thumb', 'app'];
|
||
if (!in_array($reqPath, $allowedPaths) || !$token) {
|
||
return sendErrorResponse($res, HTTP_STATUS['BAD_REQUEST'], "Bad Request: Invalid path or missing token.");
|
||
}
|
||
|
||
$viewsInfo['request']++;
|
||
$uniqidhex = md5($reqPath . $token . $sign);
|
||
$cacheMetaFile = '';
|
||
$cacheContentFile = '';
|
||
|
||
if (isset($pathIndex[$uniqidhex])) {
|
||
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
|
||
$cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content';
|
||
}
|
||
|
||
if (isset($pathIndex[$uniqidhex]) && isCacheValid($cacheMetaFile, $cacheContentFile)) {
|
||
$cacheResult = checkCacheHeaders($req, $cacheMetaFile);
|
||
|
||
if ($cacheResult['isNotModified']) {
|
||
$res->status(HTTP_STATUS['NOT_MODIFIED']);
|
||
$res->end();
|
||
} else {
|
||
$viewsInfo['cacheHit']++;
|
||
serveFromCache($cacheResult['cacheData'], $cacheContentFile, $cacheMetaFile, $res);
|
||
}
|
||
} else {
|
||
try {
|
||
$viewsInfo['apiCall']++;
|
||
$apiData = fetchApiData($reqPath, $token, $sign);
|
||
|
||
if (isset($apiData['code']) && ($apiData['code'] === HTTP_STATUS['REDIRECT'] || $apiData['code'] === 301)) {
|
||
return handleApiRedirect($res, $apiData);
|
||
}
|
||
|
||
if (isset($apiData['code']) && $apiData['code'] === HTTP_STATUS['OK'] && isset($apiData['data']) && isset($apiData['data']['url'])) {
|
||
$data = [
|
||
'realUrl' => $apiData['data']['url'],
|
||
'cloudtype' => $apiData['data']['cloudtype'] ?? '',
|
||
'expiration' => isset($apiData['data']['expiration']) ? $apiData['data']['expiration'] * 1000 : 0,
|
||
'path' => $apiData['data']['path'] ?? '',
|
||
'headers' => $apiData['data']['headers'] ?? [],
|
||
'uniqid' => $apiData['data']['uniqid'] ?? '',
|
||
'thumb' => $apiData['data']['thumb'] ?? ''
|
||
];
|
||
|
||
$pathIndex[$uniqidhex] = ['uniqid' => $data['uniqid'], 'timestamp' => time() * 1000];
|
||
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
|
||
$cacheContentFile = $cacheDir . '/' . $data['uniqid'] . '.content';
|
||
$tempCacheContentFile = $cacheDir . '/' . $data['uniqid'] . '_' . bin2hex(random_bytes(16)) . '.temp';
|
||
|
||
try {
|
||
file_put_contents($cacheMetaFile, json_encode($data));
|
||
} catch (Exception $e) {
|
||
echo "Error writing meta file {$cacheMetaFile}: " . $e->getMessage() . "\n";
|
||
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Failed to write cache metadata.');
|
||
return;
|
||
}
|
||
|
||
if (file_exists($cacheContentFile)) {
|
||
$contentLength = filesize($cacheContentFile);
|
||
if ($contentLength < 2048 && isset($data['headers']['content-length']) && intval($data['headers']['content-length']) !== $contentLength) {
|
||
echo "Content length mismatch for {$cacheContentFile}. API: {$data['headers']['content-length']}, Cache: {$contentLength}. Re-fetching.\n";
|
||
fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res);
|
||
} else {
|
||
serveFromCache($data, $cacheContentFile, $cacheMetaFile, $res);
|
||
}
|
||
} else {
|
||
fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res);
|
||
}
|
||
} else {
|
||
$viewsInfo['fetchApiWarning']++;
|
||
tryServeFromStaleCacheOrError($uniqidhex, $res, $apiData['message'] ?? null);
|
||
}
|
||
} catch (Exception $e) {
|
||
$viewsInfo['fetchApiError']++;
|
||
echo "Error in API call or processing: " . $e->getMessage() . "\n";
|
||
tryServeFromStaleCacheOrError($uniqidhex, $res, "Bad Gateway: API request failed. " . $e->getMessage());
|
||
}
|
||
}
|
||
}
|
||
|
||
// 定时清理过期缓存数据
|
||
function cleanupExpiredCache() {
|
||
global $pathIndex;
|
||
|
||
$currentTime = time() * 1000;
|
||
foreach ($pathIndex as $key => $value) {
|
||
if ($currentTime - $value['timestamp'] > CACHE_EXPIRY_MS) {
|
||
unset($pathIndex[$key]);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主函数
|
||
function main() {
|
||
global $port;
|
||
|
||
initializeApp();
|
||
|
||
// 创建服务器
|
||
$server = new Server('0.0.0.0', $port);
|
||
|
||
echo "Server started at http://0.0.0.0:{$port}\n";
|
||
|
||
// 设置定时器清理过期缓存
|
||
Swoole\Timer::tick(CACHE_CLEANUP_INTERVAL_MS, function () {
|
||
cleanupExpiredCache();
|
||
});
|
||
|
||
// 处理请求
|
||
$server->handle('/', function ($req, $res) {
|
||
handleMainRequest($req, $res);
|
||
});
|
||
|
||
$server->start();
|
||
}
|
||
|
||
// 启动服务器
|
||
run(function () {
|
||
main();
|
||
});
|