■概要
エンドポイントとして、HTTP通信の処理を行う
構成
StartServer()
├─ HttpListener.Start()
├─ [受信ループ起動] AcceptLoopAsync(_cts.Token)
│ └─ loop:
│ └─ await GetContextAsync() ← 1件来るまで待つ
│ └─ ctx を Task.Run(HandleRequestAsync) に流す(並列)
│
└─ [メイン処理 ループ起動] RunMainLoopAsync(_inspectCts.Token)
1:ボタンを押したら、StartServerを呼び出す
・ HttpListener を作成して、エンドポイントの、/predict/ と /load/ を Prefixes に登録して、 start()で、起動。
・受信ループ:AcceptLoopAsync(_cts.Token)、バックグランドで起動。
・処理メインループ:RunMainLoopAsync(_inspectCts.Token)をバックグランドで起動。
2:AcceptLoopAsync(受信待ちループ)
・await _listener.GetContextAsync() で次のHTTPリクエストを待機。
・1件来るごとに Task.Run(() => HandleRequestAsync(ctx)) で処理に回す(並列化)し、すぐ次の受信待ちにする。
3:HandleRequestAsync(1リクエスト単位の処理)
・エンドポイントのパスで分岐する(/predict, /load, /predict/push, /predict/latest)
・エンドポイント, /predict(画像POST)の場合:
・Base64 → 画像化 → _imgChan に画像を投入する
・同時に _responders に TCS(返答待ちオブジェクト)を積んでいく。
・そのTCSが検査完了でSetResultされるのを待って、JSONで応答(またはタイムアウトで空配列など)。
4:RunInspectionMainLoopAsync(検査メインループ:常時動作)
・_imgChan.Reader.ReadAsync(ct) で画像を1枚取り出す(来るまで待つ)。
・RunInspectionOnceAsync() でメイン処理実行。
・処理結果を使って _responders.TryDequeue() で待っているHTTP 1件に SetResult(status=0, prediction=[[[x,y],area], ...])
・上記の処理を、キャンセル処理が入るまで回し続ける。
■ソースコード
public static bool StartServer()
{
if (_listener != null)
{
//既に開始済みの場合は、開始成功を返す。
return true;
}
// URLの整形
var baseUrl = $"http://{ServerIp}:{ServerPort}/";
if (!baseUrl.EndsWith("/")) baseUrl += "/";
var endpoint = "predict";
var prefix = baseUrl + endpoint + "/"; // エンドポイント /predict/
var loadPrefix = baseUrl + "load/"; // エンドポイント /load/
//Http通信リスナー設定&開始
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
_listener.Prefixes.Add(loadPrefix);
_listener.Start();
// AcceptLoopAsync(受信待ちループ)処理
_cts = new CancellationTokenSource();
_ = Task.Factory.StartNew(() => AcceptLoopAsync(_cts.Token),
_cts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
_inspectCts = new CancellationTokenSource();
_loopSeq = 0;
// メイン処理
_ = ContinuousOperation.RunMainLoopAsync(_inspectCts.Token);
return true;
}
private static async Task AcceptLoopAsync(CancellationToken ct)
{
try
{
while (!ct.IsCancellationRequested && _listener != null && _listener.IsListening) // リスナーが有効な間
{
HttpListenerContext ctx = null;
try
{
ctx = await _listener.GetContextAsync(); // リクエスト待ち
}
catch (ObjectDisposedException)
{
break; // リスナーが閉じられた
}
catch (HttpListenerException)
{
break;
}
catch (Exception ex)
{
MainWindow.SetActionLog("検査受信待ち中に例外発生 " + ex.Message);
continue;
}
// リクエスト処理を非同期で実行, 受信毎に非同期処理
// HandleRequestAsync(1リクエスト単位の処理)
// HandleRequestAsync => エンドポイントのパスで分岐、処理
_ = Task.Run(() => HandleRequestAsync(ctx!), ct);
}
}
finally
{
MainWindow.SetActionLog("検査受信待ち終了");
}
}
private static async Task HandleRequestAsync(HttpListenerContext ctx)
{
var path = ctx.Request.Url.AbsolutePath.TrimEnd('/');
MainWindow.SetActionLog($"[REQ] {ctx.Request.HttpMethod} {path} CT={ctx.Request.ContentType} Len={ctx.Request.ContentLength64}");
// === CORS プリフライト
if (ctx.Request.HttpMethod == "OPTIONS")
{
AddCorsHeaders(ctx.Response);
ctx.Response.StatusCode = (int)HttpStatusCode.NoContent; // 204
ctx.Response.Close();
return;
}
// ==== /predict, /predict/alive: ヘルスチェック ====
if (ctx.Request.HttpMethod == "GET" && (path.EndsWith("/predict") || path.EndsWith("/predict/alive")))
{
await WriteJsonAsync(ctx, HttpStatusCode.OK, new { status = "alive" });
return;
}
// ==== /predict/latest: 直近の予測JSONを返す(Vue等がポーリング想定)====
if (ctx.Request.HttpMethod == "GET" && path.EndsWith("/predict/latest"))
{
await WriteJsonAsync(ctx, HttpStatusCode.OK, _lastPredictionJson);
return;
}
// ==== /predict/push: 予測JSONの受け取り(外部→自分へプッシュ)====
if (ctx.Request.HttpMethod == "POST" && path.EndsWith("/predict/push"))
{
using var sr2 = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding ?? Encoding.UTF8);
var raw = await sr2.ReadToEndAsync();
try
{
using var doc2 = JsonDocument.Parse(raw);
var root2 = doc2.RootElement;
if (!root2.TryGetProperty("status", out _) || !root2.TryGetProperty("prediction", out _))
{
await WriteJsonAsync(ctx, HttpStatusCode.BadRequest, new { error = "JSONにstatusまたはpredictionが無い" });
return;
}
_lastPredictionJson = raw;
MainWindow.SetActionLog("[PUSH] Received prediction JSON.");
await WriteJsonAsync(ctx, HttpStatusCode.OK, new { ok = true });
}
catch (Exception ex)
{
await WriteJsonAsync(ctx, HttpStatusCode.BadRequest, new { error = "JSON解析エラー: " + ex.Message });
}
return;
}
if ((ctx.Request.HttpMethod == "POST" || ctx.Request.HttpMethod == "GET") && path.EndsWith("/load"))
{
try
{
}
catch (Exception ex)
{
new { status = 999, error = "JSON解析エラー: " + ex.Message });
}
return;
}
// ==== /load: ロード状態の破棄(DELETE)====
if (ctx.Request.HttpMethod == "DELETE" && path.EndsWith("/load"))
{
try
{
}
catch (Exception)
{
await WriteJsonAsync(ctx, HttpStatusCode.BadRequest, new { error = "Base64デコードに失敗しました。" });
return;
}
}
}
public static async Task RunMainLoopAsync(CancellationToken ct)
{
MainWindow.SetActionLog("[MAIN] ********* メイン処理 ループ開始 ********* ");
try
{
while (!ct.IsCancellationRequested)
{
// メイン処理記述
}
sw.Stop();
MainWindow.SetActionLog($"[MAIN] loop #{loopNo} done in {sw.Elapsed.TotalMilliseconds:F1} ms");
// ===== 1ループ終了 =====
}
}
catch (OperationCanceledException)
{
// 正常終了
}
catch (Exception ex)
{
MainWindow.SetActionLog("[MAIN メイン処理 ループ ERROR] " + ex);
}
finally
{
MainWindow.SetActionLog("[MAIN] メイン処理 finaly 終了 ");
}
}