using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using IvfTl.ControlHost.Debug;
namespace IvfTl.ControlHost
{
///
/// control 进程内的本地 HTTP 小服务,只监听 127.0.0.1:port。
/// 阶段1:/ping、/status。阶段2:/status 补全(rich) + /serial/pause|resume(借串口) + /shutdown(受护栏停止)。
///
public class ControlHttpServer
{
private readonly int _port;
private readonly Func _pingProvider; // /ping 轻量存活
private readonly Func _statusProvider; // /status 完整快照(阶段2 §6 三块)
private readonly Func _shutdownHandler; // /shutdown(token 校验后安全停机)
private readonly Func _serialPauseHandler; // /serial/pause(借串口:control 让路该舱)
private readonly Func _serialResumeHandler; // /serial/resume(归还:恢复采集)
private readonly Action _log;
private readonly DebugSessionManager _debug;
private HttpListener _listener;
private CancellationTokenSource _cts;
public ControlHttpServer(
int port,
Func pingProvider,
Func statusProvider,
Func shutdownHandler,
Func serialPauseHandler,
Func serialResumeHandler,
Action log,
DebugSessionManager debug = null)
{
_port = port;
_pingProvider = pingProvider;
_statusProvider = statusProvider;
_shutdownHandler = shutdownHandler;
_serialPauseHandler = serialPauseHandler;
_serialResumeHandler = serialResumeHandler;
_log = log ?? (_ => { });
_debug = debug;
}
public void Start()
{
_listener = new HttpListener();
// 仅本机回环,拒绝外部访问(防外部调停机/借串口)。
_listener.Prefixes.Add($"http://127.0.0.1:{_port}/");
_listener.Start();
_cts = new CancellationTokenSource();
_log($"ControlHttpServer 监听 http://127.0.0.1:{_port}/");
Task.Run(() => Loop(_cts.Token));
}
private async Task Loop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
HttpListenerContext ctx;
try { ctx = await _listener.GetContextAsync(); }
catch (Exception ex) { if (!token.IsCancellationRequested) _log("HttpListener 异常:" + ex.Message); break; }
try { Handle(ctx); }
catch (Exception ex) { _log("处理请求异常:" + ex.Message); }
}
}
private void Handle(HttpListenerContext ctx)
{
string path = ctx.Request.Url.AbsolutePath.TrimEnd('/').ToLowerInvariant();
string method = ctx.Request.HttpMethod.ToUpperInvariant();
string body;
int code = 200;
switch (path)
{
case "/ping":
body = JsonConvert.SerializeObject(_pingProvider());
break;
case "/status":
body = JsonConvert.SerializeObject(_statusProvider());
break;
case "/shutdown":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
string token = ReadField(ctx, "token");
bool ok = _shutdownHandler != null && _shutdownHandler(token ?? "");
code = ok ? 200 : 403;
body = "{\"ok\":" + (ok ? "true" : "false") + (ok ? "" : ",\"error\":\"token invalid\"") + "}";
}
break;
case "/serial/pause":
case "/serial/resume":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
int houseSn = ReadIntField(ctx, "houseSn");
bool isPause = path == "/serial/pause";
var handler = isPause ? _serialPauseHandler : _serialResumeHandler;
bool ok = handler != null && houseSn > 0 && handler(houseSn);
code = ok ? 200 : 400;
body = "{\"ok\":" + (ok ? "true" : "false") + ",\"houseSn\":" + houseSn + (ok ? "" : ",\"error\":\"bad houseSn or handler\"") + "}";
}
break;
case "/debug/acquire":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
int houseSn = ReadIntField(ctx, "houseSn");
var r = _debug != null ? _debug.Acquire(houseSn) : DebugCommandResult.Fail("NO_HANDLE", "debug 未装配");
code = r.Ok ? 200 : 409; body = JsonConvert.SerializeObject(r);
}
break;
case "/debug/heartbeat":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
var r = _debug != null ? _debug.Heartbeat(ReadField(ctx, "sessionId")) : DebugCommandResult.Fail("SESSION_EXPIRED", "debug 未装配");
code = r.Ok ? 200 : 410; body = JsonConvert.SerializeObject(r);
}
break;
case "/debug/release":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
var r = _debug != null ? _debug.Release(ReadField(ctx, "sessionId")) : DebugCommandResult.Okay();
code = 200; body = JsonConvert.SerializeObject(r);
}
break;
case "/debug/command":
if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
{
var jo = ReadBody(ctx);
string sid = jo?["sessionId"]?.ToString();
string op = jo?["op"]?.ToString();
var argsObj = jo?["args"] as Newtonsoft.Json.Linq.JObject;
var r = _debug != null ? _debug.Execute(sid, op, argsObj) : DebugCommandResult.Fail("SESSION_EXPIRED", "debug 未装配");
code = r.Ok ? 200 : (r.Code == "SESSION_EXPIRED" ? 410 : (r.Code == "OUT_OF_RANGE" ? 400 : 200));
body = JsonConvert.SerializeObject(r);
}
break;
default:
code = 404; body = Err("not found");
break;
}
byte[] buf = Encoding.UTF8.GetBytes(body);
ctx.Response.StatusCode = code;
ctx.Response.ContentType = "application/json";
ctx.Response.ContentLength64 = buf.Length;
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
ctx.Response.OutputStream.Close();
}
private static string Err(string msg) => "{\"ok\":false,\"error\":\"" + msg + "\"}";
/// 读 POST JSON body 的某字符串字段(失败返回 null)。
private string ReadField(HttpListenerContext ctx, string field)
{
try
{
using (var sr = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8))
{
string raw = sr.ReadToEnd();
if (string.IsNullOrEmpty(raw)) return null;
var jo = JObject.Parse(raw);
return jo[field]?.ToString();
}
}
catch (Exception ex) { _log("解析请求体异常:" + ex.Message); return null; }
}
private int ReadIntField(HttpListenerContext ctx, string field)
{
string s = ReadField(ctx, field);
return int.TryParse(s, out int v) ? v : -1;
}
/// 把 POST body 整体解析为 JObject(失败返回 null)。/debug/command 多字段用。
private Newtonsoft.Json.Linq.JObject ReadBody(HttpListenerContext ctx)
{
try
{
using (var sr = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8))
{
string raw = sr.ReadToEnd();
return string.IsNullOrEmpty(raw) ? null : Newtonsoft.Json.Linq.JObject.Parse(raw);
}
}
catch (Exception ex) { _log("解析 body 异常:" + ex.Message); return null; }
}
public void Stop()
{
try { _cts?.Cancel(); _listener?.Stop(); _listener?.Close(); }
catch (Exception ex) { _log("ControlHttpServer 停止异常:" + ex.Message); }
}
}
}