418 lines
15 KiB
C#
418 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Net;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using UnityEngine;
|
||
|
||
//Зависит от SettingsManager
|
||
[System.Serializable]
|
||
public class ServerResponse
|
||
{
|
||
public string message;
|
||
public string timestamp;
|
||
}
|
||
|
||
public class WebServer : MonoBehaviour
|
||
{
|
||
private HttpListener listener;
|
||
private bool isRunning = true;
|
||
|
||
private static byte[] css;
|
||
|
||
async void Start()
|
||
{
|
||
SettingsManager.Initialize();//надо будет убирать
|
||
LoadCss();
|
||
|
||
await StartWebServerAsync();
|
||
}
|
||
|
||
async Task StartWebServerAsync()
|
||
{
|
||
listener = new HttpListener();
|
||
listener.Prefixes.Add("http://localhost:8080/");
|
||
|
||
//тест
|
||
listener.Prefixes.Add("http://fishka:8080/");
|
||
// 4. ВСЕ сетевые интерфейсы (самый важный!)
|
||
listener.Prefixes.Add("http://*:8080/");
|
||
|
||
listener.Start();
|
||
Debug.Log("Async Web сервер запущен");
|
||
|
||
while (isRunning)
|
||
{
|
||
try
|
||
{
|
||
var context = await listener.GetContextAsync();
|
||
_ = Task.Run(() => ProcessRequestAsync(context));
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogError($"Ошибка: {e.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
async Task ProcessRequestAsync(HttpListenerContext context)
|
||
{
|
||
try
|
||
{
|
||
string path = context.Request.Url.LocalPath;
|
||
string method = context.Request.HttpMethod;
|
||
|
||
Debug.Log($"Запрос: {method} {path}");
|
||
|
||
if (method == "GET" && path == "/")
|
||
{
|
||
await HandleGetSettingsAsync(context);
|
||
}
|
||
else if (method == "GET" && path == "/style.css")
|
||
{
|
||
await HandleCssRequestAsync(context);
|
||
}
|
||
else if (method == "POST" && path == "/upd")
|
||
{
|
||
await HandlePostUpdateAsync(context);
|
||
}
|
||
else
|
||
{
|
||
SendHtmlResponse(context, "<h1>404 Not Found</h1>", 404);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"Error processing request: {ex.Message}");
|
||
SendHtmlResponse(context, $"<h1>Error: {ex.Message}</h1>", 500);
|
||
}
|
||
}
|
||
|
||
async Task HandleGetSettingsAsync(HttpListenerContext context)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
try
|
||
{
|
||
Debug.Log("Starting SettingsToHtml...");
|
||
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
var htmlTask = Task.Run(() =>
|
||
SettingsToHtml.GetSettingsPage(SettingsManager.Container.Settings));
|
||
//SettingsToHtml.GetSettingsString(SettingsManager.Container.Settings));
|
||
|
||
if (htmlTask.Wait(TimeSpan.FromSeconds(5)))
|
||
{
|
||
string htmlContent = htmlTask.Result;
|
||
stopwatch.Stop();
|
||
Debug.Log($"GetSettingsString выполнился за {stopwatch.ElapsedMilliseconds}ms");
|
||
|
||
if (string.IsNullOrEmpty(htmlContent))
|
||
{
|
||
htmlContent = "<h1>Error: Method returned empty string</h1>";
|
||
}
|
||
|
||
SendHtmlResponse(context, htmlContent);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("GetSettingsString TIMEOUT (5 seconds)!");
|
||
SendHtmlResponse(context, "<h1>Error: Settings generation timeout</h1>", 500);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"Error in GetSettingsString: {ex.Message}");
|
||
SendHtmlResponse(context, $"<h1>Error: {ex.Message}</h1>", 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
async Task HandleCssRequestAsync(HttpListenerContext context)
|
||
{
|
||
context.Response.ContentType = "text/css";
|
||
context.Response.ContentLength64 = css.Length;
|
||
await context.Response.OutputStream.WriteAsync(css, 0, css.Length);
|
||
context.Response.OutputStream.Close();
|
||
}
|
||
|
||
private static void LoadCss()
|
||
{
|
||
TextAsset cssAsset = Resources.Load<TextAsset>("style");
|
||
|
||
try
|
||
{
|
||
print(cssAsset.text);
|
||
}
|
||
catch (Exception)
|
||
{
|
||
|
||
throw;
|
||
}
|
||
|
||
if (cssAsset != null)
|
||
{
|
||
css= Encoding.UTF8.GetBytes(cssAsset.text);
|
||
Debug.Log("CSS OK");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("CSS NOT OK");
|
||
css = new byte[0];
|
||
}
|
||
}
|
||
|
||
async Task HandlePostUpdateAsync(HttpListenerContext context)
|
||
{
|
||
try
|
||
{
|
||
// Определяем кодировку из Content-Type или используем UTF-8 по умолчанию
|
||
Encoding encoding = Encoding.UTF8;
|
||
string contentType = context.Request.ContentType?.ToLower() ?? "";
|
||
|
||
if (contentType.Contains("charset="))
|
||
{
|
||
try
|
||
{
|
||
string charset = contentType.Split("charset=")[1].Trim().Split(';')[0];
|
||
encoding = Encoding.GetEncoding(charset);
|
||
Debug.Log($"Определена кодировка из Content-Type: {charset}");
|
||
}
|
||
catch
|
||
{
|
||
encoding = Encoding.UTF8;
|
||
Debug.Log($"Не удалось определить кодировку из Content-Type, используем UTF-8");
|
||
}
|
||
}
|
||
|
||
// Читаем тело POST-запроса
|
||
string requestBody;
|
||
using (var reader = new StreamReader(context.Request.InputStream, encoding))
|
||
{
|
||
requestBody = await reader.ReadToEndAsync();
|
||
}
|
||
|
||
Debug.Log($"Получен POST запрос на /upd");
|
||
Debug.Log($"Content-Type: {contentType}");
|
||
Debug.Log($"Используемая кодировка: {encoding.WebName}");
|
||
Debug.Log($"Тело запроса (сырое): {requestBody}");
|
||
|
||
// Ручное декодирование данных формы
|
||
Debug.Log("\n=== Декодированные данные формы ===");
|
||
var decodedValues = new Dictionary<string, string>();
|
||
|
||
|
||
|
||
// Также можно использовать ParseQueryString для сравнения
|
||
try
|
||
{
|
||
var formData = System.Web.HttpUtility.ParseQueryString(requestBody, encoding);
|
||
Debug.Log("\n=== Данные через ParseQueryString ===");
|
||
foreach (string key in formData.AllKeys)
|
||
{
|
||
if (key != null)
|
||
{
|
||
string value = formData[key] ?? "";
|
||
Debug.Log($" {key} = {value}");
|
||
|
||
// Если ParseQueryString не декодировал ключ, делаем это вручную
|
||
if (key.Contains("%"))
|
||
{
|
||
string decodedKey = Uri.UnescapeDataString(key);
|
||
Debug.Log($" Декодированный ключ: {decodedKey}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception parseEx)
|
||
{
|
||
Debug.LogWarning($"ParseQueryString ошибка: {parseEx.Message}");
|
||
}
|
||
|
||
// Получаем action из декодированных данных
|
||
string action = decodedValues.ContainsKey("action") ? decodedValues["action"] :
|
||
(decodedValues.ContainsKey("action") ? decodedValues["action"] : null);
|
||
|
||
Debug.Log($"\nAction: {action}");
|
||
|
||
// Обработка разных действий
|
||
if (!string.IsNullOrEmpty(action))
|
||
{
|
||
if (action == "save")
|
||
{
|
||
await HandleSaveAction(decodedValues);
|
||
}
|
||
else if (action == "reset")
|
||
{
|
||
await HandleResetAction(decodedValues);
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"Неизвестное действие: {action}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.Log("Action не указан, проверяем наличие отдельных кнопок...");
|
||
|
||
// Проверяем наличие других кнопок
|
||
if (decodedValues.ContainsKey("save") && !string.IsNullOrEmpty(decodedValues["save"]))
|
||
{
|
||
await HandleSaveAction(decodedValues);
|
||
}
|
||
else if (decodedValues.ContainsKey("reset") && !string.IsNullOrEmpty(decodedValues["reset"]))
|
||
{
|
||
await HandleResetAction(decodedValues);
|
||
}
|
||
}
|
||
|
||
// Отправляем ответ обратно
|
||
string responseHtml = @"
|
||
<html>
|
||
<head>
|
||
<meta charset='UTF-8'>
|
||
<title>Обновлено</title>
|
||
<style>
|
||
body { font-family: Arial, sans-serif; padding: 20px; }
|
||
.success { color: green; font-weight: bold; }
|
||
.info { margin: 20px 0; padding: 10px; background: #f0f0f0; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1 class='success'>✓ Данные успешно получены!</h1>
|
||
<div class='info'>
|
||
<p>Сервер обработал ваш запрос.</p>
|
||
<p>Полученные данные:</p>
|
||
<ul>";
|
||
|
||
// Добавляем список полученных данных в ответ
|
||
foreach (var kvp in decodedValues)
|
||
{
|
||
responseHtml += $"<li><strong>{kvp.Key}</strong>: {kvp.Value}</li>";
|
||
}
|
||
|
||
responseHtml += @"
|
||
</ul>
|
||
</div>
|
||
<a href='/' style='display: inline-block; margin-top: 20px; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px;'>
|
||
Вернуться к настройкам
|
||
</a>
|
||
<script>
|
||
// Через 5 секунд возвращаемся назад
|
||
setTimeout(function() {
|
||
window.location.href = '/';
|
||
}, 5000);
|
||
</script>
|
||
</body>
|
||
</html>";
|
||
|
||
SendHtmlResponse(context, responseHtml);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"Ошибка обработки POST /upd: {ex.Message}");
|
||
Debug.LogError($"StackTrace: {ex.StackTrace}");
|
||
SendHtmlResponse(context,
|
||
$"<html><head><meta charset='UTF-8'><title>Ошибка</title></head><body>" +
|
||
$"<h1 style='color:red;'>Ошибка: {ex.Message}</h1>" +
|
||
$"<pre>{ex.StackTrace}</pre>" +
|
||
$"<a href='/'>Вернуться назад</a></body></html>",
|
||
500);
|
||
}
|
||
}
|
||
|
||
// Обновленный HandleSaveAction для работы с Dictionary
|
||
async Task HandleSaveAction(Dictionary<string, string> formData)
|
||
{
|
||
Debug.Log("\n=== Обработка сохранения настроек ===");
|
||
|
||
foreach (var kvp in formData)
|
||
{
|
||
string key = kvp.Key;
|
||
string value = kvp.Value;
|
||
|
||
Debug.Log($"Поле формы: '{key}' = '{value}'");
|
||
|
||
// Пропускаем служебные поля
|
||
if (key == "action" || key == "_charset_" || key == "submit")
|
||
continue;
|
||
|
||
// TODO: Здесь логика обновления SettingsManager
|
||
// Например:
|
||
// var setting = SettingsManager.Container.Settings.FirstOrDefault(s => s.Id == key);
|
||
// if (setting != null) { ... }
|
||
}
|
||
|
||
Debug.Log("Сохранение настроек завершено");
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
async Task HandleResetAction(Dictionary<string, string> formData)
|
||
{
|
||
Debug.Log("\n=== Обработка сброса настроек ===");
|
||
// TODO: Реализовать сброс настроек
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
async Task HandleSaveAction(System.Collections.Specialized.NameValueCollection formData)
|
||
{
|
||
// Здесь обрабатываем сохранение настроек
|
||
Debug.Log("Обработка сохранения настроек...");
|
||
|
||
// Пример: получаем значения из формы и обновляем настройки
|
||
foreach (string key in formData.AllKeys)
|
||
{
|
||
string value = formData[key];
|
||
Debug.Log($"Поле формы: {key} = {value}");
|
||
|
||
// TODO: Обновить SettingsManager.Container.Settings на основе данных формы
|
||
}
|
||
|
||
// TODO: Сохранить настройки
|
||
// SettingsManager.Save();
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
async Task HandleResetAction(System.Collections.Specialized.NameValueCollection formData)
|
||
{
|
||
// Здесь обрабатываем сброс настроек
|
||
Debug.Log("Обработка сброса настроек...");
|
||
|
||
// TODO: Сбросить настройки к значениям по умолчанию
|
||
|
||
await Task.CompletedTask;
|
||
}
|
||
|
||
void SendHtmlResponse(HttpListenerContext context, string html, int statusCode = 200)
|
||
{
|
||
try
|
||
{
|
||
byte[] buffer = Encoding.UTF8.GetBytes(html);
|
||
context.Response.StatusCode = statusCode;
|
||
context.Response.ContentType = "text/html; charset=utf-8";
|
||
context.Response.ContentLength64 = buffer.Length;
|
||
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
|
||
context.Response.OutputStream.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError($"Error sending response: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
isRunning = false;
|
||
listener?.Stop();
|
||
listener?.Close();
|
||
}
|
||
|
||
private void OnApplicationQuit()
|
||
{
|
||
isRunning = false;
|
||
listener?.Stop();
|
||
listener?.Close();
|
||
}
|
||
} |