CSDN助手源码剖析(二)--URL Moniker的封装

一、目标

  在这篇文章中,我们要通过对URL Moniker的封装,实现以下几个功能:

  1. 支持URL的“GET”和“POST”两种操作。
  2. 支持同步和异步调用。

二、约定

  我们建立一个类CCuteMoniker,通过向外部提供一个Request方法,从而实现以上两个功能。

HRESULT CCuteMoniker::Request(LPCTSTR szMethod,LPCTSTR szURL,VARIANT_BOOL bAsync,
               LPCTSTR szHeaders,LPCTSTR szPostData,
               CuteHTTPResponseProc pProc,void* pParam1,CParam_Http_Base* pParam2,IStream** ppResponse,
               DWORD nBindFlags)

参数说明:

szMethod:“GET”或“POST”
szURL:要访问的URL
bAsync:同步还是异步
szHeaders:Request的头部信息
szPostData:Request的POST提交数据
pProc:异步调用时的回调函数,当数据成功返回时,调用此函数
pParam1:与本次访问相关的参数1
pParam2:与本次访问相关的参数2
ppResponse:当同步调用时,返回数据流
nBindFlags:访问时指定的绑定标志,如可以指定BINDF_NOWRITECACHE,即“禁止将返回的数据写入缓存”。这对于“CSDN助手”的缓存优化很重要。相关信息请参见文章
CSDN助手源码剖析(一)--缓存优化

特别说明

参数pParam2类型为CParam_Http_Base* 。CParam_Http_Base是参数基类。外部在访问URL资源时,经常要分配一些资源。这时,可以从CParam_Http_Base派生一个类,将所有分配的资源放入其中。这样,CCuteMoniker就会在合适的时候自动释放这些资源。

三、外部调用举例

  下面举一个回复帖子的例子,主要有两个方法。
CCSDNTools::topicReply:用于启动向服务器提交数据;
CCSDNTools::func_topicReplyResponseProc:
当回复成功时,调用此回调函数。

1、CCSDNTools::topicReply

//回复帖子
//bstrTopicID:帖子ID
//bstrContent:回复正文
//pDispCallback:采用异步操作,当回复成功时,调用这个回调接口
STDMETHODIMP CCSDNTools::topicReply(BSTR bstrTopicID, BSTR bstrContent, IDispatch* pDispCallback)
{
 // TODO: 在此添加实现代码
 USES_CONVERSION;

 //构造异步参数,CParam_Execute_TopicView派生自CParam_Http_Base
 CParam_Execute_TopicView* pParamExecute=new CParam_Execute_TopicView();
 pParamExecute->m_bstrUrl=::SysAllocString(bstrTopicID);

 pParamExecute->m_pDispCallback=pDispCallback;
 if(pDispCallback!=NULL)
  pDispCallback->AddRef();//增加引用

 //构造URL
 LPCTSTR szTopicID=W2A(bstrTopicID);
 CString sUrl;
 sUrl.Format("
http://community.csdn.net/Expert/reply.asp?Topicid=%s",szTopicID);
 
 //构造Headers
 CString sHeaders;
 sHeaders.Format("Content-Type:application/x-www-form-urlencoded/r/nReferer:http://community.csdn.net/Expert/xsl/Reply_Xml.asp?Topicid=%s/r/n",szTopicID);

 //对回复的文本进行编码
 CString sContent;
 EscapeToCString(sContent,W2A(bstrContent));

 //构造Post Data 
 CString sPostData;
 sPostData.Format("Topicid=%s&xmlReply=aaaaa&csdnname=&csdnpassword=&ReplyContent=%s",
  szTopicID,sContent);

 //新建一个CCuteMoniker对象
 CComPtr<IUnknown> pUnkThis;
 this->QueryInterface(IID_IUnknown,(void**)&pUnkThis);
 CCuteMoniker* pHttp=new CCuteMoniker(pUnkThis);

 //调用Request方法
 HRESULT hr=pHttp->Request("POST",sUrl,VARIANT_TRUE,sHeaders,sPostData,
  func_topicReplyResponseProc,NULL,pParamExecute,NULL,BINDF_GETNEWESTVERSION);
 if(FAILED(hr))
 {
  return hr;
 } 

 return S_OK;
}

2、CCSDNTools::func_topicReplyResponseProc

//回复返回
//pParam1:参数1
//pParam2:参数2
//pHttpBase:这是CCuteMoniker的基类。
//pStream:这是返回的数据,用于指定回复是否成功,及后续的操作指令
void CCSDNTools::func_topicReplyResponseProc(void* pParam1,CParam_Http_Base* pParam2,CCuteHttpBase* pHttpBase,IStream* pStream)
{
 USES_CONVERSION;

 //强制转换参数2
 CParam_Execute_TopicView* pParamExecute=(CParam_Execute_TopicView*)pParam2;

 //构造URL,准备缓存结果
 LPCTSTR szTopicID=W2A(pParamExecute->m_bstrUrl);
 CString sUrl;
 sUrl.Format("http://community.csdn.net/Expert/reply.asp?Topicid=%s",szTopicID
);

 //将数据缓存至Internet临时目录,为的是让浏览器转向这个页面,从而自动执行后续的操作指令。
 char szFileName[MAX_PATH];
 CCuteToolsB::SavetoCache(pStream,sUrl,szFileName,1,"htm",NULL);
 
 //回调,将缓存得到的临时文件名回调给外部调用者,以便浏览器转向这个页面。
 CComVariant vParam1=szTopicID;
 CComVariant vParam2=szFileName;
 CCuteTools::AutoWrap(
  DISPATCH_METHOD,NULL,pParamExecute->m_pDispCallback,NULL,2,vParam2,vParam1);

}

四、创建URL Moniker,启动访问过程

  接下来,我们看看在CCuteMoniker::Request方法中如何创建URL Moniker对象,并启动访问过程。

CComPtr<IMoniker> m_spMoniker;  //URL Moniker对象
CComPtr<IBindCtx> m_spBindCtx;  //绑定环境,通过向绑定环境注册一个回调接口,我们可以控制URL传输的过程,并得到反馈信息。
CComPtr<IStream> spStream;    //如果是同步调用,可在绑定返回时,直接得到数据流

  //创建一个URL Moniker对象
  hr = CreateURLMoniker(NULL, A2W(szURL), &m_spMoniker);
  
  //创建一个绑定环境
  hr = CreateBindCtx(0, &m_spBindCtx);
  
  //向绑定环境注册一个回调接口IBindStatusCallback,CCuteMoniker派生自接口IBindStatusCallback。
  hr = RegisterBindStatusCallback(m_spBindCtx, static_cast<IBindStatusCallback*>(this), 0, 0L);

  //执行绑定,启动实际的URL访问及数据传输过程。
  hr = m_spMoniker->BindToStorage(m_spBindCtx, 0, __uuidof(IStream), (void**)&spStream);

  //如果是同步操作,则直接返回数据流
  if(!bAsync) 
  {
 
   ATLASSERT(ppResponse!=NULL);

   //复制数据流,并返回。
   return CCuteTools::CopyStream(spStream,ppResponse);
  }
 

五、绑定状态回调接口IBindStatusCallback 

  CCuteMoniker派生自接口IBindStatusCallback,在实际的访问及数据传输过程中,Moniker对象会通过接口IBindStatusCallback取得相关的绑定信息,如绑定标志、访问方法、Post Data,也可以通过它反馈当前的进度,汇报返回的数据。

1、IBindStatusCallback::OnStartBinding方法。
将传入的参数IBinding *pBinding保存下来。通过IBinding ,我们可以暂停、重启、中止绑定过程。

STDMETHOD(OnStartBinding)(DWORD /*dwReserved*/, IBinding *pBinding)
 {
  ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnStartBinding/n"));
  m_spBinding = pBinding;
  return S_OK;
 }

2、IBindStatusCallback::GetBindInfo方法。
通过这个方法,我们可以指定绑定标志、访问方法、Post Data。

STDMETHOD(GetBindInfo)(DWORD *pgrfBINDF, BINDINFO *pbindInfo)
 {
  ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::GetBindInfo/n"));

  if (pbindInfo==NULL || pbindInfo->cbSize==0 || pgrfBINDF==NULL)
   return E_INVALIDARG;

  //绑定标志,
  //默认为 (BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE |BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE)
 *pgrfBINDF = m_dwBindFlags;

  //初始化结构体  
  ULONG cbSize = pbindInfo->cbSize;  // remember incoming cbSize
  memset(pbindInfo, 0, cbSize);   // zero out structure
  pbindInfo->cbSize = cbSize;    // restore cbSize

  //指定访问方法
  if(this->m_sMethod=="POST")
   pbindInfo->dwBindVerb = BINDVERB_POST;
  else
   pbindInfo->dwBindVerb = BINDVERB_GET;

  //指定post data
  pbindInfo->cbstgmedData=this->m_dwPostSize;
  pbindInfo->stgmedData.tymed=TYMED_HGLOBAL;
  pbindInfo->stgmedData.hGlobal=this->m_hGlobalPost;

  return S_OK;
 }

3、IBindStatusCallback::OnDataAvailable方法
当有数据返回(可能分多次返回)时,调用此方法。我们可以得到返回的数据流对象及数据大小

STDMETHOD(OnDataAvailable)(DWORD grfBSCF, DWORD dwSize, FORMATETC * /*pformatetc*/, STGMEDIUM *pstgmed)
 {
  ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnDataAvailable/n"));
  HRESULT hr = S_OK;

  //当第一次返回数据时,设置标志BSCF_FIRSTDATANOTIFICATION
  //这时,我们取得数据流对象
  if (BSCF_FIRSTDATANOTIFICATION & grfBSCF)
  {
   if (!m_spStream && pstgmed->tymed == TYMED_ISTREAM)
    m_spStream = pstgmed->pstm;
  }
  
  //当最后一次返回数据时,设置标志BSCF_LASTDATANOTIFICATION 
  //这时,我们取得数据的完整大小
  if (BSCF_LASTDATANOTIFICATION & grfBSCF)
  {
   this->m_dwTotalRead=dwSize;
  }
  return hr;
 }

4、IBindStatusCallback::OnStopBinding方法
在绑定结束的时候,最后执行这个方法。这时,如果是异步调用,我们就可以把得到的数据通过回调函数返回给外部。

STDMETHOD(OnStopBinding)(HRESULT hresult, LPCWSTR /*szError*/)
 {
  ATLTRACE(atlTraceControls,2,_T("CBindStatusCallback::OnStopBinding/n"));
  
  //清理对象
  if(m_spBinding!=NULL)
   m_spBinding.Release();
  if(m_spBindCtx!=NULL)
   m_spBindCtx.Release();
  if(m_spMoniker!=NULL)
   m_spMoniker.Release();

  //如果是异步调用,则执行回调
  if(this->m_bAsync)
  {
   ATLASSERT(m_pProc!=NULL);

   //回调
   try
   {
    //复制数据流
    CComPtr<IStream> pStream;
    CCuteTools::CopyStream(m_spStream,&pStream);

    //调用回调函数
    this->m_pProc(this->m_pParam1,this->m_pParam2,this,pStream);
   }
   catch(_com_error& e)
   {}
  }

  //释放数据流
  if(m_spStream!=NULL)
   m_spStream.Release();

  //如果是异步,释放自身
  if(this->m_bAsync)
  {
   this->Release();
  }
  return S_OK;
 }

5、IBindStatusCallback还有其他几个方法,由于在"CSDN助手"中没有用到,所以简单的返回S_OK。

六、接口IHttpNegotiate,处理Headers信息

  至此,一个基本的URL访问框架已经成形了。但还没有解决在Request时发送Headers,当Response时得到Headers的问题。

  接口IHttpNegotiate可以帮助我们解决这个问题。在MSDN中,有这样一句话来描述IHttpNegotiate:“Urlmon.dll uses the QueryInterface method on your implementation of IBindStatusCallback to obtain a pointer to your IHttpNegotiate interface.”。由于类CCuteMoniker派生自接口IBindStatusCallback,显然我们还要让类CCuteMoniker派生自接口IHttpNegotiate。这样,Moniker对象才能通过已注册的接口IBindStatusCallback得到接口IHttpNegotiate。

1、IHttpNegotiate::BeginningTransaction方法,提供Request时的Headers信息

virtual HRESULT STDMETHODCALLTYPE BeginningTransaction(
   /* [in] */ LPCWSTR szURL,
   /* [unique][in] */ LPCWSTR szHeaders,
   /* [in] */ DWORD dwReserved,
   /* [out] */ LPWSTR *pszAdditionalHeaders)
 {
  USES_CONVERSION;

  if(this->m_sHeaders!="")
  {
   LPCWSTR swzHeaders=A2W(this->m_sHeaders);
   int nSize=(wcslen(swzHeaders)+1)*2;

   //必须用CoTaskMemAlloc分配内存,因为Monker对象用CoTaskMemFree进行释放。
   LPWSTR pszHeaders=(LPWSTR)CoTaskMemAlloc(nSize);
   memcpy(pszHeaders,swzHeaders,nSize);
   *pszAdditionalHeaders=pszHeaders;
  }
  return S_OK;
 }

2、IHttpNegotiate::OnResponse方法,在这里我们可以得到Response的Headers信息及响应码。

virtual HRESULT STDMETHODCALLTYPE OnResponse(
   /* [in] */ DWORD dwResponseCode,
   /* [unique][in] */ LPCWSTR szResponseHeaders,
   /* [unique][in] */ LPCWSTR szRequestHeaders,
   /* [out] */ LPWSTR *pszAdditionalRequestHeaders)
 {
  this->m_nResponseStatus=dwResponseCode;
  this->m_sResponseHeaders=szResponseHeaders;
  return S_OK;
 }

七、其他参考文章

  1. 关于CCuteTools::CopyStream方法,相关信息请参见为何有些IStream不能得到HGlobal句柄 
  2. 关于CCuteTools::AutoWrap方法,相关信息请参见如何调用IDispatch接口的方法和属性  
  3. “CSDN助手”源代码下载,请转到http://blog.csdn.net/seasol/archive/2006/07/04/873747.aspx
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值