| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- 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
- {
- /// <summary>
- /// control 进程内的本地 HTTP 小服务,只监听 127.0.0.1:port。
- /// 阶段1:/ping、/status。阶段2:/status 补全(rich) + /serial/pause|resume(借串口) + /shutdown(受护栏停止)。
- /// </summary>
- public class ControlHttpServer
- {
- private readonly int _port;
- private readonly Func<StatusDto> _pingProvider; // /ping 轻量存活
- private readonly Func<object> _statusProvider; // /status 完整快照(阶段2 §6 三块)
- private readonly Func<string, bool> _shutdownHandler; // /shutdown(token 校验后安全停机)
- private readonly Func<int, bool> _serialPauseHandler; // /serial/pause(借串口:control 让路该舱)
- private readonly Func<int, bool> _serialResumeHandler; // /serial/resume(归还:恢复采集)
- private readonly Action<string> _log;
- private readonly DebugSessionManager _debug;
- private HttpListener _listener;
- private CancellationTokenSource _cts;
- public ControlHttpServer(
- int port,
- Func<StatusDto> pingProvider,
- Func<object> statusProvider,
- Func<string, bool> shutdownHandler,
- Func<int, bool> serialPauseHandler,
- Func<int, bool> serialResumeHandler,
- Action<string> 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 + "\"}";
- /// <summary>读 POST JSON body 的某字符串字段(失败返回 null)。</summary>
- 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;
- }
- /// <summary>把 POST body 整体解析为 JObject(失败返回 null)。/debug/command 多字段用。</summary>
- 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); }
- }
- }
- }
|