WebBroker 架構分析
摘錄一篇很老的文章,老是老點,但對WebBroker說得很透徹
原文出處:http://www.newsmth.net/nForum/#!article/Delphi/752
發信人: flier (小海 //愛喝可樂^_^), 信區: Delphi
標 題: 四、WebBroker與WebReq的分析 v0.1 - 2001.8.7
發信站: BBS 水木清華站 (Wed Aug 8 00:23:28 2001)
==============================================================
VCL架構下Web服務程序實現原理分析
四、WebBroker與WebReq的分析 v0.1 - 2001.8.7
在分析完幾種不同類型的Web Application之後,讓我們把Web Application
再深挖一把,看看隱藏在其背後的TWebApplication以及TWebRequestHandler
到底是如何維護Web Application與Web Module兩部分之間的聯系的。
...
+------------+ +--------+
| +--------+ | | |
| | Web | | | |
| |Request | | | |
+------+ | +--------+ | | |
| Web +--+ Web +--+ Web |
|Server| |Application | | Module |
+------+ | | | |
| +--------+ | | |
| | Web | | | |
| |Response| | | |
| +--------+ | | |
+------------+ +--------+
...
TISAPIApplication --+
TCGIApplication --+-- TWebApplication -- TWebRequestHandler
TApacheApplication --+
...
從上圖我們可以看到,Web Application部分的不同類型實現,都是從
TWebApplication繼承出來的,這個類以及其父類TWebRequestHandler
才是VCL中為了實現不同類型Web Server程序,在Web Application這一層
做到的Server類型無關抽象的代碼所在。
在D5的實現裏,WebBroker單元的TWebApplication實現了所有的功能,
而從D6開始,被分離為TWebApplication和TWebRequestHandler兩層,
前者負責Application相關的層面的功能,後者則負責Module相關的層面的功能。
這樣一來分工更清晰,職能更明確。
讓我們先來看看負責Application相關層面的功能的WebBroker單元
...
constructor TWebApplication.Create(AOwner: TComponent);
begin
WebReq.WebRequestHandlerProc := WebRequestHandler;
inherited Create(AOwner);
Classes.ApplicationHandleException := HandleException;
if IsLibrary then
begin
IsMultiThread := True;
DLLProc := @DLLExitProc;
end;
end;
...
首先是初始化兩個全局變量。先看看WebRequestHandlerProc變量:
...
// from WebBroker
function WebRequestHandler: TWebRequestHandler; export;
begin
Result := Application;
end;
...
// from WebReq
var
WebRequestHandlerProc: function: TWebRequestHandler = nil;
...
// from WebReq
function WebRequestHandler: TWebRequestHandler;
begin
if Assigned(WebRequestHandlerProc) then
Result := WebRequestHandlerProc
else
Result := nil;
end;
...
代碼很簡單,加上很少需要直接使用WebRequestHandler函數,
因為既然我們知道他和Application等同,我想不出什麽理由不直接用
Application全局變量,呵呵。Borland這樣做的目的大概是以函數和指針的
方式實現一種類似於虛擬函數的效果,以便在WebReq單元一級更好地抽象吧。
再看看ApplicationHandleException變量。這個函數指針相對來說
使用的機會還是很多的,他提供了Application一級的錯誤處理方法的抽象。
因為無論你是以什麽形式的Application進行封裝,如Forms, WebBroker或
控制面板Applet之類的實現,都會提供相應的錯誤處理手段,而這些處理手段
的抽象,就是依賴於ApplicationHandleException函數指針。使用方式類似於
...
try
...
except
if Assigned(ApplicationHandleException) then
ApplicationHandleException(Self);
end;
...
幾乎所有的Application層實現都會替換掉此函數指針為自己的處理代碼,如
constructor TApplication.Create(AOwner: TComponent);
begin
...
if not Assigned(Classes.ApplicationHandleException) then
Classes.ApplicationHandleException := HandleException;
...
end;
...
procedure TAppletApplication.Run;
begin
if not Assigned(Classes.ApplicationHandleException) then
Classes.ApplicationHandleException := HandleException;
end;
...
緊接著,判斷是否為DLL,如果是則替換DLLProc。這個變量就比較常用了,
...
if IsLibrary then
begin
IsMultiThread := True;
DLLProc := @DLLExitProc;
end;
...
DllProc: TDLLProc; { Called whenever DLL entry point is called
}
...
TDLLProc = procedure (Reason: Integer);
...
等同於標準Win32 DLL中WinMain中對下列幾種情況的處理函數
...
const
DLL_PROCESS_DETACH = 0;
DLL_PROCESS_ATTACH = 1;
DLL_THREAD_ATTACH = 2;
DLL_THREAD_DETACH = 3;
...
如果要在DLL中完成進程、線程級構造、析構處理,則使用此函數指針,具體使用方
式
請自行查看MSDN和Delphi幫助文檔。註意這裏直接就把IsMultiThread設置為True,
此變量的討論我們在前幾節已經進行,不再多說。
前面之所以要在Dll方式時替換DllProc,是因為Application一級需要有必須執行的
析構操作調用,在DoneVCLApplication中實現
...
procedure DLLExitProc(Reason: Integer); register; export;
begin
{$IFDEF MSWINDOWS}
if Reason = DLL_PROCESS_DETACH then DoneVCLApplication;
{$ENDIF}
end;
...
procedure DoneVCLApplication; export;
begin
with Application do
begin
Destroying;
DestroyComponents;
end;
end;
...
如果不是DLL,則使用AddExitProc系統函數,將Application級析構操作函數
加入到程序Exit時自動調用的函數列表中,意義同上。
procedure TWebApplication.Run;
begin
if not IsLibrary then AddExitProc(DoneVCLApplication);
end;
至於初始化Initialize,更多的作用是提供一個抽象方法供子類繼承,也調用相應
的可能存在的初始化函數。
...
procedure TWebApplication.Initialize;
begin
// This is a place holder
if InitProc <> nil then TProcedure(InitProc);
end;
...
InitProc: Pointer; { Last installed initialization procedure }
...
InitProc是一個初始化用的全局函數指針。某些單元如ComObj通過這個函數指針
完成Application一級的初始化操作。
剩下的一個CreateForm是為了向後兼容而保存的,新的TWebApplication架構
通過類工廠方式實現類似功能,我們稍後詳細討論。
procedure TWebApplication.CreateForm(InstanceClass: TComponentClass;
var Reference);
begin
// Support CreateForm for backward compatability with D3, D4, and
// D5 web modules. D6 generated web modules register a factory.
if WebModuleClass = nil then
WebModuleClass := InstanceClass
else if WebModuleClass <> InstanceClass then
raise Exception.CreateRes(@sOnlyOneDataModuleAllowed);
end;
另外,有一個聲明我還沒有發現到底哪裏用到了他,呵呵
TServerExceptionEvent = procedure (E: Exception; wr: TWebResponse) of object
;
然後我們看看負責Module相關層面的功能的WebReq單元。
D6的WebReq單元將D5中TWebApplication類中關於Module操作部分抽象出去,
因為要實現同時支持多個WebModule一起使用,其實現相對D5來說復雜得多。
但只要你抓住其主要思路,他的實現還是非常清晰的。
關鍵在於兩個List:Factory List和Web Module List。
讓我們先來看看Factory List。
TWebModuleFactoryList的實現代碼很簡單,核心內容就是維護一個TObjectList
保存著一系列的TAbstractWebModuleFactory,另外有一個特殊的AppModuleFactory
...
procedure TWebModuleFactoryList.AddFactory(AFactory: TAbstractWebModuleFacto
ry);
begin
if FList.IndexOf(AFactory) <> -1 then
raise Exception.Create(sFactoryAlreadyRegistered);
if AFactory.IsAppModule then
begin
if Self.AppModuleFactory <> nil then
raise Exception.Create(sAppFactoryAlreadyRegistered);
Self.FAppModuleFactory := AFactory;
end;
FList.Add(AFactory);
end;
...
以上是Add Factory時的代碼,非常簡單。如果Factory已經存在則引發異常;
如果Factory是AppModule則檢查是否已經存在一個AppModule,是則引發異常,
否則保存之,然後將Factory加入到列表中。
這個IsAppModule在WebFact單元中得到支持
...
TWebAppDataModuleFactory TWebAppPageModuleFactory
| |
V V
TBaseWebModuleFactory TBaseWebPageModuleFactory
| |
| V
| TAbstractWebPageModuleFactory
| |
V V
TAbstractWebModuleFactory
...
其中TWebApp*ModuleFactory在IsAppModule中返回True,其余的返回False。
而使用到這一屬性的地方只有WebCntxt單元的TWebContext.FindApplicationModule
這個方法被用於在一個ModuleList中查找AppModule,以便從AppModule中查詢相關接口
。
我們以後介紹WebSnap時將再詳細討論。而TWebContext在D6幫助中更是直接說明為
Help for this feature or language element was unavailable
when this product shipped. Please check your release notes
(located at the root installation folder) for Help update information.
這些我們暫且不談,以後有時間我再詳細說明TWebContext這個信息提供者或者說
連接紐帶的作用。
我們接下來看看Web Module List。
以前在D5中,每個Web Server程序只允許有一個WebModule,TWebApplication
在CreateForm時將傳入的WebModule的class(註意是Class而不是Object,因為D5
也實現了我們馬上要談到的Module Pooling,因此必須保存Class以便在適當時候建立新
的
實例)保存到FWebModuleClass,然後在適當的時候,直接使用這個class建立新的實例
。
這和我們上面看到的D6的CreateForm代碼相同,但實際上在D6中,CreateForm方法
已經失去了原來的作用,取而代之的是使用前面介紹的Factory List和即將介紹的
Web Module List,允許用戶同時使用多個Web Module。D6只是為了向後兼容才保留。
D6中的Factory List的作用類似D5中的FWebModuleClass,用於在適當時候建立
Web Module實例,但因為D6支持同時使用多個Web Module,因此,在D5中將實例直接
保存的方法在D6中改為以一個TWebModuleList保存一個線程內的一系列Web Module,
然後在TWebRequestHandler中維護一個Module List Pooling,池中每個Item是一個
TWebModuleList。而需要時建立TWebModuleList實例的工作由TWebModuleFactoryList
完成。因此我前面才有兩個List一說。TWebRequestHandler主要代碼都是圍繞著
一系列TWebModuleList的管理。讓我們來看看TWebModuleList的實現。
TWebModuleList中有兩個主要的List,
...
TWebModuleList = class(TAbstractWebModuleList)
...
FFactories: TWebModuleFactoryList;
FList: TComponentList;
...
FFactories如前所述,保存著TWebModuleList對應的Factory List,
FList則保存此WebModuleList中的已有Web Module Instance。
限於篇幅原因,簡單的代碼我就不多羅嗦了,讓我們來看看幾個比較重要也相對
復雜的方法。首先是向WebModuleList裏面加入Web Module。
...
TWebModuleList = class(TAbstractWebModuleList)
...
function AddModuleClass(AClass: TComponentClass): TComponent; override;
function AddModuleName(const AName: string): TComponent; override;
procedure AddModule(AComponent: TComponent);
...
Add的方法有很多,最直接的莫過於AddModule,直接將一個現有的WebModule
加入到列表中,而AddModuleName和AddModuleClass則分別是通過名字和Class
增加新的WebModule。
...
procedure TWebModuleList.AddModule(AComponent: TComponent);
begin
Assert(FFixupLevel >= 1, ‘Module created outside of fixup block‘); { Do no
t localize }
FList.Add(AComponent);
if Assigned(FModuleAddedProc) then
FModuleAddedProc(AComponent);
end;
...
function TWebModuleList.AddModuleName(const AName: string): TComponent;
var
I: Integer;
Factory: TAbstractWebModuleFactory;
begin
Result := nil;
Assert(FindModuleName(AName) = nil);
for I := 0 to Factories.ItemCount - 1 do
begin
Factory := Factories.Items[I];
if CompareText(AName, Factory.ModuleName) = 0 then
begin
StartFixup;
try
Result := Factory.GetModule;
AddModule(Result);
break;
finally
EndFixup;
end;
end;
end;
end;
...
function TWebModuleList.AddModuleClass(
AClass: TComponentClass): TComponent;
var
I: Integer;
Factory: TAbstractWebModuleFactory;
begin
Result := nil;
Assert(FindModuleClass(AClass) = nil);
for I := 0 to Factories.ItemCount - 1 do
begin
Factory := Factories.Items[I];
if AClass = Factory.ComponentClass then
begin
StartFixup;
try
Result := Factory.GetModule;
AddModule(Result);
finally
EndFixup;
end;
end;
end;
end;
...
AddModule代碼很簡單,FModuleAddedProc是一個事件通知,可惜起了個怪怪
的名字,如果改名叫FOnModuleAdded應該更明朗一些,他通知感興趣的客戶端,有一個
新的WebModule被加入到此TWebModuleList實例中。
AddModuleName和AddModuleClass的代碼很相似,區別是前者通過比較Factory
List中項目的名字而後者通過比較Class來取得合適的Factory,然後以GetModule方法
建立實例,然後調用AddModule。在這裏我們可以看到Factory List的巨大作用。
而在建立WebModule List後,同樣可以通過兩種方法查找已有WebModule
...
function FindModuleClass(AClass: TComponentClass): TComponent; override;
function FindModuleName(const AName: string): TComponent; override;
...
代碼很簡單,我就不多說了,請自行查看代碼。
另外TWebModuleList提供了自動化的WebModuleList構造、析構支持。
...
procedure AutoCreateModules;
procedure AutoDestroyModules;
...
procedure TWebModuleList.AutoCreateModules;
var
I: Integer;
Factory: TAbstractWebModuleFactory;
begin
StartFixup;
try
for I := 0 to Factories.ItemCount - 1 do
begin
Factory := Factories.Items[I];
if Factory.CreateMode = crAlways then
if FindModuleClass(Factory.ComponentClass) = nil then
AddModule(Factory.GetModule);
end;
finally
EndFixup;
end;
end;
...
procedure TWebModuleList.AutoDestroyModules;
var
I: Integer;
Factory: TAbstractWebModuleFactory;
Component: TComponent;
begin
for I := 0 to Factories.ItemCount - 1 do
begin
Factory := Factories.Items[I];
if Factory.CacheMode = caDestroy then
begin
Component := FindModuleClass(Factory.ComponentClass);
if Assigned(Component) then
Component.Free;
end;
end;
end;
...
這裏順便把TAbstractWebModuleFactory及其子類說一說。這個基類定義在HttpApp
單元中,一大把的virtual; abstract;方法,在WebReq中提供了一個簡單的實現,
TDefaultWebModuleFactory,代碼很簡單,請大家自己閱讀,只需記住他的CacheMode
為caCache;CreateMode為crAlways即可,因為我們馬上要用到。至於WebFact裏面提供
的更專業一些的實現,我們以後有機會再說。
...
TWebModuleCreateMode = (crOnDemand, crAlways);
TWebModuleCacheMode = (caCache, caDestroy);
...
AutoCreateModules遍歷整個Factory List,查找每個Factory的CreateMode,
如果是crAlways則構造之;如果是crOnDemand,則等到以後需要使用時再構造。
而AutoDestroyModules則檢查每個Factory的CacheMode,如果是caDestroy
則在ModuleList中找到相應實例析構之,如果是caCache則保留之,以便下次直接可用。
另外還有幾個方法如下
...
procedure EndFixup;
procedure StartFixup;
procedure RecordUnresolvedName(const AName: string);
procedure PromoteFactoryClass(const AName: string);
...
他們是為了修正VCL中一些潛在的問題而定制的,因為和我們的主題相關不大,
而且解釋他們的實現原理需要涉及到很多VCL結構中比較低層的知識,幾句話說不清
因此幹脆跳過去算了。如果你有興趣進一步了解,可以來信和我討論 :)
在了解了兩個List(Factory List & Module List)之後,再來看
TWebRequestHandler的實現代碼就很簡單了 :)
TWebRequestHandler中最重要的還是一個Factory List - FWebModuleFactories,
兩個保存TWebModuleList的TList - FActiveWebModules, FInactiveWebModules
而真正的核心函數只有三個
...
function ActivateWebModules: TWebModuleList;
procedure DeactivateWebModules(WebModules: TWebModuleList);
function HandleRequest(Request: TWebRequest; Response: TWebResponse): Bo
olean;
...
讓我們來一步一步介紹(痛苦啊,終於快說寫了,手都敲累了,呵呵 :)
記得我剛剛說過,D5和D6都實現了Module Pooling,類似於以前介紹過的ISAPI實現
的
Thread Pooling的思路,FActiveWebModules和FInactiveWebModules分別保存活動的
和掛起的WebModuleList(D5是WebModule)實例。
...
function TWebRequestHandler.ActivateWebModules: TWebModuleList;
begin
if (FMaxConnections > 0) and (FAddingActiveModules >= FMaxConnections) the
n
raise Exception.CreateRes(@sTooManyActiveConnections);
FCriticalSection.Enter;
try
FAddingActiveModules := FActiveWebModules.Count + 1;
try
Result := nil;
if (FMaxConnections > 0) and (FActiveWebModules.Count >= FMaxConnectio
ns) then
raise Exception.CreateRes(@sTooManyActiveConnections);
if FInactiveWebModules.Count > 0 then
begin
Result := FInactiveWebModules[0];
Result.OnModuleAdded := nil;
FInactiveWebModules.Delete(0);
FActiveWebModules.Add(Result);
end
else
begin
if FWebModuleFactories.ItemCount = 0 then
if WebModuleClass <> nil then
FWebModuleFactories.AddFactory(TDefaultWebModuleFactory.Create(W
ebModuleClass));
if FWebModuleFactories.ItemCount > 0 then
begin
Result := TWebModuleList.Create(FWebModuleFactories);
FActiveWebModules.Add(Result);
end
else
raise Exception.CreateRes(@sNoDataModulesRegistered);
end;
finally
FAddingActiveModules := 0;
end;
finally
FCriticalSection.Leave;
end;
Result.AutoCreateModules;
end;
...
ActivateWebModules從池中激活一個WebModuleList並返回之。一開始檢測是否
超過最大連接數,FMaxConnections在Create中設置缺省值為32,也就是說最多可以在
池中緩沖32個客戶端WebModuleList。接下來以臨界區FCriticalSection包裹主要代碼
實現同步。然後又是檢測最大連接數(真是麻煩的說,不知為何不把檢測代碼合並到一
起?)
接著看看池中(FInactiveWebModules)是否有可以直接激活使用的WebModuleList實例,
如果有,則將第一個可用實例取出放入返回值,並加入到活動WebModuleList列表中;
如果池中沒有,則檢測Factory List是否為空,為空則檢測向後兼容的WebModuleClass
是否為空,不為空就將WebModuleClass的類型加入到Factory List中。
為了兼容D5代碼,你使用的第一個WebModule的初始化代碼如下
...
initialization
if WebRequestHandler <> nil then
WebRequestHandler.WebModuleClass := TWebDataModule1;
...
而如果再加入新的WebModule就會過渡到使用Factory的構建模型上
...
initialization
if WebRequestHandler <> nil then
WebRequestHandler.AddWebModuleFactory(
TWebDataModuleFactory.Create(TWebDataModule2, crOnDemand, caCache));
...
因此這裏對Factory List如此處理。如果還是沒有可用的Factory,則引發異常;
如果有,則建立一個新的TWebModuleList加入到活動列表中。最後調用AutoCreateModu
les
構造TWebModuleList裏需要構造的WebModule。這部分我們剛剛討論應該已經夠詳細了。
...
procedure TWebRequestHandler.DeactivateWebModules(WebModules: TWebModuleList
);
begin
FCriticalSection.Enter;
try
FActiveWebModules.Remove(WebModules);
WebModules.AutoDestroyModules;
if FCacheConnections and (WebModules.GetItemCount > 0) then
FInactiveWebModules.Add(WebModules)
else
begin
WebModules.Free;
end;
finally
FCriticalSection.Leave;
end;
end;
...
然後掛起TWebModuleList的代碼就相對簡單了,從活動列表FActiveWebModules
裏Remove此WebModuleList,然後AutoDestroyModules之,如果TWebRequestHandler
允許緩存(FCacheConnections = True),並且WebModuleList內有沒有析構的
WebModule(WebModules.GetItemCount > 0)也就是說不是所有的Factory設置
CacheMode為caDestroy,則將此WebModuleList加入到掛起列表
FInactiveWebModules中。
...
function TWebRequestHandler.HandleRequest(Request: TWebRequest;
Response: TWebResponse): Boolean;
var
I: Integer;
WebModules: TWebModuleList;
WebModule: TComponent;
WebAppServices: IWebAppServices;
GetWebAppServices: IGetWebAppServices;
begin
Result := False;
WebModules := ActivateWebModules;
if Assigned(WebModules) then
try
if WebModules.ItemCount = 0 then
raise Exception.CreateRes(@sNoWebModulesActivated);
try
// Look at modules for a web application
for I := 0 to WebModules.ItemCount - 1 do
begin
WebModule := WebModules[I];
if Supports(IInterface(WebModule), IGetWebAppServices, GetWebAppServ
ices) then
WebAppServices := GetWebAppServices.GetWebAppServices;
if WebAppServices <> nil then break;
end;
if WebAppServices = nil then
WebAppServices := TDefaultWebAppServices.Create(nil);
WebAppServices.InitContext(WebModules, Request, Response);
try
try
Result := WebAppServices.HandleRequest;
except
ApplicationHandleException(WebAppServices.ExceptionHandler);
end;
finally
WebAppServices.FinishContext;
end;
if Result and not Response.Sent then
Response.SendResponse;
except
ApplicationHandleException(nil);
end;
finally
DeactivateWebModules(WebModules);
end;
end;
...
最後就剩下一個HandleRequest了,我們前面幾節討論中,數據的處理最終都歸結到
這個方法上來,讓我們看看他是如何進行處理的。
首先是取得一個活動的WebModuleList,如果成功Assigned(WebModules)並且
有WebModule存在(WebModules.ItemCount <> 0)則遍歷此WebModuleList,
向每個WebModule查詢是否支持IGetWebAppServices接口,如果支持則使用
IGetWebAppServices.GetWebAppServices取得此WebModule的IWebAppServices
接口;如果都不支持則以缺省的TDefaultWebAppServices建立一個IWebAppServices。
然後以IWebAppServices初始化上下文(InitContext);處理請求(HandleRequest);
最後結束(FinishContext)。如果中途出現異常則調用ApplicationHandleException
處理,前面我們分析WebBroker時曾經談到,ApplicationHandleException實際上被
初始化為HandleException方法
...
procedure TWebRequestHandler.HandleException(Sender: TObject);
var
Handled: Boolean;
Intf: IWebExceptionHandler;
begin
Handled := False;
if (ExceptObject is Exception) and
not (ExceptObject is EAbort) and
Supports(Sender, IWebExceptionHandler, Intf) then
Intf.HandleException(Exception(ExceptObject), Handled);
if not Handled then
SysUtils.ShowException(ExceptObject, ExceptAddr);
end;
...
如果HandleRequest成功並且返回信息沒有被發送(not Response.Sent),
則發送返回信息Response.SendResponse。
剩下的一些方法,代碼大多很簡單,有這次分析WebReq和以前分析ISAPI的經驗,
看懂應該不是難事,我就不多羅嗦了,呵呵。
而IWebAppServices等等一系列接口的結構與實現,則是以後小節的主要內容
我這裏就先不多說了,呵呵,最後讓我們總結一下 :)
不同的Web Server類型的實現,在處理完其類型相關的操作後,將輸入和輸出
的抽象包裝類作為參數傳遞給TWebRequestHandler.HandleRequest方法,
此方法先檢查最大緩沖連接數是否超過,沒有,則試圖從非活動WebModuleList列表
中取出現成的可用實例,如果沒有則建立之。最終取得一個可用的實例。然後在此實例
的所有WebModule成員中查詢並取得IWebAppServices接口(如都不支持則以缺省
TDefaultWebAppServices實現之),在取得接口後,通過接口的相關函數建立、
接觸上下文環境,在此兩項操作之間處理用戶請求。
至此,Web Server程序的控制權已經從Web Application部分移交給了
Web Module部分,因為實際實現IWebAppServices接口的是某個WebModule,
我的系列文章也總算完成了他的第一部分。在接下來的章節中,我們將逐步接觸真正
實現Web Server邏輯的Web Module部分。
==============================================================
預告 :)
五、Web Module部分的請求調度
從上一小節開始,用戶請求已經從Web Application轉向到Web Module部分
在座這個小節,讓我們分析一下,用戶請求是如何在Web Module本分析、調度、處理
用戶編寫的WebModule是如何相應客戶請求的。
待續 :)
==============================================================
VCL架構下Web服務程序實現原理分析
Flier
目錄 v0.1
標題 發表日期 最新修正版本
前言 2001.7.22 0.1
一、整體結構及ISAPI實現 v0.1 2001.7.22 0.1
二、ISAPI實現中線程池原理與實現 2001.7.24 0.1
三、Web App的其他類型實現的分析 2001.7.30 0.1
四、WebBroker與WebReq的分析 2001.8.7 0.1
五、Web Module部分的請求調度 待定
==============================================================
Flier Lu@白雲黃鶴,2000.8.7
mailto:[email protected]
telnet://bbs.whnet.edu.cn
WebBroker 架構分析