|
<?php |
|
/** |
|
* gateway.php |
|
* |
|
* Purpose |
|
* ------- |
|
* Acts as an HTTP reverse proxy that delivers any request reaching |
|
* http://localhost:4280/gateway.php?session=<TOKEN> |
|
* straight into the browser that owns <TOKEN>. |
|
* |
|
*/ |
|
|
|
define('MSG_DIR', __DIR__ . '/msg_queue'); |
|
define('POLL_US', 10000); // 10 ms |
|
define('PING_SEC', 10); |
|
define('WAIT_SEC', 30); |
|
|
|
if (!is_dir(MSG_DIR)) mkdir(MSG_DIR, 0775, true); |
|
|
|
@apache_setenv('no-gzip', '1'); |
|
ini_set('output_buffering', 'off'); |
|
ini_set('zlib.output_compression', '0'); |
|
header('X-Accel-Buffering: no'); |
|
|
|
function sid(): string { |
|
$raw = $_GET['session'] ?? $_POST['session'] ?? ''; |
|
if (!$raw) { http_response_code(400); exit('no session'); } |
|
return preg_replace('/[^A-Za-z0-9_-]/', '', $raw); |
|
} |
|
function path(string $sid, string $rid, string $ext): string { |
|
$dir = MSG_DIR."/{$sid}"; |
|
if (!is_dir($dir)) mkdir($dir, 0775, true); |
|
return "{$dir}/{$rid}.{$ext}"; |
|
} |
|
$sid = sid(); |
|
|
|
/* ----- server→browser stream (SSE) ----- */ |
|
if (isset($_GET['stream'])) { |
|
header('Content-Type: text/event-stream'); |
|
header('Cache-Control: no-cache'); |
|
@ob_end_flush(); flush(); |
|
|
|
$log = path($sid, 'events', 'log'); |
|
if (!file_exists($log)) touch($log); |
|
$pos = 0; $nextPing = time() + PING_SEC; |
|
ignore_user_abort(true); |
|
|
|
while (true) { |
|
clearstatcache(false, $log); |
|
$size = filesize($log); |
|
if ($size > $pos) { |
|
$h = fopen($log, 'r'); fseek($h, $pos); |
|
while (($l = fgets($h)) !== false) { |
|
echo "data: $l\n\n"; flush(); |
|
} |
|
fclose($h); |
|
$pos = $size; |
|
} |
|
if (time() >= $nextPing) { |
|
echo ": ping\n\n"; flush(); |
|
$nextPing = time() + PING_SEC; |
|
} |
|
if (connection_aborted()) break; |
|
usleep(POLL_US); |
|
} |
|
exit; |
|
} |
|
|
|
/* ----- browser → gateway (POST response) ----- */ |
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['respond'])) { |
|
$in = json_decode(file_get_contents('php://input'), true) ?: $_POST; |
|
$rid = $in['req_id'] ?? ''; |
|
$resp = $in['response']?? null; |
|
if (!$rid || !$resp) { http_response_code(400); exit('bad'); } |
|
file_put_contents(path($sid, $rid, 'res'), json_encode($resp), LOCK_EX); |
|
exit('stored'); |
|
} |
|
|
|
/* ----- external caller → gateway (new request) ----- */ |
|
$rid = bin2hex(random_bytes(8)); |
|
$reqData = [ |
|
'req_id' => $rid, |
|
'method' => $_SERVER['REQUEST_METHOD'], |
|
'path' => $_SERVER['REQUEST_URI'], |
|
'headers' => getallheaders(), |
|
'body' => file_get_contents('php://input') |
|
]; |
|
file_put_contents(path($sid, $rid, 'req'), json_encode($reqData), LOCK_EX); |
|
file_put_contents(path($sid, 'events', 'log'), |
|
json_encode($reqData)."\n", |
|
FILE_APPEND | LOCK_EX); |
|
|
|
/* wait synchronously for browser reply */ |
|
$resFile = path($sid, $rid, 'res'); |
|
$start = microtime(true); |
|
while (!file_exists($resFile) && |
|
microtime(true) - $start < WAIT_SEC) { |
|
usleep(POLL_US); |
|
} |
|
if (!file_exists($resFile)) { http_response_code(504); exit('timeout'); } |
|
|
|
/* ----- gateway → external caller (response) ----- */ |
|
|
|
$resp = json_decode(file_get_contents($resFile), true) ?? []; |
|
$resp = json_decode($resp, true); |
|
http_response_code($resp['status'] ?? 200); |
|
foreach (($resp['headers'] ?? []) as $k => $v) header("$k: $v"); |
|
echo $resp['body'] ?? ''; |
|
|
|
unlink($resFile); |
|
unlink(path($sid, $rid, 'req')); |