HttpListenerについて

 お世話になっております。

 今System.Net.HttpListenerを使用して通信しようとしているのですが、こいつの動作が分かりにくいので実験します。

 サーバーを開始するには、以下の手順を実行します。

  1. HttpListenerクラスのインスタンスを作る。
  2. Prefixを設定する。
  3. Startメソッドを実行する。
  4. リクエストの受信を待つ。

 上記4につきましては、今回はコールバックによる実装としました。このときのポイントとしては、BeginGetContextの第二パラメータにHttpListenerのインスタンスを渡すことです。コールバックメソッドから、HttpListener.IsListeningを参照するには、これがベストな方法かと考えます。

 次に、受信時の処理です。今回は、単純なエコーバック+試験用のスリープのみです。コールバックメソッド全体をlockしているのは、こうしないと処理中(今回はスリープ中)にも関わらず、HttpListenerを停止すると接続を切断してしまうからです。クライアントで切断に対応する実装方法もありますが、今回はわかりやすくするためにlockするようにしました。


 最後に、サーバーの停止です。サーバーの停止には、以下4つのメソッドがあります。

  1. HttpListener.Abort Method (System.Net) | Microsoft Docs
  2. HttpListener.Close Method (System.Net) | Microsoft Docs
  3. HttpListener.Stop Method (System.Net) | Microsoft Docs
  4. HttpListener.IDisposable.Dispose Method (System.Net) | Microsoft Docs

 まず、4は「not intended to be used directly from your code.」とありますので、使えません。残りのメソッドは、状況に応じて使い分けます。

 以上で実装は完了です。Enterキー入力でサーバーを停止します。スリープ中にEnterを押した時は、応答の送信が完了してから終了します。
 ただ。サーバーが停止するときにコールバックが動作するパターンと動作しないパターンがあります。この点はまだ不明です。

 以下、コードです。

using System;
using System.Net;
using System.IO;
using System.Threading;
using System.Diagnostics;

namespace Http {
  // 試験用HTTPサーバー
  public sealed class HttpServer {
    private HttpListener listener;
    private readonly object lockObj = new object();
    
    //  HttpListenerのインスタンスを作成する。
    //  Prefixをセットする。
    //  
    public void Start() {
      lock(this.lockObj){
        this.listener = new HttpListener();
        this.listener.Prefixes.Add("http://*:9999/");
        this.listener.Start();
        this.listener.BeginGetContext(this.OnRequested, this.listener);
      }
    }

    //  要求待ちを終了する。
    public void Stop() {
      lock(this.lockObj) {
        this.listener.Close();
      }
    }

    //  要求を受信した時に実行するメソッド。
    public void OnRequested(IAsyncResult result){
      lock(this.lockObj){
        Debug.Assert(result != null, "result is not null.");
        HttpListener listener = (HttpListener)result.AsyncState;
        if(!listener.IsListening){
          Console.WriteLine("listening finished.");
          return;
        }
        
        Console.WriteLine("OnRequested start.");
        Console.WriteLine("OnRequested result.IsCompleted " + result.IsCompleted);
        Console.WriteLine("OnRequested result.CompletedSynchronously " + result.CompletedSynchronously);
        Console.WriteLine("OnRequested listener.IsListening " + listener.IsListening);

        HttpListenerContext ctx = listener.EndGetContext(result);
        HttpListenerRequest req = null;
        HttpListenerResponse res = null;
        StreamReader reader = null;
        StreamWriter writer = null;
        
        //  試験用のスリープ
        Thread.Sleep(10000);
        
        try{
          req = ctx.Request;
          res = ctx.Response;
          
          reader = new StreamReader(req.InputStream);
          writer = new StreamWriter(res.OutputStream);
          
          string received = reader.ReadToEnd();
          Console.WriteLine(received);
          writer.Write(received);
          writer.Flush();
          
        }catch(Exception ex){
          Console.WriteLine(ex.ToString());
        }finally{
          try{
            if(null != writer) writer.Close();
            if(null != reader) reader.Close();
            if(null != res) res.Close();
          }catch(Exception ex){
            Console.WriteLine(ex.ToString());
          }
        }

        listener.BeginGetContext(this.OnRequested, listener);
      }
      Console.WriteLine("OnRequested end");
      Console.WriteLine();

    }

    public static void Main(){
      HttpServer server = new HttpServer();
      server.Start();
      Console.ReadLine();
      server.Stop();
    }
  }
}

以上