using System; using System.Collections.Generic; using System.Net; using System.Text; using System.Threading; using UnityEngine; namespace KindRetargeting.Remote { /// /// HTTP 서버 - 웹 리모컨 UI 페이지를 제공합니다. /// Resources 폴더에서 CSS, HTML 템플릿, JavaScript를 로드합니다. /// public class RetargetingHTTPServer { private HttpListener listener; private Thread listenerThread; private bool isRunning = false; private int httpPort; private int wsPort; private string cachedCSS = ""; private string cachedTemplate = ""; private string cachedJS = ""; private List boundAddresses = new List(); public bool IsRunning => isRunning; public IReadOnlyList BoundAddresses => boundAddresses; public RetargetingHTTPServer(int httpPort, int wsPort) { this.httpPort = httpPort; this.wsPort = wsPort; } public void Start() { if (isRunning) return; LoadResources(); try { listener = new HttpListener(); boundAddresses.Clear(); // 로컬 접속 지원 listener.Prefixes.Add($"http://localhost:{httpPort}/"); boundAddresses.Add($"http://localhost:{httpPort}"); listener.Prefixes.Add($"http://127.0.0.1:{httpPort}/"); boundAddresses.Add($"http://127.0.0.1:{httpPort}"); // 외부 접속도 시도 try { listener.Prefixes.Add($"http://+:{httpPort}/"); string localIP = GetLocalIPAddress(); if (!string.IsNullOrEmpty(localIP)) { boundAddresses.Add($"http://{localIP}:{httpPort}"); } } catch (Exception) { Debug.LogWarning("[RetargetingHTTP] 외부 접속 바인딩 실패. localhost만 사용 가능합니다."); } listener.Start(); isRunning = true; listenerThread = new Thread(HandleRequests); listenerThread.IsBackground = true; listenerThread.Start(); Debug.Log("[RetargetingHTTP] HTTP 서버 시작됨"); foreach (var addr in boundAddresses) { Debug.Log($"[RetargetingHTTP] 접속: {addr}"); } } catch (Exception ex) { Debug.LogError($"[RetargetingHTTP] 서버 시작 실패: {ex.Message}"); } } public void Stop() { if (!isRunning) return; isRunning = false; try { listener?.Stop(); listener?.Close(); } catch (Exception) { } Debug.Log("[RetargetingHTTP] HTTP 서버 중지됨"); } private void LoadResources() { TextAsset cssAsset = Resources.Load("KindRetargeting/retargeting_style"); TextAsset templateAsset = Resources.Load("KindRetargeting/retargeting_template"); TextAsset jsAsset = Resources.Load("KindRetargeting/retargeting_script"); cachedCSS = cssAsset != null ? cssAsset.text : GetFallbackCSS(); cachedTemplate = templateAsset != null ? templateAsset.text : GetFallbackTemplate(); cachedJS = jsAsset != null ? jsAsset.text : GetFallbackJS(); if (cssAsset == null || templateAsset == null || jsAsset == null) { Debug.LogWarning("[RetargetingHTTP] 일부 리소스를 로드할 수 없습니다. Fallback 사용 중."); } } private void HandleRequests() { while (isRunning) { try { HttpListenerContext context = listener.GetContext(); ProcessRequest(context); } catch (HttpListenerException) { // 서버 종료 시 발생 } catch (Exception ex) { if (isRunning) { Debug.LogError($"[RetargetingHTTP] 요청 처리 오류: {ex.Message}"); } } } } private void ProcessRequest(HttpListenerContext context) { try { string path = context.Request.Url.AbsolutePath; string responseContent; string contentType; if (path == "/" || path == "/index.html") { responseContent = GenerateHTML(); contentType = "text/html; charset=utf-8"; } else if (path == "/style.css") { responseContent = cachedCSS; contentType = "text/css; charset=utf-8"; } else if (path == "/script.js") { responseContent = cachedJS.Replace("{{WS_PORT}}", wsPort.ToString()); contentType = "application/javascript; charset=utf-8"; } else { context.Response.StatusCode = 404; responseContent = "Not Found"; contentType = "text/plain"; } byte[] buffer = Encoding.UTF8.GetBytes(responseContent); context.Response.ContentType = contentType; context.Response.ContentLength64 = buffer.Length; context.Response.AddHeader("Cache-Control", "no-cache"); context.Response.OutputStream.Write(buffer, 0, buffer.Length); context.Response.Close(); } catch (Exception ex) { Debug.LogError($"[RetargetingHTTP] 응답 처리 오류: {ex.Message}"); } } private string GenerateHTML() { string html = cachedTemplate; html = html.Replace("{{CSS}}", cachedCSS); html = html.Replace("{{JS}}", cachedJS.Replace("{{WS_PORT}}", wsPort.ToString())); return html; } private string GetLocalIPAddress() { try { var host = Dns.GetHostEntry(Dns.GetHostName()); foreach (var ip in host.AddressList) { if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { return ip.ToString(); } } } catch (Exception) { } return ""; } private string GetFallbackCSS() { return @" body { font-family: sans-serif; background: #1a1a2e; color: #fff; padding: 20px; } .error { color: #ff6b6b; padding: 20px; text-align: center; } "; } private string GetFallbackTemplate() { return @" 리타게팅 리모컨
리소스 파일을 찾을 수 없습니다.
Assets/Resources/KindRetargeting/ 폴더에 파일이 있는지 확인해주세요.
"; } private string GetFallbackJS() { return "console.error('JavaScript resource not found');"; } } }