基本的互联网请求
用于使用 Wininet 进行简单互联网调用的类。
引言
过去几天在办公室里,我一直听到很多关于如何进行一些互联网请求的抱怨。 基本上,一位同事编写了一些 Qt 代码,想要进行一些简单的 GET 请求。 QNetworkAccessManager
, QNetworkReply
, 信号, slots, deleteLater()
, 涉及了大约 3-7 个类 - 有点太多了。 所以我抓取了一些我在个人项目中使用过的简单类,并将它们抛给了 Vlad(那位咆哮的同事,而不是穿刺者),瞧 - 问题解决了。
背景
熟悉 Wininet 以及非常基本的 C++ 类用法 (wstring
, stream
) 很有用。 了解 Justin.TV REST API - 请转到文档页面 这里。
开始:从后往前
我们将从这里开始,从我们想要做的事情开始。 在这种情况下,我正在编写一个流媒体播放器,并且我正在实现 justin.tv 支持 - 我正在进行 stream/summary justin.tv API 调用。
int __cdecl
wmain(int argc, wchar_t** argv) {
// get justin.tv stream summary
jtv_test_streamsummary();
return 0;
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
}
stream/summary 调用依次调用 "justin.tv" API 调用 - 在 jtv_api
类中实现(见下文)。
void jtv_test_streamsummary() {
std::wstring data;
if(jtv_api::streamsummary(data, L"", L"", L"")) {
std::wcout << data << std::endl;
}
}
jtv_api
类(为实现此目的而修剪的版本)实现了公共调用 streamsearch
; 我不会深入探讨 jtv REST API - stream/search 返回 XML 或 JSON,其中包含有关所有直播频道的聚合信息。(HTTP 调用的组成是最小的 - 只是将该调用的参数 - 频道,类别和语言附加到基本路径 * /api/stream/summary*,这些参数将结果过滤到指定值(如果有)- 所以不要指望这适用于特殊的未转义情况等。 请记住 - 这是一个示例。)
class jtv_api {
private:
jtv_api() {
}
~jtv_api() {
}
private:
jtv_api(const jtv_api&);
jtv_api& operator=(const jtv_api&);
public:
static bool streamsummary(std::wstring& data,
const std::wstring& channel,
const std::wstring& category,
const std::wstring& language
) {
// compose request relative url
std::wstring rpath = L"/api/stream/summary";
rpath += L".xml";
// add query part
std::wstring qry = L"";
if(!channel.empty()) {
if(!qry.empty()) {
qry += L"&"; }
qry += L"channel=";
qry += channel;
}
if(!category.empty()) {
if(!qry.empty()) {
qry += L"&"; }
qry += L"category=";
qry += category;
}
if(!language.empty()) {
if(!qry.empty()) {
qry += L"&"; }
qry += L"language=";
qry += language;
}
// append query part, if any
if(!qry.empty()) {
rpath += L"?";
rpath += qry;
}
return _execute_request(data, rpath);
}
反过来,所有公共调用 - *这个* 公共调用,在此版本中 :) - 路由到 _execute_request
私有助手,该助手执行所有 HTTP 操作并将字节响应转换为 std::wstring,使用 _response_to_string
助手
private:
static bool _response_to_string(std::wstring& data, const vector_t<BYTE>& response) {
int cv = ::MultiByteToWideChar(CP_UTF8, 0,
(LPCSTR)response.operator const BYTE *(), response.size(), NULL, 0);
if(cv != 0) {
wchar_t* wz = new wchar_t[cv];
if(!wz) {
return false; }
memset(wz, 0, cv * sizeof(wchar_t));
int cv2 = ::MultiByteToWideChar(CP_UTF8, 0,
(LPCSTR)response.operator const BYTE *(), response.size(), wz, cv);
data = wz;
delete []wz;
return true;
UNREFERENCED_PARAMETER(cv2);
}
return false;
}
我们感兴趣的是 _execute_request
调用
static bool _execute_request(std::wstring& data,
const std::wstring& request, const std::wstring& verb = L"GET") {
/*1*/ internet_api api;
/*2*/ internet_session session(&api);
if(!session.open()) {
return false; }
/*3*/ internet_connection connection(&api, &session, L"api.justin.tv");
if(!connection.open()) {
return false; }
// do request
/*4*/ internet_request req(&api, &connection);
if(!req.open(verb.c_str(), request.c_str())) {
return false; }
std::wstring status;
vector_t<BYTE> response;
if(!req.get_response(status, response)) {
return false; }
if(!_response_to_string(data, response)) {
return false; }
return true;
}
它浓缩了所有互联网工作。 这些调用使用如下所述的互联网类(除了 internet_api
之外的所有类都派生自 internet_handle
HINTERNET
包装类)。
internet_api
api
对象:只不过是 wininet.dll 的一个花哨的动态包装器,用于调用:InternetConnectW
,InternetOpenW
,InternetCloseHandle
等; 加载 wininet.dll,使用GetProcAddress
解析所有 API,并传递给所有需要 API 调用的其他类。- 实例化并打开
session
对象,该对象调用InternetOpenW
; - 实例化
connection
对象,该对象调用InternetConnectW
以连接到主机 - 实例化
internet_request
req
对象,该对象调用HttpOpenRequestW
以执行请求打开(使用默认动词 GET),获取请求响应 (HttpSendRequestW
),状态和数据 (HttpQueryInfoW
,InternetReadFile
),并将响应转换为std::wstring
。
class internet_session
: public internet_handle {
public:
internet_session(internet_api* api)
: internet_handle(api) {
}
virtual ~internet_session() {
}
private:
internet_session(const internet_session&);
internet_session& operator=(const internet_session&);
public:
virtual bool open() {
HINTERNET h = _api->api_InternetOpenW(
NULL,
INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, 0);
if(h == NULL) {
return false; }
attach(h);
return true; }
};
class internet_connection
: public internet_handle {
private:
pointer_t<internet_session, false> _session;
std::wstring _host;
public:
internet_connection(internet_api* api,
internet_session *session, const std::wstring& host)
: internet_handle(api)
, _session(session)
, _host(host) {
}
virtual ~internet_connection() {
}
private:
internet_connection(const internet_connection&);
internet_connection& operator=(const internet_connection&);
public:
const std::wstring& host() const {
return _host; }
virtual bool open() {
if(!_session) {
return false; }
HINTERNET h = _api->api_InternetConnectW(
*_session, _host.c_str(),
INTERNET_INVALID_PORT_NUMBER, NULL, NULL,
INTERNET_SERVICE_HTTP,
0, 0);
if(h == NULL) {
return false; }
attach(h);
return true; }
};
class internet_request
: public internet_handle {
private:
pointer_t<internet_connection, false> _connection;
public:
internet_request(internet_api* api, internet_connection *connection)
: internet_handle(api)
, _connection(connection) {
}
virtual ~internet_request() {
}
private:
internet_request(const internet_request&);
internet_request& operator=(const internet_request&);
public:
virtual bool open(LPCWSTR verb, LPCWSTR object) {
if(!_connection) {
return false; }
HINTERNET h = _api->api_HttpOpenRequestW(
*_connection,
verb,
object,
HTTP_VERSIONW,
NULL, NULL,
INTERNET_FLAG_KEEP_CONNECTION
| INTERNET_FLAG_NO_UI
| INTERNET_FLAG_KEEP_CONNECTION
| INTERNET_FLAG_PRAGMA_NOCACHE
| INTERNET_FLAG_DONT_CACHE
| INTERNET_FLAG_RELOAD, 0);
if(h == NULL) {
return false; }
attach(h);
return true; }
public:
bool get_response(std::wstring& status,
vector_t<BYTE>& response) {
status = L"";
if(!_api->api_HttpSendRequestW(_h, NULL, 0, NULL, 0)) {
return false; }
WCHAR sz[128] = L"";
DWORD buflen = _countof(sz);
if(!_api->api_HttpQueryInfoW(
_h, HTTP_QUERY_STATUS_CODE,
(LPVOID)sz, &buflen, NULL)) {
return false; }
status = sz;
// read response
DWORD bufrd = 0;
do {
BYTE respp[8192 + 1];
memset(respp, 0, _countof(respp));
bufrd = 0;
if(!_api->api_InternetReadFile(
_h, (LPVOID)respp, _countof(respp) - 1, &bufrd)) {
return false; }
if(bufrd == 0) {
break; }
for(DWORD d = 0; d < bufrd; d++) {
response += respp[d]; }
} while(bufrd != 0);
response += (BYTE)'\0';
response += (BYTE)'\0';
return true; }
};
一切都被封装起来了 - 在这个例子中,在 _execute_request(data, rpath)
调用中。 这就是最初的目的 - 拥有一种简单的方法,仅使用简单的 Win32 调用和极简类来执行 HTTP 客户端请求。
(关于支持类,pointer_t
和 vector_t
的说明 - 它们肯定在 STL 和 boost 世界中有更好的等效项,但对于我的项目,最小依赖项(减去操作系统和 VC 运行时)是必须的,它们目前服务于它们的目的。)
历史
- 版本 1.0 - 2011 年 7 月 24 日。