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

采用 REST 方式:一个用于快速 Web 接口交互的 Windows C++ 库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2017年1月1日

CPOL

3分钟阅读

viewsIcon

31847

downloadIcon

541

只需几个函数,即可调用 Google Drive 等各种 Web 库

REST 类现在是我 RGF 库 的一部分。

引言

Web 上有太多可用的服务(Google Drive、Facebook API 等),但你只能获得 Java SDK、PHP SDK、Python SDK,而从来没有 Windows SDK。这是一个小型的库,可以快速处理任何 REST 接口。

我将在我的下一篇文章中使用这个库,它将提供一个用于 Windows 的 Google/Onedrive/Dropbox 驱动器库。

ihandle 封装器

为了方便编码,HINTERNET WinInet 句柄被封装在一个 ihandle 类中

class ihandle
        {
        private:
            HINTERNET hX = 0;
            std::shared_ptr<size_t> ptr = std::make_shared<size_t>();

        public:

            void Close()
                {
                if (!ptr || !ptr.unique())
                    {
                    ptr.reset();
                    return;
                    }
                ptr.reset();
                if (hX != 0)
                    InternetCloseHandle(hX);
                hX = 0;
                }

            ihandle()
                {
                hX = 0;
                }
            ~ihandle()
                {
                Close();
                }
            ihandle(const ihandle& h)
                {
                Dup(h);
                }
            ihandle(ihandle&& h)
                {
                Move(std::forward<ihandle>(h));
                }
            ihandle(HINTERNET hY)
                {
                hX = hY;
                }
            ihandle& operator =(const ihandle& h)
                {
                Dup(h);
                return *this;
                }
            ihandle& operator =(ihandle&& h)
                {
                Move(std::forward<ihandle>(h));
                return *this;
                }

            void Dup(const ihandle& h)
                {
                Close();
                hX = h.hX;
                ptr = h.ptr;
                }
            void Move(ihandle&& h)
                {
                Close();
                hX = h.hX;
                ptr = h.ptr;
                h.ptr.reset();
                h.hX = 0;
                }
            operator HINTERNET() const
                {
                return hX;
                }
        }

这个类提供了复制/移动函数,并且可以代替标准的 HINTERNET 句柄使用。

提供者

该库必须从某个地方读取数据或将数据写入某个地方。该库期望读取数据的抽象接口是 data_provider

    class data_provider
        {
        public:
            
            virtual size_t s() = 0;
            virtual size_t Read(char* Buff, size_t sz) = 0;
            virtual bool CanOnce() = 0;
            virtual std::tuple<const char*, size_t> Once() = 0;

        };

为了方便起见,该库提供了它的两种实现:memory_data_provider file_data_provider,它们分别提供从内存缓冲区或文件句柄读取数据的功能。

写入数据的抽象接口是 data_writer

    class data_writer
        {
        public:

            virtual DWORD Write(const char* Buff, DWORD sz) = 0;
            virtual size_t s() = 0;

        };

此外,为了方便起见,该库还提供了 memory_data_writerfile_data_writer,用于写入到内存或句柄。

构造函数:传递代理名称。

REST(const wchar_t* ag = 0);

连接或断开连接

HRESULT Connect(const wchar_t* host, bool SSL = false, 
unsigned short Port = 0, DWORD flg = 0,const wchar_t* user= 0,const wchar_t* pass = 0)
void Disconnect();

如果 SSLtrue,则默认端口是 443,并且会附加 INTERNET_FLAG_SECURE 标志。

发起请求

ihandle Request2(const wchar_t* url, data_provider& dp, bool Once = true, 
    const wchar_t* verb = L"POST", std::initializer_list<wstring> hdrs = {}, 
    std::function<HRESULT(size_t sent, size_t tot, void*)> fx = nullptr, 
    void* lp = 0, 
    const char* extradata1 = 0, DWORD extradatasize1 = 0,
    const char* extradata2 = 0,DWORD extradatasize2 = 0);

此函数的参数是

  • 要连接的 url
    • 如果你传递一个完整的 url(例如 https://#/o/oauth2),则该函数
      • 如果请求是 GET 则使用 InternetOpenURL 获取整个 url 并返回该句柄,而不做任何其他操作(所有其他参数都将被忽略)。
      • 如果请求是除 GET 之外的动词,它会将 url 解析为其各个部分,并按照你传递相对 url 的方式继续执行后续步骤。
    • 如果你传递一个相对 url,则该函数使用它来 HttpOpenRequest()
  • data provider 为请求提供更多数据。 当请求需要 HTTP 正文时,例如传递表单数据或上传文件时,将使用此功能。
  • Once 可以为 true,以指示该函数一次性传递 dp 中的所有数据。 除非数据提供程序是内存数据提供程序并且 extradata1.extradata2null,否则此选项将被忽略。
  • verb 是要发送的 HTTP 动词,例如 GETPOSTPUT
  • hdrs 包括要发送到请求的额外 HTTP 标头(例如,在 Google Auth 中,你可以使用 Authorization: Bearer 标头)。
  • fx lp 指定一个函数和一个自定义值。 只要上传正在进行,就会定期调用该函数。
  • 如果 extradata1/extradatasize1 不为 null,则在上传 dp 中的数据之前发送它们。
  • 如果 extradata2/extradatasize2 在上传 dp 中的数据之后发送它们。

成功时,该函数返回一个非 null ihandle

为了方便起见,该库提供了 RequestWithBuffer RequestWithFile 函数。

获取与请求相关的任何 headers

long Headers(ihandle& hI3,std::map<wstring,wstring>& t);

它以 map 形式返回所有标头,并将 HTTP 结果代码(例如 200 或 404)作为返回值返回。

读取结果

HRESULT Read2(ihandle& hI3, data_writer& dw, 
std::function<HRESULT(unsigned long long, unsigned long long, void*)> 
                      fx = nullptr, void* lp = 0);

此函数的参数是

  • 要读取的 handle
  • 用于写入数据的 provider
  • fxlp 指定一个函数和一个自定义值。 只要下载正在进行,就会定期调用该函数。

如果下载的大小未知,则 fx 函数的第二个参数为 -1

为了方便起见,该库提供了 ReadToFileReadToMemory 函数。

Google 身份验证

Google 身份验证流程在 text.exe 中演示。

  • 该应用程序会询问客户端 ID 和密钥。 要使用它,你必须拥有应用程序的客户端 ID 和密钥(在 Google API 控制台中创建)。
  • 该应用程序会打开一个 Google 权限 URL,你单击“允许”以获取代码。
  • 该应用程序会询问代码。
  • 然后,该应用程序使用 REST 来获取访问令牌
if (FAILED(Connect(L"accounts.google.com", true)))
    return false;

string u;
// Build the thing to be passed as the request body
u = {...} ;

wchar_t au[1000] = { 0 };
swprintf_s(au, 1000, L"Content-Length: %u", (DWORD)u.length());
auto hi = RequestWithBuffer(L"/o/oauth2/token", L"POST",
     { L"Content-Type: application/x-www-form-urlencoded",au }, 
      u.data(), u.size());

然后读取返回结果

vector<char> out;
ReadToMemory(hi, out);
out.resize(out.size() + 1); // to make it a ASCIIZ
char* p = (char*)out.data();
j.Parse(p);

并使用 JSON 进行解析,以提供刷新令牌和访问令牌。

历史

  • 21-01-2017:添加了 user+pass 以进行连接以及一些小修复
  • 04-01-2017:修复了一个(严重的)错误,哎
  • 01-01-2017:新年快乐(以及首次发布)
© . All rights reserved.