本文章由杨芹勍原创,如需转载请注明作者及出处,否则保留追究法律责任的权利!
一、前言
在IE浏览器中,如果想捕获浏览器的HTTP请求,可以通过BHO或者“异步可插协议”技术实现,类似于“迅雷”之类的下载软件,当用户点击一个下载链接会自动弹出。在Symbian OS中有一种技术与“异步可插协议”非常类似,Symbian OS会在HTTP请求中每一步处理过程都通知用户代码,本文将介绍这种技术的实现,并在文章的最后提供在手机上实现下载软件的具体思路。
二、核心ECOM接口类CEComFilter
从类名可以看出,CEComFilter是一个ECOM接口类(关于ECOM方面的编程总结,我会在后面的章节陆续放出),以下列出CEComFilter类的关键代码(SDK中文件cecomfilter.h内):
1
const
TUid KUidFilterPluginInterface
=
{
0x101F446D
};
2
class
CEComFilter :
public
CBase
3
{
4
public
:
5
static
CEComFilter
*
InstallFilterL(RHTTPSession aSession,
const
TUid aEComFilterUid)
6
{
7
TAny
*
filterPlugin
=
REComSession::CreateImplementationL(aEComFilterUid, _FOFF(CEComFilter,iEcomDtorID),
&
aSession);
8
return
REINTERPRET_CAST(CEComFilter
*
, filterPlugin);
9
}
10
11
static
CEComFilter
*
InstallFilterL(RHTTPSession aSession,
const
TDesC8
&
aEComFilterDataType)
12
{
13
TEComResolverParams resolverParams;
14
resolverParams.SetDataType(aEComFilterDataType);
15
16
//
This will leave if the plugin is not found
17
TAny
*
filterPlugin
=
REComSession::CreateImplementationL(KUidFilterPluginInterface,
18
_FOFF(CEComFilter,iEcomDtorID),
19
&
aSession, resolverParams);
20
return
REINTERPRET_CAST(CEComFilter
*
, filterPlugin);
21
}
22
23
~
CEComFilter()
24
{
25
REComSession::DestroyedImplementation(iEcomDtorID);
26
}
27
28
private
:
29
TUid iEcomDtorID;
30
};
通过代码我们可以分析出以下结论:
- 第1行,CEComFilter这个ECOM接口类的接口UID(interface_uid)为0x101F446D;
- 第5行、第11行,该接口提供了能够获得“ECOM实现(指实现了ECOM接口的类)”的两个工厂方法:InstallFilterL的两个重载函数。第一个函数将“ECOM实现”的“实现UID(implementation_uid)”传给默认ECOM决议者,第二个函数将提示参数传给默认ECOM决议者。
注意:两个工厂方法都会将RHTTPSession http会话的指针通过ECOM决议者创建“ECOM实现”时传入“ECOM实现”的构造函数。 - 系统创建了HTTP会话后,会遍历所有实现了CEComFilter接口的ECOM实现UID(implementation_uid),使用工厂方法将ECOM实现实例化,同时传入HTTP会话(RHTTPSession);这样,我们的CEComFilter的实现就能够针对此HTTP会话进行相关监控。
在此,系统只知道interface_uid,如何通过interface_uid知道有哪些implementation_uid的实现呢?很简单,使用如下函数即可获得:
class
REComSession :
public
RSessionBase { ... IMPORT_C
static
void
ListImplementationsL(TUid aInterfaceUid, RImplInfoPtrArray
&
aImplInfoArray); ... }
具体流程如下(模拟实现):
![image image](https://i-blog.csdnimg.cn/blog_migrate/7c9c8263c123b5f45f7cbd1559c871fd.png)
三、如何实现CEComFilter接口
实现CEComFilter,其实就是实现一个普通的ECOM接口,步骤非常简单,在此不再说明具体步骤。重点在第5步:如何对传入的RHTTPSession进行监控。
SDK中关于RHTTPSession::FilterCollection()方法的解释:
Accessor for the filter collection. Note that the filter collection can't be edited after the first transaction has been created.
注意:如果已经在RHTTPSession上创建了RHTTPTransaction,则不能对Filter Collection进行修改。
此方法返回类型RHTTPFilterCollection,使用RHTTPFilterCollection::AddFilterL的重载函数向RHTTPSession的监视器队列中添加“监视器”,用于捕获HTTP请求信息:
IMPORT_C
void
AddFilterL(MHTTPFilter
&
aFilter, THTTPEvent aEvent, RStringF aHeader, TInt aStatusCode, TInt aPosition, RStringF aName);
参数说明:
- aFilter:类型为MHTTPFilter的引用,MHTTPFilter的定义为:
1
class
MHTTPFilter
2
{
3
public
:
4
IMPORT_C
virtual
void
MHFUnload(RHTTPSession aSession, THTTPFilterHandle aHandle);
5
IMPORT_C
virtual
void
MHFLoad(RHTTPSession aSession, THTTPFilterHandle aHandle);
6
IMPORT_C
virtual
void
MHFRunL(RHTTPTransaction aTransaction,
const
THTTPEvent
&
aEvent);
7
IMPORT_C
virtual
void
MHFSessionRunL(
const
THTTPSessionEvent
&
aEvent);
8
IMPORT_C
virtual
TInt MHFRunError(TInt aError, RHTTPTransaction aTransaction,
const
THTTPEvent
&
aEvent);
9
IMPORT_C
virtual
TInt MHFSessionRunError(TInt aError,
const
THTTPSessionEvent
&
aEvent);
10
};
看到这里大家肯定会想起MHTTPTransactionCallback类,此类用在RHTTPSession::OpenTransactionL中,为HTTP请求的回调,我们使用RHTTPSession::OpenTransactionL打开一个连接后,往往会在MHFRunL方法中获取HTTP返回的字节、检查StatusCode、检查HTTP头信息等。而MHTTPFilter也提供MHFRunL方法,我们可以再此方法中对系统任意一个HTTP请求进行捕获,做相应的检查和处理;
另外,MHTTPFilter类中的方法都不是纯虚函数,我们的类中可以有选择的进行继承; - aEvent:监视器所监视的HTTP请求事件类型,可以监视所有时间或者只针对Transaction Event、Session Event进行监视,具体请参看SDK中“THTTPEvent”类的文档;
- aHeaders:HTTP接收到指定的头将触发事件;
- aStatusCode:HTTP接收到指定的状态码将触发事件;
- aPosition:监视器所在“位置”,在RHTTPSession完成一个HTTP请求时,会分为几个步骤:处理协议、处理缓存、处理状态码、处理UA等,aPosition所代表的“位置”指的就是把监视器插入哪个步骤中进行监视,具体数值及详细解释请参看MHTTPFilter::TPositions枚举值的说明;
- aName:自定义的监视器的名字。
根据以上分析,我们可以通过实现CEComFilter接口完成一个Demo:只要系统提交了HTTP进行Url的请求,就弹出一个全局消息框,显示HTTP所请求的Url地址。
四、实现CEComFilter接口
完整源代码请点击此处下载
1、实现接口的类名叫CFelixHttpFilter,定义如下:
1
class
CFelixHttpFilter:
public
CEComFilter,
private
MHTTPFilter
2
{
3
public
:
4
~
CFelixHttpFilter();
5
static
CFelixHttpFilter
*
NewL(TAny
*
aHttpSession);
6
static
CFelixHttpFilter
*
NewLC(TAny
*
aHttpSession);
7
8
public
:
9
//
MHTTPFilter
10
void
MHFUnload(RHTTPSession aSession, THTTPFilterHandle aHandle);
11
void
MHFLoad(RHTTPSession aSession, THTTPFilterHandle aHandle);
12
void
MHFRunL(RHTTPTransaction aTransaction,
const
THTTPEvent
&
aEvent);
13
14
private
:
15
CFelixHttpFilter();
16
void
ConstructL(TAny
*
aHttpSession);
17
18
private
:
19
RStringF iFilterName;
20
};
21
2、关键类代码实现如下:
1
_LIT8(KFilterName,
"
FelixFilter
"
);
2
3
CFelixHttpFilter::
~
CFelixHttpFilter()
4
{
5
iFilterName.Close();
6
}
7
8
CFelixHttpFilter
*
CFelixHttpFilter::NewLC(TAny
*
aHttpSession)
9
{
10
CFelixHttpFilter
*
self
=
new
(ELeave) CFelixHttpFilter();
11
CleanupStack::PushL(self);
12
self
->
ConstructL(aHttpSession);
13
return
self;
14
}
15
16
CFelixHttpFilter
*
CFelixHttpFilter::NewL(TAny
*
aHttpSession)
17
{
18
CFelixHttpFilter
*
self
=
CFelixHttpFilter::NewLC(aHttpSession);
19
CleanupStack::Pop();
//
self;
20
return
self;
21
}
22
23
void
CFelixHttpFilter::ConstructL(TAny
*
aHttpSession)
24
{
25
RHTTPSession
&
httpSession
=
*
(reinterpret_cast
<
RHTTPSession
*>
(aHttpSession));
26
27
iFilterName
=
httpSession.StringPool().OpenFStringL(KFilterName);
28
CleanupClosePushL(iFilterName);
29
30
httpSession.FilterCollection().AddFilterL(
*
this
, THTTPEvent::EAnyTransactionEvent, RStringF(), KAnyStatusCode, EClientFilters, iFilterName);
31
CleanupStack::Pop(
&
iFilterName);
32
}
33
34
void
CFelixHttpFilter::MHFUnload(RHTTPSession aSession, THTTPFilterHandle aHandle)
35
{
36
delete
this
;
37
}
38
39
void
CFelixHttpFilter::MHFRunL(RHTTPTransaction aTransaction,
const
THTTPEvent
&
aEvent)
40
{
41
if
(aEvent
==
THTTPEvent::ESubmit)
42
{
43
//
在此处拦截到请求的Url
44
const
TDesC8
&
url
=
aTransaction.Request().URI().UriDes();
45
TBuf
<
KMaxFileName
>
url16;
46
url16.Copy(url);
47
48
//
效果:将url打印到屏幕上
49
CEikonEnv::Static()
->
InfoWinL(url16, KNullDesC16);
50
}
51
}
52
53
const
TImplementationProxy kImplementationTable[]
=
54
{
55
IMPLEMENTATION_PROXY_ENTRY(
0xA00133E9
, CFelixHttpFilter::NewL)
56
};
57
58
EXPORT_C
const
TImplementationProxy
*
ImplementationGroupProxy(TInt
&
aTableCount)
59
{
60
aTableCount
=
sizeof
(kImplementationTable)
/
sizeof
(TImplementationProxy);
61
return
kImplementationTable;
62
}
63
注意,代码第55行中的0xA00133E9是我们ECOM实现的implementation_uid。
3、我们的程序UID3为0xA00133E8,ECOM资源文件名:A00133E8.rss,内容如下:
1
RESOURCE REGISTRY_INFO filterinfo
2
{
3
dll_uid
=
_UID3;
4
interfaces
=
5
{
6
INTERFACE_INFO
7
{
8
interface_uid
=
0x101F446D
;
9
implementations
=
10
{
11
IMPLEMENTATION_INFO
12
{
13
implementation_uid
=
0xA00133E9
;
14
version_no
=
1
;
15
display_name
=
"
Felix Filter
"
;
16
default_data
=
"
HTTP/+FELIXFILTER
"
;
17
opaque_data
=
""
;
18
}
19
};
20
}
21
};
22
}
23
以上资源文件第16行,default_data的作用:
HTTP监视器能够“主动的”加载或者“被动的”加载,这取决于你资源文件中IMPLEMENTATION_INFO小节中的“default_data”字段(以上代码第16行)所设定的值,有如下三个加载选项被支持:
- HTTP/+FELIXFILTER:监视器总是能自动加载
- HTTP/-FELIXFILTER:监视器不会加载,用户只能手动的加载它
- HTTP/FELIXFILTER:除非用户从监视器集合中移除,监视器都会加载
五、如何实现手机下载软件
基本概念都已经清楚,现在我们来想想如何实现手机上的迅雷。根据以下流程可以容易的做到:
- 按照以上步骤,实现CEComFilter接口,生成基于多态dll的plugin;
- 重写MHTTPFilter::MHFRunL方法;
- 因为要做下载软件不可能在MHFRunL方法中简单地通过判断Url来判断文件类型,必须通过判断服务器返回的MIME类型是否为所支持下载文件类型的MIME,因此得判断aEvent是否为“服务器端Headers返回”的状态,来获取返回头的“Content-Type”字段;
- 判断MHFRunL方法中的aEvent是否为EGotResponseHeaders;
- 通过aTransaction.Response().GetHeaderCollection()获取字段“Content-Type”的值,通过“自定义字符串池(将在以后章节介绍)”的技术加快MIME的匹配对比;
- 如果服务器返回的MIME类型满足下载工具支持的类型,则弹出全局询问框,询问用户是否通过下载工具下载;
- 用户选择“是”,则将aTransaction.Cancel(),获取请求的Url,启动下载程序并将Url发送过去;
- 用户选择“否”,什么事都不做。
六、参考文献
- how to get your filter loaded automatically
- Writing an HTTP filter plugin
本文章由杨芹勍原创,如需转载请注明作者及出处,否则保留追究法律责任的权利!