65.9K
CodeProject 正在变化。 阅读更多。
Home

基本的互联网请求

2011年7月24日

CPOL

3分钟阅读

viewsIcon

21689

downloadIcon

5

用于使用 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 包装类)。

  1. internet_api api 对象:只不过是 wininet.dll 的一个花哨的动态包装器,用于调用:InternetConnectW, InternetOpenW, InternetCloseHandle 等; 加载 wininet.dll,使用 GetProcAddress 解析所有 API,并传递给所有需要 API 调用的其他类。
  2. 实例化并打开 session 对象,该对象调用 InternetOpenW;
  3. 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; }
        };
  4. 实例化 connection 对象,该对象调用 InternetConnectW 以连接到主机
  5. 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; }
    };
  6. 实例化 internet_request req 对象,该对象调用 HttpOpenRequestW 以执行请求打开(使用默认动词 GET),获取请求响应 (HttpSendRequestW),状态和数据 (HttpQueryInfoW, InternetReadFile),并将响应转换为 std::wstring
  7. 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_tvector_t 的说明 - 它们肯定在 STL 和 boost 世界中有更好的等效项,但对于我的项目,最小依赖项(减去操作系统和 VC 运行时)是必须的,它们目前服务于它们的目的。)

历史

  • 版本 1.0 - 2011 年 7 月 24 日。
© . All rights reserved.