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





5.00/5 (14投票s)
只需几个函数,即可调用 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_writer
和 file_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();
如果 SSL
是 true
,则默认端口是 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.extradata2
是null
,否则此选项将被忽略。verb
是要发送的 HTTP 动词,例如GET
、POST
或PUT
。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
fx
和lp
指定一个函数和一个自定义值。 只要下载正在进行,就会定期调用该函数。
如果下载的大小未知,则 fx
函数的第二个参数为 -1
。
为了方便起见,该库提供了 ReadToFile
和 ReadToMemory
函数。
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:新年快乐(以及首次发布)