ASP.NET Coreパイプラインアーキテクチャの中核構成要素と内部実装復元

フレームワークの内部動作を真正面から理解するには、公式のAPIを単に使用するだけでなく、その基盤となるアーキテクチャ設計を分析し、ゼロから核心部分を再構築することが最も確実な方法です。ASP.NET Coreのランタイム処理は、本質的に「HTTPリクエストを受け付け、登録された処理ステージを連鎖的に実行し、レスポンスを返す」というパイプラインモデルで構成されています。このモデルを支える7つの中核コンポーネントを分解し、内部構造とデータフローを解説します。

必要知識

  • デリゲートとラムダ式
  • ビルダーパターン
  • アダプターパターン
  • チェーンオブレスポンシビリティ

1. HttpContext(実行コンテキスト)

HTTPトランザクションの全行程を通じてリクエスト情報とレスポンス操作を保持するコンテナです。フレームワーク内部では、サーバー層が物理的なネットワークデータを抽象化し、このオブジェクトとして標準化されます。サーバーからミドルウェアへ、あるいはミドルウェア同士で状態やデータを渡す際の単一真実の源として機能します。

public sealed class HttpContext
{
    public HttpRequest IncomingData { get; }
    public HttpResponse OutgoingData { get; }
}

public sealed class HttpRequest
{
    public Uri RequestUri { get; }
    public Dictionary<string, string> Headers { get; }
    public Stream ContentStream { get; }
}

public sealed class HttpResponse
{
    public Dictionary<string, string> Headers { get; }
    public Stream ContentStream { get; }
    public int Status { get; set; }
}

2. RequestDelegate(非同期ハンドラシグネチャ)

パイプライン上の各処理ノードが従う実行契約です。C#のデリゲートを活用することで、型安全な関数ポインタとして振る舞い、開閉の原則に則った拡張性を確保します。異なる実装を持つ処理モジュールを同一のインターフェースで呼び出す標準化された形式です。

public delegate Task RequestHandler(HttpContext ctx);

3. Middleware(処理ステージ)

単独で完結するのではなく、前後の処理と結合してパイプラインを形成するユニットです。本質的には「次のハンドラを受け取り、新しいハンドラを返す」関数として設計されます。入力として後続の処理チェーンを受け取り、現在のロジックを前後に挟み込んだ新しいデリゲートを出力します。

using HandlerFactory = Func<RequestHandler, RequestHandler>;
private readonly List<HandlerFactory> _stages = new();

4. ApplicationBuilder(パイプライン組立て器)

個別のミドルウェアを収集し、最終的な実行チェーンをコンパイルする役割を持ちます。ビルダーパターンを採用し、宣言的な設定と内部的な最適化を分離します。内部では終端ハンドラを定義し、登録されたファクトリを順次適用することで、単一のデリゲートに収束させます。

public interface IPipelineConfigurator
{
    IPipelineConfigurator AddStage(HandlerFactory stage);
    RequestHandler Compile();
}

public class PipelineConfigurator : IPipelineConfigurator
{
    private readonly List<HandlerFactory> _collectedStages = new();

    public IPipelineConfigurator AddStage(HandlerFactory stage)
    {
        _collectedStages.Add(stage);
        return this;
    }

    public RequestHandler Compile()
    {
        // 登録順に実行させるため、連結処理の前にリストを逆順にする
        _collectedStages.Reverse(); 

        RequestHandler terminator = ctx =>
        {
            ctx.OutgoingData.Status = 404;
            return Task.CompletedTask;
        };

        foreach (var factory in _collectedStages)
        {
            terminator = factory(terminator);
        }

        return terminator;
    }
}

5. Server(リスナとコンテキスト生成層)

外部からのTCP/HTTP接続を監視し、生データを取り込みます。ASP.NET Coreでは複数のサーバー実装を切り替えるため、アダプターパターンが導入されています。サーバー固有のFeature集合を標準的なコンテキストへ変換し、パイプラインの入り口であるハンドラに渡します。

public interface IWebServer
{
    Task ListenAsync(RequestHandler appHandler);
}

public class SocketListenerServer : IWebServer
{
    private readonly string[] _endpoints;

    public SocketListenerServer(params string[] endpoints) => _endpoints = endpoints;

    public async Task ListenAsync(RequestHandler appHandler)
    {
        Console.WriteLine($"Listening on: {string.Join(", ", _endpoints)}");
        while (true)
        {
            // ネットワークリスニングの抽象化
            var rawPacket = await CaptureNetworkDataAsync();
            var featureSet = MapFeatures(rawPacket);
            var ctx = new HttpContext(featureSet);

            await appHandler(ctx);

            SendResponse(ctx);
        }
    }
}

6. WebHost(ランタイム実行体)

組まれたパイプラインとサーバー実装を結合し、アプリケーションの実行を制御するランタイムコンテナです。依存オブジェクトを保持し、起動信号を受け取るとサーバーのリスン処理を委譲するシンプルな構造です。

public interface IRuntimeHost
{
    Task RunAsync();
}

public class RuntimeHost : IRuntimeHost
{
    private readonly IWebServer _listener;
    private readonly RequestHandler _appPipeline;

    public RuntimeHost(IWebServer listener, RequestHandler pipeline)
    {
        _listener = listener;
        _appPipeline = pipeline;
    }

    public Task RunAsync() => _listener.ListenAsync(_appPipeline);
}

7. WebHostBuilder(構成の集約と初期化)

設定ファイル、環境変数、サーバー選択、ミドルウェア登録ロジックを収集し、最終的に実行可能なランタイムホストを生成するファクトリです。エントリーポイントでは、このビルダーを使用して設定を宣言し、生成メソッドを呼び出すことで内部状態が凍結され、実際のインスタンスが構築されます。

public interface IHostConfigurator
{
    IHostConfigurator AssignServer(IWebServer srv);
    IHostConfigurator RegisterPipeline(Action<IPipelineConfigurator> setup);
    IRuntimeHost Resolve();
}

public class HostConfigurator : IHostConfigurator
{
    private IWebServer _selectedServer;
    private readonly List<Action<IPipelineConfigurator>> _setupActions = new();

    public IHostConfigurator AssignServer(IWebServer srv)
    {
        _selectedServer = srv;
        return this;
    }

    public IHostConfigurator RegisterPipeline(Action<IPipelineConfigurator> setup)
    {
        _setupActions.Add(setup);
        return this;
    }

    public IRuntimeHost Resolve()
    {
        var pipelineBuilder = new PipelineConfigurator();
        foreach (var action in _setupActions)
        {
            action(pipelineBuilder);
        }

        var compiledHandler = pipelineBuilder.Compile();
        return new RuntimeHost(_selectedServer, compiledHandler);
    }
}

初期化フェーズではホスト構成器が設定を収集し、解決呼び出し時にパイプライン組立て器がミドルウェアチェーンを逆順で連結します。生成された実行体によってサーバーが起動し、外部リクエストが発生するとアダプター層がコンテキストを構築。最後にコンパイル済みのデリゲートチェーンが順次実行され、レスポンスがクライアントへ返還されます。この一連のデータフローと状態遷移が、基盤となるアーキテクチャの核心を成しています。

タグ: ASP.NET Core C# パイプラインアーキテクチャ ミドルウェア デリゲート

6月3日 18:10 投稿