はじめに
こんにちは!@mahiro0x00です。
この記事ではVBScriptやIIS、それらを取り巻く技術について、サンプル実装、全体のアーキテクチャ、HTTPリクエスト処理フローなど複数の観点から解説していきたいと思います。
モダン技術の波に揉まれる皆さん、たまには一息ついて、古代技術の深淵を覗いてみてはいかがでしょうか。
基礎知識
IISとは
IIS (Internet Information Services) は、Microsoftが開発したWebサーバです。
Windows ServerというOSのWeb基盤として広く使用され、具体的には静的/動的なWebコンテンツの配信、アプリケーションホスティング、Webサービスのプラットフォームとして利用されています。
VBScriptとは
VBScript (Visual Basic Script) の略で、Microsoftが開発したVisual Basicに基づくスクリプト言語です。
実体である実行エンジンはVBScript.dllと呼ばれるDLL (Dynamic Link Library)で、プロセス内のメモリにロードされる関数やコード群です。
他のスクリプト言語であるPythonのような独立して実行できる&&実態がバイナリのものとは異なり、VBSは他プロセスから呼び出されるライブラリのようなイメージです。
IISでは、このVBScript.dllがw3wp.exeのプロセスのメモリにロードされ、インタープリタとして機能します。(後半で詳しく)
このインタープリタがスクリプトの構文解析を行い、メモリ上にAST(抽象構文木)を生成、その後スクリプトの逐次解釈実行を行いプログラムを実行しています。
ASPとは
Active Server Pages (ASP) は、Webページ内にVBSを埋め込んで動的なWebコンテンツを生成するフレームワークです。(ここで呼ぶASPはClassic ASPを指します。ちなみに現在その後継はASP.NET Coreが担っています。)
実体はASP.dll(おそらく)というDLLで、これもVBScript.dllと同様にw3wp.exeプロセスのメモリ上にロードされます。
ASPファイルのサンプルは以下です。
<html>
<body>
<%
' VBSブロック
Dim currentTime
currentTime = Now()
%>
<h1>現在時刻: <%= currentTime %></h1>
</body>
</html>
上記のようにHTMLにVBSを混在させて記述ができます。このファイルはIIS上で解析、実行され動的にHTMLが生成されます。
後述しますが、ASPには上記以外にもセッション管理やDB接続などの機能を提供しています。
VBSとASPで作る簡単なAPI実装
アプリケーションへの合計アクセス数(totalAccess) とユーザの最終アクセス日(lastAccess)を返却するAPIです。
<%@ Language=VBScript %>
<%
Response.ContentType = "application/json"
' アクセスカウンターをApplication変数で管理
If Application("AccessCount") = "" Then
Application.Lock
Application("AccessCount") = 0
Application.UnLock
End If
' セッション変数で最終アクセス時刻を保存
If Session("LastAccess") = "" Then
Session("LastAccess") = Now()
End If
' アクセスカウントを増やす。Lockを使い複数ユーザーからの同時アクセスによる競合を防ぐ
Application.Lock
Application("AccessCount") = Application("AccessCount") + 1
Application.UnLock
' レスポンスの構築
Response.Write "{"
Response.Write """totalAccess"": " & Application("AccessCount") & ","
Response.Write """lastAccess"": """ & Session("LastAccess") & """"
Response.Write "}"
%>
ApplicationとSessionについて
Application はアプリケーション全体で共有される変数を格納するオブジェクトです。すべてのユーザー間で値が共有され、実行しているプロセス(w3wp.exe)自体が再起動されるまで値は保持されます。
Sessionは個々のユーザーセッションに紐付いた変数を格納するオブジェクトです。ブラウザごとに独立した値を持ち、セッションのタイムアウトかCookieのSessionが破棄されるまで値は保持されます。
ユーザー固有の情報(ログイン情報やショッピングカートなど)の管理によく使用されます。
サンプルを見てわかるように、Sessionは明示的な初期化や更新が必要ありません。
サーバアクセス時に自動でセッションの有効期限の延長や、クライアントのCookieにセッションがなければ新規作成、さらにセッションがタイムアウトしていた場合は新規セッションの発行もしてくれます。
初見ではイメージしづらいですが、このあたりの処理をフレームワーク側(ASP)が吸収してくれてるのは楽でいいですね。
HTTPリクエスト処理
HTTPリクエストの処理について全体像はこちらです。この図をベースに各技術の詳細を追っていきます。
各概念の詳細な説明
HTTP.sys
HTTP.sys (HTTP Protocol Stack) は、カーネルモードで動作するHTTPリスナーです。ネットワークからのHTTPリクエストを受け付け WebサーバであるIISに渡し、処理された応答をクライアントに返却します。
特徴として、カーネルモードでのキャッシュやワーカプロセスに渡す際のキューイング、事前処理やセキュリティフィルターといった役割を持ちます。
カーネルモードで実装されていることで、カーネルモードで動くネットワークスタックからHTTPリクエストを直接受け取り処理することができます。一方ユーザモードで実装すると、ネットワークデータ受信のたびにカーネル⇄ユーザ間のコンテキストスイッチが必要で、オーバーヘッドとなります。
WWWサービス (World Wide Web Publishing Service)
WWWサービスは、HTTPの管理や構成、プロセス管理などを行うサービスです。省略してW3SVCとも呼ばれます。
Windowsプロセスアクティブ化サービス
Windowsプロセスアクティブ化サービス(WAS) は、Application Poolやワーカプロセスの管理行うサービスです。以前はこれらの処理もWWWサービスで行っていましたが、IIS7以降ではWASへと切り出されています。
WASに切り出されたことによって、HTTP関連の処理とアプリケーション操作の責務を分離させることができました。
これにより、WWWサービスはIIS全体の構成管理(リクエストをどこに渡すかなどの設定) を担当し、HTTP.sysは直接WASにリクエスト通知を行う仕組みになっています。
また、HTTPクライアント以外からもWASを呼び出してアプリケーションの操作が可能になりました。
前述したWWWサービス、WASはSvchost.exe上で動いており、これらがHTTPリクエストを適切なアプリケーションに転送するIISの中核となるプロセスになります。
Application Pool
Applicatino Poolは アプリケーションの実行環境を定義する設定グループです。
具体的には、メモリ制限、CPU使用率制限、リサイクル間隔(再起動する間隔) などの設定があります。
<applicationPools>
<add name="ClassicASP_Pool">
<!-- 実行ユーザー -->
<processModel identityType="NetworkService" />
<!-- リソース制限 -->
<cpu limit="85" /> <!-- CPU使用率制限 -->
<recycling>
<periodicRestart>
<privateMemory>1048576</privateMemory> <!-- メモリ制限: 1GB -->
</periodicRestart>
</recycling>
</add>
</applicationPools>
w3wp.exe
w3wp.exeは、Application Poolの宣言的な設定通りに動く実体のプロセスです。WASが上記の設定に基づいてw3wp.exeを起動し、プロセスを監視・制御します。
これがWebアプリケーションのコアとなる部分で、HTTPリクエストの受付やその処理、ASP/VBScriptの実行などします。
HTTPリクエスト処理フロー
リクエストのフローを整理してみると以下のようになります。
① HTTPリクエスト:
ブラウザからHTTP.sysに対してHTTPリクエストが送信されます。
② リクエスト保持:
HTTP.sysは受け取ったリクエストをリクエストキューに保持します。
③ 構成情報を参照:
HTTP.sysはIISの構成情報(WWWサービスから提供される)を基にURLを解析し、対象のサイトやApplication Pool、物理パスを特定します。
④ リクエスト検知:
リクエストキューにリクエストが追加されると、WWWサービスがこれを検知します。
⑤ WASに通知:
WWWサービスはWASにリクエストが来たことを通知します。
⑥ プロセス確認/起動:
WASは、リクエストの処理に必要なApplication Poolを特定し、対応するワーカープロセス(w3wp.exe)の状態を確認します。プロセスが起動していない場合は新たに起動します。
⑦ リクエスト転送:
HTTP.sysはリクエストキューからリクエストを取り出し、該当するワーカープロセスに直接転送します。
ワーカプロセスでの処理 (本記事で紹介したASP/VBSのサンプルAPIの場合)
- 受け取ったASPファイルを参照し、スクリプトをメモリ上にロードします
- ASP.dllを使用してHTMLとVBSブロックを分離します
- VBScript.dllを使用してVBSコードを解釈実行します
- 実行結果とHTMLを結合してレスポンスを生成します
⑧ レスポンス生成:
ワーカープロセスはリクエストを処理し、レスポンスを生成してリクエストキューに戻します。
⑨ HTTPレスポンス準備:
HTTP.sysはリクエストキューからレスポンスを取り出し、レスポンスを準備します。
⑩ クライアントへの応答:
HTTP.sysはクライアント(ブラウザ)にレスポンスを返却します。
おわりに
本記事ではVBSやIISについて深掘りしてみました。VBSやIIS自体ソースが公開されてないことからより詳細までは追いきれなかったのですが、HTTPリクエスト受付時のフローはある程度想像できるようになったのではないでしょうか?
少しでもこの記事がどなたかの参考になれば幸いです。もし間違いなどあれば、気軽にご指摘お願いします。
余談
古すぎてドキュメントすら見当たらないものがありました...リプレイス頑張っていきたいですね