本节中,我们将通过介绍如何完成枚举资源功能来介绍MapGuide Web扩展的部分源代码。
在浏览器端的地址栏输入类似如下字符串,就会发送一个枚举资源的HTTP请求。
http://hostname/mapGuide/mapagent.fcig?OPERATION=EnumerateResources&VERSION=1.0.0& RESOURCEID=Library:// &TYPE=FeatureSource&DEPTH=3 |
下面我们来看看MapGuide Web扩展如何处理这个请求。
在介绍MapGuide Web扩展如何处理枚举资源HTTP请求之前,让我们首先来看看MapGuide Web扩展用于处理HTTP请求和响应的类,这些类的类图如图19‑4所示。
图 19‑5 HTTP请求和响应类的类图
类MgHttpRequest和类MgHttpResponse分别是对HTTP请求和响应结果的抽象,这两个类可以用于任何类型的Web应用服务器,也就是说它们的代码不依赖于任何Web应用服务器API。在文件夹“/MgDev/Web/src/HttpHandler”下可以找到所有前缀为“MgHttp”类的源代码。
类MgHttpRequest用于处理HTTP请求,它包含了一个HTTP请求头MgHttpHeader的实例、一个HTTP请求参数HttpRequestParam的实例和一个HTTP请求元数据HttpRequestMetadata的实例。调用方法MgHttpRequest::Execute()会将HTTP请求转发给MapGuide服务器,当MapGuide服务器处理完这个请求,将请求结果返回给Web扩展之后,这个方法会返回一个MgHttpResponse对象,它表示HTTP请求的响应结果。
接下来让我们看看MapGuide Web扩展如何处理枚举资源的HTTP请求。大多数MapGuide用户都是使用IIS作为Web应用服务器,并且使用Web扩展模块isapi_MapAgent.dll来处理HTTP请求,所以本节侧重于isapi_MapAgent.dll模块处理HTTP请求的流程,这个处理流程的时序图如图19‑5所示。
1) 加载Web应用服务器扩展模块
如果使用的是IIS Web应用服务,那么在接收到后缀为“fcgi”的HTTP请求后,IIS会加载Web应用服务器扩展模块isapi_MapAgent.dll或MapAgent.exe。其中,isapi_MapAgent.dll基于IIS API,具有更好的性能;MapAgent.exe基于CGI技术,性能要稍微差一些。如果使用的是Apache Web应用服务器,那么在Windows平台上Apache会加载Web应用服务器扩展模块MapAgent.exe,在Linux平台上会加载模块mod_mgmapagent.so。
这三个Web服务器扩展模块实现了类似的功能,它们的源代码位置如表19‑2所示。
服务器扩展模块 | 源代码位置 |
isapi_MapAgent.dll | /Web/src/IsapiAgent |
MapAgent.exe | /Web/src/CgiAgent |
mod_mgmapagent.so | /Web/src/ApacheAgent |
从图19‑6中可以看到这三个Web服务器扩展模块提供了类似的类,这些类提供了相同的接口,类XXXPostParser用于解析HTTP请求中传入的参数及参数值,类XXXResponseHandler用于将HTTP请求的响应结果发送给客户端。其中,“XXX”表示“Cgi”、“Isapi”或“Apache”。
对于模块isapi_MapAgent.dll,每个“fcgi”类型的HTTP请求会调用此模块中的方法HttpExtensionProc,这个方法的代码如下所示,为了便于理解我们只保留了一些核心代码。
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) { Initialize(pECB); // 调用GetServerVariable读取诸如服务器名称、端口号等服务器变量信息 ...... // 创建MgHttpRequest的实例,用于处理HTTP请求 Ptr request = new MgHttpRequest(wUrl); // 解析HTTP请求中的参数,并且将这些参数置入MgHttpRequest对象中 Ptr params = request->GetRequestParam(); ...... IsapiPostParser postParser(pECB); postParser.Parse(params); ...... // 创建IsapiResponseHandler的实例,用于将响应请求返回给客户端 IsapiResponseHandler responseHandler(pECB); ...... // 将HTTP请求转发给MapGuide服务器,当MapGuide服务器处理完这个请求, // 将请求结果返回给Web扩展之后,这个方法会返回一个MgHttpResponse对象, // 它表示HTTP请求的响应结果。 Ptr response = request->Execute(); // 将响应结果返回给客户端 responseHandler.SendResponse(response); ...... } |
2) 创建MgHttpRequest的实例,用于处理HTTP请求
在加载Web应用服务器扩展模块后,需要创建一个MgHttpRequest的实例用于处理HTTP请求。类MgHttpRequest并不关联与任何类型的Web应用服务器,所以Web应用服务器扩展模块会使用类XXXPostParser解析HTTP请求中传入的参数及参数值,将这些参数设置到MgHttpRequest对象中。
3) 调用方法MgHttpRequest::Execute()处理HTTP请求
方法MgHttpRequest::Execute()的源代码如下所示。为了便于理解,我们同样只保留了一些核心代码。
MgHttpResponse* MgHttpRequest::Execute() { Ptr hResponse; Ptr result; // 创建MgHttpResponse对象,用于返回HTTP请求响应结果 hResponse = new MgHttpResponse(); result = hResponse->GetResult(); // 获得HTTP请求中参数“OPERATION”的值 STRING sParamValue = m_requestParam->GetParameterValue(MgHttpResourceStrings::reqOperation); ...... // 根据参数“OPERATION”的值取得对应操作的请求响应处理器 Ptr rrHandler = CreateRequestResponseHandler(sParamValue, result); ....... // 处理HTTP请求 if(rrHandler != NULL) rrHandler->Execute(*hResponse); ...... // 返回响应结果 return SAFE_ADDREF((MgHttpResponse*)hResponse); } |
每个HTTP请求中包含一个参数“OPERATION”,它用于代表操作的类型。对于不同的操作,MapGuide定义了不同的请求响应处理器类,这个类会调用MapGuide服务中对应的方法处理这个操作,这些请求响应处理器类的类图如图19‑7所示。从图19‑7可以看到,所有请求响应处理器类都继承自类MgHttpRequestResponseHandler,每一种操作都有一个对应的子类,例如枚举资源操作使用了类MgHttpEnumerateResources,描述模式操作使用了类MgHttpDescribeSchema。
MapGuide定义了一个全局map对象用于存放每种操作对应的请求响应处理器类对象,它的键是操作的名称,值是请求响应处理器类对象。
// 定义一个全局map对象,用于存放每种操作对应的请求响应处理器类对象 static map httpClassCreators; bool InitializeStaticData() { httpClassCreators[MgHttpResourceStrings::opGetMap] = MgHttpGetMap::CreateObject; httpClassCreators[MgHttpResourceStrings::opGetMapUpdate] = MgHttpGetMapUpdate::CreateObject; httpClassCreators[MgHttpResourceStrings::opGetDrawing] = MgHttpGetDrawing::CreateObject; ...... httpClassCreators[MgHttpResourceStrings::opDescribeDrawing] = MgHttpDescribeDrawing::CreateObject; httpClassCreators[MgHttpResourceStrings::opEnumerateResources] = MgHttpEnumerateResources::CreateObject; ...... } |
调用方法MgHttpRequest::CreateRequestResponseHandler(...)可以根据操作的名称获得对应的请求响应处理器类对象。对于枚举资源操作,它会返回一个类MgHttpEnumerateResources的对象。
调用这些请求响应处理器类的Execute(…)方法,会创建一个代理服务类对象,然后调用代理服务中对应的方法。为什么说创建的是一个代理服务类对象呢?我想看完图19‑8之后大家会有所明白。从图19‑8中可以看到,所有的MapGuide服务有两个子类,一个是名称为MgProxyXXXService的代理服务类,它是MapGuide Web扩展端的一个类,另一个是名称为MgServerXXXService的服务器服务类,它是MapGuide服务器端的一个类。MgProxyXXXService最终会将服务请求转发给服务器端类MgServerXXXService,所以说真正处理服务请求的类是MgServerXXXService,MgProxyXXXService仅仅起到一个转发的作用,这也就是为什么称类MgProxyXXXService为代理服务类的原因。
对于枚举资源操作,MgHttpEnumerateResources::Execute(...)的源代码如下所示。调用这个方法会创建一个代理资源服务类MgProxyResourceService的对象,然后调用MgProxyResourceService::EnumerateResources(...)枚举资源,最后将操作的执行结果放入MgHttpResponse对象中。
void MgHttpEnumerateResources::Execute(MgHttpResponse& hResponse) { Ptr hResult; hResult = hResponse.GetResult(); // 检查HTTP请求中的参数 ValidateCommonParameters(); // 创建MgProxyResourceService实例 Ptr mgprService = (MgResourceService*)(CreateService(MgServiceType::ResourceService)); ...... // 执行枚举资源操作 Ptr byteReader = mgprService-> EnumerateResources(&mgrIdentifier, m_depth, m_type, m_computeChildren); // 如果需要,转换响应请求的格式 ProcessFormatConversion(byteReader); // 将操作结果放入响应请求中的MgHttpResult对象 hResult->SetResultObject(byteReader, byteReader->GetMimeType()); } MgByteReader* MgProxyResourceService::EnumerateResources( MgResourceIdentifier* resource, INT32 depth, CREFSTRING type, INT32 properties, CREFSTRING fromDate, CREFSTRING toDate, bool computeChildren) { MgCommand cmd; cmd.ExecuteCommand(m_connProp, MgCommand::knObject, MgResourceService::opIdEnumerateResources, 7, Resource_Service, BUILD_VERSION(1,0,0), MgCommand::knObject, resource, MgCommand::knInt32, depth, MgCommand::knString, &type, MgCommand::knInt32, properties, MgCommand::knString, &fromDate, MgCommand::knString, &toDate, MgCommand::knInt8, (int)computeChildren, MgCommand::knNone); SetWarning(cmd.GetWarningObject()); return (MgByteReader*)cmd.GetReturnValue().val.m_obj; } |
4) 将HTTP请求的响应结果返回给客户端
调用MgHttpRequest::Execute()会返回一个MgHttpResponse对象,这个对象包含了HTTP请求的响应结果。不过,还需要调用方法XXXResponseHandler::SendResponse(...),这样Web应用服务器才会将这个响应结果返回给客户端。