图 1 会话、连接和请求
® 的现今的应用程序必须能够执行 HTTP 请求。虽然 HTTP 相对简单,但现今的 HTTP 的处理却未必简单。异步处理需要缓冲大量的请求和响应、身份验证、自动代理服务器检测、持久连接等操作。当然,您可以忽略其中的许多问题,但这会影响应用程序的质量,而且模拟 HTTP 不像模拟 TCP 套接字那样简单。那么,C++ 开发人员的任务是什么?
® .NET Framework。事实上,使用托管代码的开发人员仍然必须处理许多我刚刚提到的问题,而且具象状态传输 (REST) 等许多新式 Web 服务使用本机代码进行编程也很容易。
® 和 Windows Server
® 2008 中,WinHTTP 支持上载大于 4GB 的文件、改进了基于证书的身份验证,并且还提供了检索源 IP 地址和目标 IP 地址的功能。
图 1 中描述了这些对象之间的关系。一个会话可能具有多个活动连接,并且一个连接可能包含多个并发请求。
会话、连接和请求(单击图像可查看大图)
DWORD length = 0; request.QueryOption(WINHTTP_OPTION_URL, 0, length); ASSERT(ERROR_INSUFFICIENT_BUFFER == ::GetLastError()); CString url; COM_VERIFY(request.QueryOption(WINHTTP_OPTION_URL, url.GetBufferSetLength(length / sizeof(WCHAR)), length)); url.ReleaseBuffer();
图 2 WinHttpHandle 类
class WinHttpHandle { public: WinHttpHandle() : m_handle(0) {} ~WinHttpHandle() { Close(); } bool Attach(HINTERNET handle) { ASSERT(0 == m_h); m_handle = handle; return 0 != m_handle; } HINTERNET Detach() { HANDLE handle = m_handle; m_handle = 0; return handle; } void Close() { if (0 != m_handle) { VERIFY(::WinHttpCloseHandle(m_handle)); m_handle = 0; } } HRESULT SetOption(DWORD option, const void* value, DWORD length) { if (!::WinHttpSetOption(m_handle, option, const_cast
(value), length)) { return HRESULT_FROM_WIN32(::GetLastError()); } return S_OK; } HRESULT QueryOption(DWORD option, void* value, DWORD& length) const { if (!::WinHttpQueryOption(m_handle, option, value, &length)) { return HRESULT_FROM_WIN32(::GetLastError()); } return S_OK; } HINTERNET m_handle; };
确定代理设置”的侧栏中的内容)。最后一个参数中包含标记,但当前只定义了其中一个标记。WINHTTP_FLAG_ASYNC 指示 WinHTTP 函数将异步执行:
HINTERNET session = ::WinHttpOpen(0, // no agent string WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
图 2 中的 WinHttpHandle 派生的 WinHttpSession 类,WinHttpSession 类可以简化会话对象的创建过程。WinHttpSession 使用某些默认值创建会话,在大多数情况下,这些默认值就足够了。
图 3 WinHttpSession 类
HINTERNET connection = ::WinHttpConnect(session, L"example.com", INTERNET_DEFAULT_PORT, 0); // reserved if (0 == connection) { // Call GetLastError for error information }
图 4 WinHttpConnection 类
class WinHttpConnection : public WinHttpHandle { public: HRESULT Initialize(PCWSTR serverName, INTERNET_PORT portNumber, const WinHttpSession& session) { if (!Attach(::WinHttpConnect(session.m_handle, serverName, portNumber, 0))) // reserved { return HRESULT_FROM_WIN32(::GetLastError()); } return S_OK; } };
HINTERNET request = ::WinHttpOpenRequest(connection, 0, // use GET as the verb L"/developers/", 0, // use HTTP version 1.1 WINHTTP_NO_REFERRER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); // flags
if (WINHTTP_INVALID_STATUS_CALLBACK == ::WinHttpSetStatusCallback(request, Callback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0)) // reserved { // Call GetLastError for error information }
if (!::WinHttpSendRequest(request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, // headers length WINHTTP_NO_REQUEST_DATA, 0, // request data length 0, // total length 0)) // context { // Call GetLastError for error information }
图 5 简要显示了我将在本文的其余部分中构建的 WinHttpRequest 类模板。OnCallback 成员函数的内部存在着许多有趣的逻辑。不过,我首先要介绍如何设计 WinHttpRequest 类模板。
图 5 WinHttpRequest 类模板
template
class WinHttpRequest : public WinHttpHandle { public: HRESULT Initialize(PCWSTR path, __in_opt PCWSTR verb, const WinHttpConnection& connection) { HR(m_buffer.Initialize(8 * 1024)); // Call WinHttpOpenRequest and WinHttpSetStatusCallback. } HRESULT SendRequest(__in_opt PCWSTR headers, DWORD headersLength, __in_opt const void* optional, DWORD optionalLength, DWORD totalLength) { T* pT = static_cast
(this); // Call WinHttpSendRequest with pT as the context value. } protected: static void CALLBACK Callback(HINTERNET handle, DWORD_PTR context, DWORD code, void* info, DWORD length) { if (0 != context) { T* pT = reinterpret_cast
(context); HRESULT result = pT->OnCallback(code, info, length); if (FAILED(result)) { pT->OnResponseComplete(result); } } } HRESULT OnCallback(DWORD code, const void* info, DWORD length) { // Handle notifications here. } SimpleBuffer m_buffer; };
® 库使用这一常用技术来实现有效编译时虚拟函数调用。类似于 WinHttpSession 类和 WinHttpConnection 类,WinHttpRequest 可提供用于初始化 WinHTTP 请求对象的 Initialize 成员函数。若要初始化 WinHTTP 请求对象,首先要创建请求数据和响应数据所要使用的缓冲区。该缓冲区的实现并不重要。它只需管理 BYTE 数组的生存期。接着,Initialize 调用 WinHttpOpenRequest 来创建 WinHTTP 请求对象,并调用 WinHttpSetStatusCallback 来将其与 WinHttpRequest 类内部的静态 Callback 成员函数关联起来。
图 6 中所示的 DownloadFileRequest 具体类以全面了解 WinHttpRequest。这样做的好处是 DownloadFileRequest 可以重点了解下载文件涉及的细节,而不会陷入 HTTP 请求管理的复杂性问题中。其 Initialize 成员函数可以接受要下载的源文件的路径以及要将该文件编写到的目标流。每次接收到响应块时都会调用 OnReadComplete 成员函数;请求完成时则调用 OnResponseComplete。
图 6 DownloadFileRequest 类
class DownloadFileRequest : public WinHttpRequest
{ public: HRESULT Initialize(PCWSTR source, IStream* destination, const WinHttpConnection& connection) { m_destination = destination; return __super::Initialize(source, 0, // GET connection); } HRESULT OnReadComplete(const void* buffer, DWORD bytesRead) { return m_destination->Write(buffer, bytesRead, 0); // ignored } void OnResponseComplete(HRESULT result) { if (S_OK == result) { // Download succeeded } } private: CComPtr
m_destination; };
图 7 中您可以看到,OnCallback 再次使用 static_cast 来将“this”指针调整为指向派生类。然后,使用 switch 语句来处理各种通知。如果存在尚未处理的特定通知,则返回 S_FALSE。如果您希望覆盖派生类中的 OnCallback 成员函数,这可能会派上用场。
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: { if (!::WinHttpReceiveResponse(m_handle, 0)) // reserved { return HRESULT_FROM_WIN32(::GetLastError()); } break; }
:
图 8)。
图 8 检查成功请求
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: { DWORD statusCode = 0; DWORD statusCodeSize = sizeof(DWORD); if (!::WinHttpQueryHeaders(m_handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) { return HRESULT_FROM_WIN32(::GetLastError()); } if (HTTP_STATUS_OK != statusCode) { return E_FAIL; } if (!::WinHttpReadData(m_handle, m_buffer.GetData(), m_buffer.GetCount(), 0)) // async result { return HRESULT_FROM_WIN32(::GetLastError()); } break; }
图 8 中,为了检索服务器返回的状态代码,我使用了 WINHTTP_QUERY_STATUS_CODE 标记,还使用了 WINHTTP_QUERY_FLAG_NUMBER 修饰符,以告知 WinHttpQueryHeaders 我希望返回的值是 32 位数字而不是字符串。
图 9)。
图 9 检查通知
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: { if (0 < length) { HR(pT->OnReadComplete(m_buffer.GetData(), length)); if (!::WinHttpReadData(m_handle, m_buffer.GetData(), m_buffer.GetCount(), 0)) // async result { return HRESULT_FROM_WIN32(::GetLastError()); } } else { pT->OnResponseComplete(S_OK); } break; }
图 6 中 DownloadFileRequest 类中的“事件”的来源。每次接收到数据时,WINHTTP_CALLBACK_STATUS_READ_COMPLETE 处理程序都会调用派生类中的 OnReadComplete 方法。然后,调用 WinHttpReadData 来读取下一个数据块。最后,没有其他数据可用时,将调用派生类中的 OnResponseComplete 方法,如果出现 S_OK,则表示已成功读取响应。
图 10 介绍了更新后的 WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 处理程序,它可以轻松处理 GET 和 POST 这两个请求。
图 10 处理 GET 和 POST 请求
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: { HRESULT result = pT->OnWriteData(); if (FAILED(result)) { return result; } if (S_FALSE == result) { if (!::WinHttpReceiveResponse(m_handle, 0)) // reserved { return HRESULT_FROM_WIN32(::GetLastError()); } } break; }
if (!::WinHttpWriteData(request, buffer, count, 0)) // async result { // Call GetLastError for error information }
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/205599.html原文链接:https://javaforall.net
