|
|
@@ -1,34 +1,52 @@
|
|
|
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;
|
|
|
|
|
|
namespace IvfTl.ControlHost
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// control 进程内的本地 HTTP 小服务,只监听 127.0.0.1:port。
|
|
|
- /// 阶段1:/ping、/status。阶段2 扩展 /serial/pause|resume、/shutdown。
|
|
|
+ /// 阶段1:/ping、/status。阶段2:/status 补全(rich) + /serial/pause|resume(借串口) + /shutdown(受护栏停止)。
|
|
|
/// </summary>
|
|
|
public class ControlHttpServer
|
|
|
{
|
|
|
private readonly int _port;
|
|
|
- private readonly Func<StatusDto> _statusProvider;
|
|
|
+ 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 HttpListener _listener;
|
|
|
private CancellationTokenSource _cts;
|
|
|
|
|
|
- public ControlHttpServer(int port, Func<StatusDto> statusProvider, Action<string> log)
|
|
|
+ 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)
|
|
|
{
|
|
|
_port = port;
|
|
|
+ _pingProvider = pingProvider;
|
|
|
_statusProvider = statusProvider;
|
|
|
+ _shutdownHandler = shutdownHandler;
|
|
|
+ _serialPauseHandler = serialPauseHandler;
|
|
|
+ _serialResumeHandler = serialResumeHandler;
|
|
|
_log = log ?? (_ => { });
|
|
|
}
|
|
|
|
|
|
public void Start()
|
|
|
{
|
|
|
_listener = new HttpListener();
|
|
|
+ // 仅本机回环,拒绝外部访问(防外部调停机/借串口)。
|
|
|
_listener.Prefixes.Add($"http://127.0.0.1:{_port}/");
|
|
|
_listener.Start();
|
|
|
_cts = new CancellationTokenSource();
|
|
|
@@ -51,16 +69,40 @@ namespace IvfTl.ControlHost
|
|
|
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;
|
|
|
default:
|
|
|
- code = 404; body = "{\"ok\":false,\"error\":\"not found\"}";
|
|
|
+ code = 404; body = Err("not found");
|
|
|
break;
|
|
|
}
|
|
|
byte[] buf = Encoding.UTF8.GetBytes(body);
|
|
|
@@ -71,6 +113,30 @@ namespace IvfTl.ControlHost
|
|
|
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;
|
|
|
+ }
|
|
|
+
|
|
|
public void Stop()
|
|
|
{
|
|
|
try { _cts?.Cancel(); _listener?.Stop(); _listener?.Close(); }
|