-
Notifications
You must be signed in to change notification settings - Fork 0
MS_HttpApplicationModuleHandler
- 戻る(ASP.NET)
HttpApplication(Global.asax)、
HttpModule、HttpHandler等について。
補足(三者の関係を先に): 名前が似ていて混同されやすいので、
役割の違いを先に整理しておく。【リクエストの流れ】 ブラウザ ↓ [ HttpApplication ] … アプリ全体の共通処理(Global.asax) ↓ [ HttpModule ] [ HttpModule ] [ HttpModule ] … ↑ 全リクエストが通る「フィルター」。何個でも並べられる ↓ [ HttpHandler ] … 実際に応答を作る「終点」。1 つだけ (.aspx → PageHandlerFactory、.ashx → 自作ハンドラー …) ↓ [ HttpModule ](後処理)… ↓ ブラウザ
個数 役割 対応する ASP.NET Core HttpApplication 1(プール) アプリ全体のイベント Program.cs/ ミドルウェアHttpModule 何個でも 横断的な処理(認証、ログ、書き換え) ミドルウェア HttpHandler 1 つだけ 応答の生成 エンドポイント(Map〜) HttpModule = ミドルウェア、HttpHandler = エンドポイント
という対応で捉えると、ASP.NET Coreへの移行 の
見通しが良くなる。
-
共通の処理を定義する。
- メソッド
- プロパティ
- イベント
-
Global.asax ファイルで定義する。
要求を傍受、参加、または変更できる。
-
HttpHandlerの実行前と実行後に呼び出される。
-
インターフェイス IHttpModule を実装する。
ISAPI拡張機能に類似した機能。
-
個々のエンドポイント要求を処理するために使用する。
-
要求を処理するために使用されるハンドラーは 1 つのみ。
-
インターフェイス IHttpHandler を実装する。
-
アプリ内の HTTP URL または URL 拡張機能のグループを処理できる。
-
また、HttpModule、HttpHandlerで処理可能な、
HTTP のリクエスト・レスポンスの書き換えについてもまとめた。
-
同時実行数に合わせてインスタンス化される。
-
リクエスト-レスポンス間は専有する(メンバはスレッドセーフ)。
-
インスタンス化されたオブジェクトは複数ユーザ間で使いまわされる。
-
なお、HttpApplicationとHttpModuleは
アクセスできるイベントが異なる。-
HttpApplication
- アプリケーションレベルのイベント
- リクエストレベルのイベント
-
HttpModule
- リクエストレベルのイベント
-
補足(原文が言う「思わぬ落とし穴」の中身): この 3 行が
本ページで最も重要な記述である。【誤解】 HttpApplication / HttpModule は「リクエストごとに new される」 → インスタンス変数に状態を持ってよい 【実際】 プール(インスタンス プール)から取り出して使い回される ・1 リクエストの間は 1 スレッドが専有する(ここは安全) ・しかし、リクエストが終わるとプールに戻る ・次のリクエストで「別のユーザー」に貸し出される → 前のユーザーの値が残っていると情報漏洩public class MyModule : IHttpModule { private string _userId; // ← 危険。使い回される public void Init(HttpApplication app) { app.AuthenticateRequest += (s, e) => { _userId = GetUser(); }; app.EndRequest += (s, e) => { Log(_userId); }; // → 同一リクエスト内なら動くが、 // クリアし忘れると次のリクエストに残る } }リクエスト単位の状態は
HttpContext.Itemsに持つのが定石である。HttpContext.Current.Items["UserId"] = userId; // リクエスト終了で破棄される
Initは初回のみ呼ばれる(後述の★印)ため、
Initで確保したものは全リクエストで共有される点にも注意する。ASP.NET Core のミドルウェアでも同じ罠がある
(ミドルウェアのインスタンスはアプリ起動時に 1 つだけ作られる)。public class MyMiddleware { private readonly RequestDelegate _next; // ここにリクエスト固有の状態を持ってはいけない(Singleton 相当) public async Task InvokeAsync(HttpContext ctx, IScopedService svc) { // ↑ Scoped は引数で受け取る await _next(ctx); } }DI の Captive Dependency と同根の問題である。
- リクエストの都度、IHttpHandlerFactory によって、
HttpHandlerをインスタンス化される。 -
<httpHandlers>の定義に従って、拡張子や GET,POST などに応じて
適切なHttpHandlerがインスタンス化される。
補足(
IsReusableに注意):IHttpHandlerには
IsReusableプロパティがあり、
trueにすると HttpModule と同様に使い回される。public class MyHandler : IHttpHandler { public bool IsReusable => false; // ← 状態を持つなら false public void ProcessRequest(HttpContext context) { ... } }状態を持たないなら
true(インスタンス生成が省ける)、
持つならfalse、という判断になる。
迷ったらfalseにしておく方が安全である。
★は初回のみ。
- Global.Application_Start ★
- IHTTPModule
- Constructor ★
- IHTTPModule.Init ★
-
BeginRequest
- Global.BeginRequest
- IHTTPModule.BeginRequest
- Global.Application_BeginRequest
-
Global.DefaultAuthentication
-
AuthenticateRequest
- Global.AuthenticateRequest
- IHTTPModule.AuthenticateRequest
- Global.Application_AuthenticateRequest
-
PostAuthenticateRequest
- Global.PostAuthenticateRequest
- IHTTPModule.PostAuthenticateRequest
-
AuthorizeRequest
- Global.AuthorizeRequest
- IHTTPModule.AuthorizeRequest
-
PostAuthorizeRequest
- Global.PostAuthorizeRequest
- IHTTPModule.PostAuthorizeRequest
-
ResolveRequestCache
- Global.ResolveRequestCache
- IHTTPModule.ResolveRequestCache
-
PostResolveRequestCache
- Global.PostResolveRequestCache
- IHTTPModule.PostResolveRequestCache
-
Page.Constructor
-
PostMapRequestHandler
- Global.PostMapRequestHandler
- IHTTPModule.PostMapRequestHandler
-
Global.Session_Start ★
-
AcquireRequestState
- Global.AcquireRequestState
- IHTTPModule.AcquireRequestState
-
PostAcquireRequestState
- Global.PostAcquireRequestState
- IHTTPModule.PostAcquireRequestState
-
PreRequestHandlerExecute
- Global.PreRequestHandlerExecute
- IHTTPModule.PreRequestHandlerExecute
-
Page.PreInit
-
MasterPage.Constructor
-
Init
- MasterPage.ContentPlaceHolder
- MasterPage
- Page
-
Page
- InitComplete
- PreLoad
-
Load
- Page
- MasterPage
- MasterPage.ContentPlaceHolder
-
Page
- Page.LoadComplete
-
PreRender
- Page
- MasterPage
- MasterPage.ContentPlaceHolder
-
Page
- PreRenderComplete
- SaveStateComplete
-
Unload
- MasterPage.ContentPlaceHolder
- MasterPage
- Page
補足(ページ内の順序について): この 2 節は
ASP.NET Web Formsのイベント発生順 の内容と対応する。
PageHandlerFactoryが生成したPage(= HttpHandler の一種)の
内部で起きていることが、ここに展開されている。マスター ページとの順序が逆転している点が実務上の注意点。
Init : ContentPlaceHolder → MasterPage → Page (内側から外側へ) Load : Page → MasterPage → ContentPlaceHolder (外側から内側へ) Unload : ContentPlaceHolder → MasterPage → Page (内側から外側へ)
Initの時点ではまだPageの初期化が終わっていないため、
マスター ページ側からPageのプロパティを参照すると
未設定である、という事故が起きる。
-
PostRequestHandlerExecute
- Global.PostRequestHandlerExecute
- IHTTPModule.PostRequestHandlerExecute
-
ReleaseRequestState
- Global.ReleaseRequestState
- IHTTPModule.ReleaseRequestState
-
PostReleaseRequestState
- Global.PostReleaseRequestState
- IHTTPModule.PostReleaseRequestState
-
UpdateRequestCache
- Global.UpdateRequestCache
- IHTTPModule.UpdateRequestCache
-
PostUpdateRequestCache
- Global.PostUpdateRequestCache
- IHTTPModule.PostUpdateRequestCache
-
EndRequest
- Global.EndRequest
- IHTTPModule.EndRequest
-
PreSendRequestHeaders
- Global.PreSendRequestHeaders
- IHTTPModule.PreSendRequestHeaders
-
PreSendRequestContent
- Global.PreSendRequestContent
- IHTTPModule.PreSendRequestContent
- Global.Session_End ★
- IHTTPModule.Dispose ★
- Global.Application_End ★
補足(
Session_Endは当てにできない):Session_Endは
InProcモードのときしか発火しない。
セッション モード Session_EndInProc 発火する(ただし保証はされない) StateServer 発火しない SQLServer 発火しない Custom 発火しない さらに InProc でも、
- アプリケーション プールのリサイクルでは発火しない
(ASP.NET config のperiodicRestart)- プロセスが落ちれば当然発火しない
「ログアウト時の後処理」を
Session_Endに書くのは危険であり、
明示的なログアウト処理か、バッチによる期限切れ処理で担保する。
| # | イベント | 説明 |
|---|---|---|
| 1 | BeginRequest | リクエストを受信して最初に発生 |
| 2 | AuthenticateRequest | ASP.NET がユーザの認証処理の準備が完了したときに発生 |
| 3 | PostAuthenticateRequest | Post |
| 4 | AuthorizeRequest | 権限の承認処理の準備が完了したときに発生 |
| 5 | PostAuthorizeRequest | Post |
| 6 | ResolveRequestCache | リクエストに対してキャッシュからレスポンスを生成するのか レスポンスを1から生成するのかを決定する |
| 7 | PostResolveRequestCache | Post |
| 8 | PostMapRequestHandler | 現在の要求を適切なイベント ハンドラにマップすると発生 |
| 9 | AcquireRequestState | セッション変数の準備処理 |
| 10 | PostAcquireRequestState | Post |
| 11 | PreRequestHandlerExecute | 各ハンドラ実行直前に発生 |
| 12 | PostRequestHandlerExecute | 各ハンドラ実行直後に発生 |
| 13 | ReleaseRequestState | セッション変数などの値を更新・保存する |
| 14 | PostReleaseRequestState | Post |
| 15 | UpdateRequestCache | リクエストキャッシュの更新処理を行う |
| 16 | PostUpdateRequestCache | Post |
| 17 | EndRequest | クライアントのブラウザへデータを送信する直前に発生 |
| 18 | PreSendRequestHeaders | HTTP ヘッダーをクライアントに送信する直前に発生 |
| 19 | PreSendRequestContent | コンテンツをクライアントに送信する直前に発生 |
移行メモ(表記): 原文の「ReleaserequestState」は
ReleaseRequestStateの表記揺れと判断し統一した。
補足(どのイベントに書くべきか): 実務でよく使うものを整理しておく。
やりたいこと イベント 理由 URL の書き換え BeginRequestハンドラーが決まる前 認証(自前) AuthenticateRequestUserを差し替えられる認可(自前) AuthorizeRequest認証済みの前提で判定 セッションを使う AcquireRequestState以降これより前は Sessionが nullレスポンス本文の加工 BeginRequest(Response.Filterを設定)ヘッダーの追加・削除 PreSendRequestHeaders送信直前 ログ出力 EndRequest状態が確定している 「
BeginRequestでSessionを触ったら null だった」というのが
頻出のつまずきで、AcquireRequestStateより前にセッションは存在しない
ことが原因である。
- リライト ≒ URL の書き換えはHttpHandlerでも可能のようだが、
- その他の書き換え処理は、要求パイプラインに
モジュールを「プラグイン」できるHttpModuleを使うことが多い模様。
URL の書き換えは、
- HttpHandlerか、
- 要求パイプライン中の Application_BeginRequest ハンドラで
(HttpApplication、HttpModule)
行う。
- 参考
- URL書き換え(Rewriting)を行う - Netplanetes
http://www.pine4.net/Memo/Article/Archives/11
- URL書き換え(Rewriting)を行う - Netplanetes
補足(現在は IIS URL Rewrite が第一候補): 自前で
HttpModuleを書くより、IIS の URL Rewrite モジュールを使う方が
簡単で高速である(ネイティブ モジュールで、マネージド コードを通らない)。<system.webServer> <rewrite> <rules> <rule name="Redirect to HTTPS" stopProcessing="true"> <match url="(.*)" /> <conditions><add input="{HTTPS}" pattern="off" /></conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule> </rules> </rewrite> </system.webServer>**
Rewrite(内部書き換え)とRedirect(302/301 を返す)**は
別物である点に注意する。ASP.NET Core では
app.UseRewriter()
(Microsoft.AspNetCore.Rewrite)が対応する。app.UseRewriter(new RewriteOptions() .AddRedirectToHttps() .AddRewrite(@"^old/(.*)", "new/$1", skipRemainingRules: true));
ResponseBody の書き換えは、HttpModuleで Response.Filter に
デコレートされた ResponseStream を設定することで行う
(HttpApplication、HttpModule)。
- 参考
- HttpRequestのパラメータに小細工をしたい時にどうするか(・ω・)? -うさ☆うさ日記
http://d.hatena.ne.jp/machi_pon/20091203/1259842545
- HttpRequestのパラメータに小細工をしたい時にどうするか(・ω・)? -うさ☆うさ日記
補足(ASP.NET Core での対応):
Response.Filterに相当するのは
HttpResponse.Bodyの差し替えである。app.Use(async (ctx, next) => { var original = ctx.Response.Body; using var buffer = new MemoryStream(); ctx.Response.Body = buffer; await next(); buffer.Seek(0, SeekOrigin.Begin); var text = await new StreamReader(buffer).ReadToEndAsync(); text = text.Replace("foo", "bar"); // 加工 ctx.Response.Body = original; await ctx.Response.WriteAsync(text); });ただし、本文を丸ごとバッファリングするため、
メモリ使用量とストリーミング性が犠牲になる。
本当に必要か(フロント側や CDN で処理できないか)を
先に検討するのが妥当である。
ResponseHeader の書き換えは、
要求パイプライン中の PreSendRequestHeaders ハンドラで行う
(HttpApplication、HttpModule)。
- 参考
- IIS7 の機能を拡張してみる-レスポンスヘッダー内のサーバー名の改ざん – monoe's blog
https://blogs.msdn.microsoft.com/osamum/2010/04/05/iis7-2/ - IIS 7/7.5 で不要なHTTPレスポンスヘッダーを削除 « Fukui Labs
http://blog.progfast.jp/labs/index.php/arts/iis-7-httpresponseheader/
- IIS7 の機能を拡張してみる-レスポンスヘッダー内のサーバー名の改ざん – monoe's blog
補足(不要なヘッダーの削除は
web.configでできる): 参考記事が
扱っている「サーバー情報を隠す」目的なら、
コードを書かずにweb.configで対処できる。<system.web> <httpRuntime enableVersionHeader="false" /> <!-- X-AspNet-Version --> </system.web> <system.webServer> <httpProtocol> <customHeaders> <remove name="X-Powered-By" /> <!-- セキュリティ ヘッダーの追加もここで --> <add name="X-Content-Type-Options" value="nosniff" /> <add name="Strict-Transport-Security" value="max-age=31536000" /> </customHeaders> </httpProtocol> <security> <requestFiltering removeServerHeader="true" /> <!-- Server(IIS 10+) --> </security> </system.webServer>ASP.NET Core では ミドルウェアで追加するのが素直である。
app.Use(async (ctx, next) => { ctx.Response.Headers["X-Content-Type-Options"] = "nosniff"; await next(); });
PreSendRequestHeadersは ASP.NET Core には無い
(そもそも応答の書き込み前に設定するのが自然な設計になっている)。
- [INFO] ASP.NET のアプリケーション インスタンス、アプリケーション イベント、およびアプリケーション状態
https://support.microsoft.com/ja-jp/help/312607/ - [INFO] ASP.NET の HTTP モジュールと HTTP ハンドラの概要
https://support.microsoft.com/ja-jp/help/307985/ - Visual C# .NET を使用して ASP.NET HTTP モジュールを作成する方法
https://support.microsoft.com/ja-jp/help/307996/
-
HttpApplication クラス (System.Web)
https://learn.microsoft.com/ja-jp/dotnet/api/system.web.httpapplication -
IHttpModule インターフェイス (System.Web)
https://learn.microsoft.com/ja-jp/dotnet/api/system.web.ihttpmodule -
IHttpHandler インターフェイス (System.Web)
https://learn.microsoft.com/ja-jp/dotnet/api/system.web.ihttphandler -
ASP.NET HTTP モジュールとハンドラー
https://learn.microsoft.com/ja-jp/troubleshoot/developer/webapps/aspnet/development/http-modules-handlers -
HTTP ハンドラーとモジュールを ASP.NET Core ミドルウェアに移行する
https://learn.microsoft.com/ja-jp/aspnet/core/migration/http-modules
- HTTP handler vs HTTP module - Stack Overflow
https://stackoverflow.com/questions/6449132/http-handler-vs-http-module - Difference between ASP.NET HttpHandler and HttpModule
http://www.c-sharpcorner.com/blogs/difference-between-asp-net-httphandler-and-httpmodule1
- asp.net - What is the difference between HttpApplication class and IHttpModule? - Stack Overflow
https://stackoverflow.com/questions/4850056/what-is-the-difference-between-httpapplication-class-and-ihttpmodule
- ASP.NETのライフサイクルの仕組み
http://article.higlabo.com/ja/asp_net_life_cycle.html
- ASP.NET のイベント発生順序 – MiYABiS note.
http://note.miyabis.jp/2009/11/33503965.html
- ブログ表示(3) -クラスライブラリ | ++C++; // 未確認飛行 C
http://ufcpp.net/study/dotnet/aspx/blog3/ - URLのリダイレクト
http://uchukamen.com/ASPNET20/URLRedirect/index.htm
Tags: 移行, .NET開発, ASP.NET
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。