t
Some checks failed
BEND FIELD CI / release (push) Has been cancelled
BEND FIELD CI / production-bridge (push) Has been cancelled

This commit is contained in:
33333-33333 2026-08-01 22:31:04 +09:00
commit 9d70afb4cc
42 changed files with 1266 additions and 630 deletions

View file

@ -9,6 +9,55 @@ function fail_json(int $status, string $message): never {
exit;
}
function write_all($socket, string $value): void {
$written = 0;
$length = strlen($value);
while ($written < $length) {
$result = fwrite($socket, substr($value, $written, 64 * 1024));
if ($result === false || $result === 0) {
fclose($socket);
fail_json(502, 'Failed to send the request to the LinkField server.');
}
$written += $result;
}
}
function request_body(int $maximum): array {
$input = fopen('php://input', 'rb');
if (!is_resource($input)) fail_json(400, 'Could not read the request body.');
$declared = trim((string) ($_SERVER['CONTENT_LENGTH'] ?? ''));
if ($declared !== '') {
if (!preg_match('/^[0-9]+$/D', $declared)) { fclose($input); fail_json(400, 'Invalid request content length.'); }
$length = (int) $declared;
if ($length > $maximum) { fclose($input); fail_json(413, 'Request body is too large.'); }
return [$input, $length];
}
$temporary = fopen('php://temp/maxmemory:1048576', 'w+b');
if (!is_resource($temporary)) { fclose($input); fail_json(500, 'Could not stage the request body.'); }
$length = 0;
while (!feof($input)) {
$chunk = fread($input, 64 * 1024);
if ($chunk === false) { fclose($input); fclose($temporary); fail_json(400, 'Could not read the request body.'); }
$length += strlen($chunk);
if ($length > $maximum) { fclose($input); fclose($temporary); fail_json(413, 'Request body is too large.'); }
if ($chunk !== '' && fwrite($temporary, $chunk) !== strlen($chunk)) { fclose($input); fclose($temporary); fail_json(500, 'Could not stage the request body.'); }
}
fclose($input);
rewind($temporary);
return [$temporary, $length];
}
function write_body($socket, $body, int $length): void {
$written = 0;
while ($written < $length) {
$chunk = fread($body, min(64 * 1024, $length - $written));
if ($chunk === false || $chunk === '') { fclose($body); fclose($socket); fail_json(400, 'Request body ended before its declared length.'); }
write_all($socket, $chunk);
$written += strlen($chunk);
}
fclose($body);
}
$portFile = __DIR__ . DIRECTORY_SEPARATOR . '.linkfield-port';
if (!is_file($portFile)) {
fail_json(503, 'LinkField server is not running. Run npm start in this directory.');
@ -20,7 +69,7 @@ if ($port === false) {
}
$targetPath = isset($_GET['path']) ? (string) $_GET['path'] : '';
if (!preg_match('#^/api/(?:cloud|player|realtime)(?:/|$)#', $targetPath)) {
if (!preg_match('#^/api/(?:cloud|player|realtime)(?:/[A-Za-z0-9_-]+)*$#D', $targetPath)) {
fail_json(400, 'Invalid LinkField API path.');
}
$query = $_GET;
@ -33,11 +82,7 @@ $method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
if (!in_array($method, ['GET', 'POST', 'HEAD'], true)) {
fail_json(405, 'Method not allowed.');
}
$body = file_get_contents('php://input');
if ($body === false) $body = '';
if (strlen($body) > 64 * 1024 * 1024) {
fail_json(413, 'Request body is too large.');
}
[$body, $bodyLength] = request_body(8 * 1024 * 1024);
$authorization = '';
if (function_exists('getallheaders')) {
@ -63,7 +108,7 @@ $socket = @stream_socket_client(
if (!is_resource($socket)) {
fail_json(502, 'LinkField server is not reachable on its local port. Restart npm start.');
}
stream_set_timeout($socket, 15);
stream_set_timeout($socket, 30);
$requestHeaders = [
$method . ' ' . $targetPath . " HTTP/1.1",
@ -71,29 +116,27 @@ $requestHeaders = [
'Connection: close',
'Accept: application/json',
'Content-Type: application/json',
'Content-Length: ' . strlen($body),
'Content-Length: ' . $bodyLength,
];
if ($authorization !== '') $requestHeaders[] = 'Authorization: ' . str_replace(["\r", "\n"], '', $authorization);
$request = implode("\r\n", $requestHeaders) . "\r\n\r\n" . $body;
$written = 0;
$length = strlen($request);
while ($written < $length) {
$result = fwrite($socket, substr($request, $written));
if ($result === false || $result === 0) {
fclose($socket);
fail_json(502, 'Failed to send the request to the LinkField server.');
}
$written += $result;
$remoteAddress = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
if (preg_match('/^[0-9A-Fa-f:.]{3,64}$/', $remoteAddress)) $requestHeaders[] = 'X-Forwarded-For: ' . $remoteAddress;
write_all($socket, implode("\r\n", $requestHeaders) . "\r\n\r\n");
write_body($socket, $body, $bodyLength);
$response = '';
$separator = false;
while (!feof($socket) && $separator === false && strlen($response) <= 64 * 1024) {
$chunk = fread($socket, 8192);
if ($chunk === false) break;
$response .= $chunk;
$separator = strpos($response, "\r\n\r\n");
}
$response = stream_get_contents($socket);
$meta = stream_get_meta_data($socket);
fclose($socket);
if ($response === false || $response === '' || !empty($meta['timed_out'])) {
if ($response === '' || $separator === false || !empty($meta['timed_out'])) {
fclose($socket);
fail_json(504, 'LinkField server did not respond in time.');
}
$separator = strpos($response, "\r\n\r\n");
if ($separator === false) fail_json(502, 'Invalid response from the LinkField server.');
$headerText = substr($response, 0, $separator);
$responseBody = substr($response, $separator + 4);
$headerLines = explode("\r\n", $headerText);
@ -107,9 +150,19 @@ foreach ($headerLines as $line) {
if ($colon === false) continue;
$name = trim(substr($line, 0, $colon));
$value = trim(substr($line, $colon + 1));
if (in_array(strtolower($name), ['content-type', 'cache-control', 'x-content-type-options'], true)) {
if (in_array(strtolower($name), ['content-type', 'cache-control', 'x-content-type-options', 'retry-after'], true)) {
header($name . ': ' . $value, true);
}
}
header('X-LinkField-Bridge: php', true);
if ($method !== 'HEAD') echo $responseBody;
header_remove('X-Powered-By');
if ($method !== 'HEAD') {
echo $responseBody;
while (!feof($socket)) {
$chunk = fread($socket, 64 * 1024);
if ($chunk === false) break;
echo $chunk;
if (function_exists('flush')) flush();
}
}
fclose($socket);