<?php
// proxy.php
// =========================
// Настройки
// =========================
// Куда проксировать (без завершающего /)
$upstream_base_url = 'http://192.168.1.31';
// Какие пути разрешены (белый список) — подстрой под себя
$allowed_prefixes = [
'/api/v1/',
];
// Кто может обращаться (CORS)
$allowed_origins = [
'http://localhost:5173',
'http://127.0.0.1:5173',
// 'https://your-frontend-domain.com',
];
function send_json_error($code, $message, $extra = []) {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array_merge([
'status' => false,
'message' => $message,
], $extra), JSON_UNESCAPED_UNICODE);
exit;
}
function get_request_headers_lower() {
$headers = [];
foreach (getallheaders() as $k => $v) {
$headers[strtolower($k)] = $v;
}
return $headers;
}
function cors_headers($origin, $allowed_origins) {
if ($origin && in_array($origin, $allowed_origins, true)) {
header("Access-Control-Allow-Origin: {$origin}");
header("Vary: Origin");
} else {
// Если хочешь разрешить всем — раскомментируй (но лучше белый список)
header("Access-Control-Allow-Origin: *");
}
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Authorization, Content-Type, Accept, X-Requested-With");
header("Access-Control-Max-Age: 86400");
}
// =========================
// CORS preflight
// =========================
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
cors_headers($origin, $allowed_origins);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
// =========================
// Валидация входа
// =========================
// Ожидаем ?path=/api/v1/...
$path = $_GET['path'] ?? '';
if (!$path || $path[0] !== '/') {
send_json_error(400, 'Missing or invalid "path" parameter. Example: ?path=/api/v1/scripts/actions/list');
}
// белый список путей
$ok = false;
foreach ($allowed_prefixes as $p) {
if (str_starts_with($path, $p)) {
$ok = true;
break;
}
}
if (!$ok) {
send_json_error(403, 'Path not allowed', ['path' => $path]);
}
// Собираем URL апстрима + query string (кроме path)
$query = $_GET;
unset($query['path']);
$qs = http_build_query($query);
$upstream_url = rtrim($upstream_base_url, '/') . $path . ($qs ? ('?' . $qs) : '');
// =========================
// Проксирование через cURL
// =========================
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$headers_in = get_request_headers_lower();
$ch = curl_init($upstream_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HEADER, true); // чтобы разделить headers/body
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
// Проксируем body для методов кроме GET/HEAD
$body = file_get_contents('php://input');
if (!in_array($method, ['GET', 'HEAD'], true)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
// Заголовки, которые прокидываем
$forward_headers = [];
// Authorization
if (!empty($headers_in['authorization'])) {
$forward_headers[] = 'Authorization: ' . $headers_in['authorization'];
}
// Content-Type
if (!empty($headers_in['content-type'])) {
$forward_headers[] = 'Content-Type: ' . $headers_in['content-type'];
}
// Accept
if (!empty($headers_in['accept'])) {
$forward_headers[] = 'Accept: ' . $headers_in['accept'];
}
// Можно добавить свой заголовок, например X-Proxy
$forward_headers[] = 'X-Proxy: php';
curl_setopt($ch, CURLOPT_HTTPHEADER, $forward_headers);
$response = curl_exec($ch);
if ($response === false) {
$err = curl_error($ch);
curl_close($ch);
send_json_error(502, 'Upstream request failed', ['details' => $err]);
}
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$raw_headers = substr($response, 0, $header_size);
$resp_body = substr($response, $header_size);
// =========================
// Отдаём ответ клиенту
// =========================
http_response_code($http_code);
// Проксируем часть заголовков ответа (без CORS/опасных)
$lines = preg_split("/\r\n|\n|\r/", trim($raw_headers));
foreach ($lines as $line) {
if (stripos($line, 'HTTP/') === 0) continue;
$pos = strpos($line, ':');
if ($pos === false) continue;
$name = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos + 1));
$name_l = strtolower($name);
// не прокидываем hop-by-hop и то, что конфликтует
if (in_array($name_l, ['transfer-encoding', 'content-length', 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'upgrade'], true)) {
continue;
}
// CORS мы уже выставили сами
if (str_starts_with($name_l, 'access-control-')) {
continue;
}
header($name . ': ' . $value, false);
}
echo $resp_body;