1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# WPF HTTP通信サーバ エンドポイント処理

Posted at

■概要

エンドポイントとして、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 終了 ");
         }
     }

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?