在Thread pooling for web connections中我曾经提到Shared Source CLI,这次再借用另一个场合宣传一下这个"Open Source .NET Framework"吧。
Topic还是web connection pooling:可以很自然地想到我们可以设计这么一个class:
class WebConnector
{
WebRequest CreateRequest(string targetUrl)
{
return WebRequest.Create(targetUrl);
}
...
}
这个class现在什么都没有做,因为我们的希望是.NET提供的WebRequest可以提供一定的pooling mechanism,如果真是那样,那生活真是很美好,我们什么都不要做就可以下班了。而其实这也不是什么太wild的dream,毕竟是.NET,连个pooling都不提供,也不要混了。
在深入研究一下WebRequest class之前,我们还需要考虑一个问题:web application很常见的会用到其他的web service,也就是用SOAP像其他web service请求服务。在Visual Studio中如何生成web service的proxy class等等我就不用重复重复了吧(wsdl.exe等等的)?关键在于:这个由wsdl自动生成proxy class用的是什么connection mechanism呢?虽然基本可以想到应该也是somewhere, somehow用的WebRequest,但还是让我们double check一下。找任意一份wsdl生成的proxy class的代码看看:
public partial class WSProxcyClass : System.Web.Services.Protocols.SoapHttpClientProtocol
嗯,看来基本功能都在SoapHttpClientProtocol这个类里面。
再研究一下MSDN里SoapHttpClientProtocol的API,aha,有这么一个member function:
protected override WebRequest GetWebRequest(Uri uri)
看来所有SoapHttpClientProtocol里面用到的WebRequest都是通过这个函数来取得的。注意一下前缀的override关键字,很好很好,这说明如果我们对WebRequest现有的pooling mechanism不满意的话(或者WebRequest根本没有提供pooling机制),我们可以从SoapHttpClientProtocol派生一个类,然后改写掉GetWebRequest来实现我们满意的pooling机制。其他的接口都还是用SoapHttpClientProtocol,而WSProxyClass只要继承新的派生类就可以了。
嗯,很好,至少我们有了backup plan,虽然麻烦了一点点,但也算不错,感谢SoapHttpClientProtocol良好的界面。
现在该进一步了,我们来研究一下WebRequest到底有没有提供pooling吧。
(题外话:可以看到.NET源代码的朋友可以研究一下SoapHttpClientProtocol的实现,这里是一个class hierarchy:
WebClientProtocol
-> HttpWebClientProtocol
-> SoapHttpClientProtocol :
你会发现,GetWebRequest其实一直延伸到最基类的WebClientProtocol里,有不少messy stuff to deal with,所以真的要改写GetWebRequest并非那么容易。不过,anyway, that's a side note to this article, we are heading for better stuff, baby.
)
这下该用到,搜一下webrequest.cs,嗯嗯,time to read the source code:
private static WebRequest Create(Uri requestUri, bool useUriBase) {
string LookupUri;
WebRequestPrefixElement Current = null;
bool Found = false;
if (!useUriBase) {
LookupUri = requestUri.AbsoluteUri;
}
else {
// schemes are registered as <schemeName>":", so add the separator
// to the string returned from the Uri object
LookupUri = requestUri.Scheme + ':';
}
int LookupLength = LookupUri.Length;
// Copy the prefix list so that if it is updated it will
// not affect us on this thread.
ArrayList prefixList = PrefixList;
// Look for the longest matching prefix.
// Walk down the list of prefixes. The prefixes are kept longest
// first. When we find a prefix that is shorter or the same size
// as this Uri, we'll do a compare to see if they match. If they
// do we'll break out of the loop and call the creator.
for (int i = 0; i < prefixList.Count; i++) {
Current = (WebRequestPrefixElement)prefixList[i];
// See if this prefix is short enough.
if (LookupLength >= Current.Prefix.Length) {
// It is. See if these match.
if (String.Compare(Current.Prefix, 0, LookupUri, 0, Current.Prefix.Length, StringComparison.OrdinalIgnoreCase ) == 0) {
// These match. Remember that we found it and break
// out.
Found = true;
break;
}
}
}
WebRequest webRequest = null;
if (Found) {
// We found a match, so just call the creator and return what it
// does.
webRequest = Current.Creator.Create(requestUri);
return webRequest;
}
// Otherwise no match, throw an exception.
throw new NotSupportedException(SR.GetString(SR.net_unknown_prefix));
}
WebRequest.Create就是最后的生成一个个实际的request object的地方。如果有pooling mechanism,在这里也该看出些端倪来了。
嗯,这段代码实在太有意思了。看懂了么?WebRequest有一个url prefixlist这样的列表,对于每个注册过的url prefix,都有一个对应的Creator来create相应的webrequest。可以想象的,一定还有一个" * "这样的prefix来匹配所有没有注册的prefix。
而匹配prefix的过程更是有趣,如果你仔细研究并理解我用红色highlight的comment的话,一定可以看出为什么了。不过这个不是我的focus,我就不分析了。
重要的是,我们知道有Creator这么一个东西,而且用户可以注册不同的url用不同的Creator,比如对于连接到www.microsoft.com的webrequest有一种要求,对于连接到www.amazon.com的webrequest有另一种要求,这些要求都可以customize。
^_^ 很好很好,我们已经很接近了。现在只有最后两个问题了,怎么customize webrequest? 以及,可以customize到什么程度?
嗯,第二个问题很好,我猜,答案是。没准,嗯,没准可以customize的程度很高?高到可以让我们指定pooling的功能!!
现在所有的线索都串起来了,我们知道有那么一个Creator,特别是那个匹配" * " (即所有)url的Creator,它应该可以是customize的,而且很可能允许我们指定pooling的一些参数。
那好,现在只有最后一个问题了:怎么customize?
难不成要我们自己派生几个Creator的子类,然后改写一下Create函数??
太复杂了吧。藏的这么深,谁知道这一把怎么玩的。。。
嗯,找找线索。
而线索也只有一条了:Creator的type!
Current = (WebRequestPrefixElement)prefixList[i];
webRequest = Current.Creator.Create(requestUri);
Bingo! WebRequestPrefixElement!
Hmm, element, element, element... 想到了什么?web.config里的configuration element么!
web.config -- we are almost done, baby.
让我们查一下msdn里system.net configuration element的文档(因为webrequest类在system.net里),有4个child element:
<authenticationModules> Specifies the modules used to authenticate Internet requests.
<connectionManagement> Specifies the maximum number of connections to Internet hosts.
<defaultProxy> Specifies the proxy server used for HTTP requests to the Internet.
<webRequestModules> Specifies the modules used to request information from Internet hosts.
找到了,从connectionManagement里你可以看到如何设置maxConnection #,然后最低下有指向ServicePoint和ServicePointManager的连接,那里,有如何设置pooling的介绍。
Well, quite an adventure, isn't it? I mean, it really doesn't have to take that much if you know system.net.connectionManagement element from the every beginning. I just found this process of following the cues, digging around, pondering about things, etc is quite some fun and definitely worth the time, : )
At least, for me, it is.
Hope you enjoyed it.
========================================================
升级后没有了code highlighter真不方便啊,code snippet在我的主页上看好好的,在博客堂的主页上看就很乱。