ControlHttpServer.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. using System;
  2. using System.IO;
  3. using System.Net;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Newtonsoft.Json;
  8. using Newtonsoft.Json.Linq;
  9. namespace IvfTl.ControlHost
  10. {
  11. /// <summary>
  12. /// control 进程内的本地 HTTP 小服务,只监听 127.0.0.1:port。
  13. /// 阶段1:/ping、/status。阶段2:/status 补全(rich) + /serial/pause|resume(借串口) + /shutdown(受护栏停止)。
  14. /// </summary>
  15. public class ControlHttpServer
  16. {
  17. private readonly int _port;
  18. private readonly Func<StatusDto> _pingProvider; // /ping 轻量存活
  19. private readonly Func<object> _statusProvider; // /status 完整快照(阶段2 §6 三块)
  20. private readonly Func<string, bool> _shutdownHandler; // /shutdown(token 校验后安全停机)
  21. private readonly Func<int, bool> _serialPauseHandler; // /serial/pause(借串口:control 让路该舱)
  22. private readonly Func<int, bool> _serialResumeHandler; // /serial/resume(归还:恢复采集)
  23. private readonly Action<string> _log;
  24. private HttpListener _listener;
  25. private CancellationTokenSource _cts;
  26. public ControlHttpServer(
  27. int port,
  28. Func<StatusDto> pingProvider,
  29. Func<object> statusProvider,
  30. Func<string, bool> shutdownHandler,
  31. Func<int, bool> serialPauseHandler,
  32. Func<int, bool> serialResumeHandler,
  33. Action<string> log)
  34. {
  35. _port = port;
  36. _pingProvider = pingProvider;
  37. _statusProvider = statusProvider;
  38. _shutdownHandler = shutdownHandler;
  39. _serialPauseHandler = serialPauseHandler;
  40. _serialResumeHandler = serialResumeHandler;
  41. _log = log ?? (_ => { });
  42. }
  43. public void Start()
  44. {
  45. _listener = new HttpListener();
  46. // 仅本机回环,拒绝外部访问(防外部调停机/借串口)。
  47. _listener.Prefixes.Add($"http://127.0.0.1:{_port}/");
  48. _listener.Start();
  49. _cts = new CancellationTokenSource();
  50. _log($"ControlHttpServer 监听 http://127.0.0.1:{_port}/");
  51. Task.Run(() => Loop(_cts.Token));
  52. }
  53. private async Task Loop(CancellationToken token)
  54. {
  55. while (!token.IsCancellationRequested)
  56. {
  57. HttpListenerContext ctx;
  58. try { ctx = await _listener.GetContextAsync(); }
  59. catch (Exception ex) { if (!token.IsCancellationRequested) _log("HttpListener 异常:" + ex.Message); break; }
  60. try { Handle(ctx); }
  61. catch (Exception ex) { _log("处理请求异常:" + ex.Message); }
  62. }
  63. }
  64. private void Handle(HttpListenerContext ctx)
  65. {
  66. string path = ctx.Request.Url.AbsolutePath.TrimEnd('/').ToLowerInvariant();
  67. string method = ctx.Request.HttpMethod.ToUpperInvariant();
  68. string body;
  69. int code = 200;
  70. switch (path)
  71. {
  72. case "/ping":
  73. body = JsonConvert.SerializeObject(_pingProvider());
  74. break;
  75. case "/status":
  76. body = JsonConvert.SerializeObject(_statusProvider());
  77. break;
  78. case "/shutdown":
  79. if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
  80. {
  81. string token = ReadField(ctx, "token");
  82. bool ok = _shutdownHandler != null && _shutdownHandler(token ?? "");
  83. code = ok ? 200 : 403;
  84. body = "{\"ok\":" + (ok ? "true" : "false") + (ok ? "" : ",\"error\":\"token invalid\"") + "}";
  85. }
  86. break;
  87. case "/serial/pause":
  88. case "/serial/resume":
  89. if (method != "POST") { code = 405; body = Err("method not allowed"); break; }
  90. {
  91. int houseSn = ReadIntField(ctx, "houseSn");
  92. bool isPause = path == "/serial/pause";
  93. var handler = isPause ? _serialPauseHandler : _serialResumeHandler;
  94. bool ok = handler != null && houseSn > 0 && handler(houseSn);
  95. code = ok ? 200 : 400;
  96. body = "{\"ok\":" + (ok ? "true" : "false") + ",\"houseSn\":" + houseSn + (ok ? "" : ",\"error\":\"bad houseSn or handler\"") + "}";
  97. }
  98. break;
  99. default:
  100. code = 404; body = Err("not found");
  101. break;
  102. }
  103. byte[] buf = Encoding.UTF8.GetBytes(body);
  104. ctx.Response.StatusCode = code;
  105. ctx.Response.ContentType = "application/json";
  106. ctx.Response.ContentLength64 = buf.Length;
  107. ctx.Response.OutputStream.Write(buf, 0, buf.Length);
  108. ctx.Response.OutputStream.Close();
  109. }
  110. private static string Err(string msg) => "{\"ok\":false,\"error\":\"" + msg + "\"}";
  111. /// <summary>读 POST JSON body 的某字符串字段(失败返回 null)。</summary>
  112. private string ReadField(HttpListenerContext ctx, string field)
  113. {
  114. try
  115. {
  116. using (var sr = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8))
  117. {
  118. string raw = sr.ReadToEnd();
  119. if (string.IsNullOrEmpty(raw)) return null;
  120. var jo = JObject.Parse(raw);
  121. return jo[field]?.ToString();
  122. }
  123. }
  124. catch (Exception ex) { _log("解析请求体异常:" + ex.Message); return null; }
  125. }
  126. private int ReadIntField(HttpListenerContext ctx, string field)
  127. {
  128. string s = ReadField(ctx, field);
  129. return int.TryParse(s, out int v) ? v : -1;
  130. }
  131. public void Stop()
  132. {
  133. try { _cts?.Cancel(); _listener?.Stop(); _listener?.Close(); }
  134. catch (Exception ex) { _log("ControlHttpServer 停止异常:" + ex.Message); }
  135. }
  136. }
  137. }