REST class is now part of my RGF library.
Introduction
Too many services are available for the web (Google Drive, Facebook APIs, etc.) but you only get a Java SDK, a PHP SDK, a Python SDK and never a Windows SDK. Here is a small library that can take care of any REST interface in a quick manner.
I will use this library in my next article, which will provide a Google/Onedrive/Dropbox drive library for Windows.
The ihandle Wrapper
To facilitate coding, the HINTERNET
WinInet handle is wrapped in an ihandle
class:
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;
}
}
This class provides copy/move functions and can be used in place of the standard HINTERNET
handle.
The Providers
The library must read from or write data to somewhere. The abstract interface which the library expects to read data is 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;
};
For convenience, the library provides two implementations of it, memory_data_provider
and file_data_provider
which provide reading functions from a memory buffer or a file handle respectively.
The abstract interface to write data is data_writer
:
class data_writer
{
public:
virtual DWORD Write(const char* Buff, DWORD sz) = 0;
virtual size_t s() = 0;
};
In addition, the library provides memory_data_writer
and file_data_writer
to write to memory or a handle, for convenience.
The Library
Constructor: Pass the agent name.
REST(const wchar_t* ag = 0);
Connect or disconnect:
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();
If SSL
is true
, the default port is 443
and flags are appended INTERNET_FLAG_SECURE
.
Make a request:
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);
The parameters to this function are:
- The
url
to connect.
- If you pass a full
url
(like https://accounts.google.com/o/oauth2), then the function:
- If the request is a
GET
, takes the entire url
with InternetOpenURL
and returns that handle without doing anything else (all other parameters are ignored). - If the request is a verb other than
GET
, it cracks the url
to its parts and proceeds to the steps as if you had passed a relative url
.
- If you pass a relative
url
, then the function uses it to HttpOpenRequest()
.
- The
data provider
to provide more data to the request. This is used when the request requires a HTTP body, for example, when passing form data or when uploading a file. Once
can be true
to instruct the function to pass the entire data in dp
at once. This is ignored unless the data provider is a memory data provider and extradata1.extradata2
are null
. verb
is the HTTP verb to send, like GET
, POST
or PUT
. hdrs
include extra HTTP headers to be sent to the request (for example, in Google Auth, you can use an Authorization: Bearer header). fx
and lp
specify a function and a custom value, The function is to be called periodically as long as the upload is progressing. extradata1
/extradatasize1
are sent, if not null
, before the upload of the data in dp
. extradata2
/extradatasize2
are sent, if not null
, after the upload of the data in dp
.
The function returns a non-null ihandle
on success.
For convenience, the library provides the RequestWithBuffer
and RequestWithFile
functions.
Get any headers
related to a request:
long Headers(ihandle& hI3,std::map<wstring,wstring>& t);
It returns all the headers in a map
and also the HTTP result code (say, 200 or 404) as a return value.
Read the result:
HRESULT Read2(ihandle& hI3, data_writer& dw,
std::function<HRESULT(unsigned long long, unsigned long long, void*)>
fx = nullptr, void* lp = 0);
The parameters to this function are:
- The
handle
to read - A
provider
to write the data fx
and lp
specify a function and a custom value, The function is to be called periodically as long as the download is progressing.
If the size of the download is unknown, the second parameter of the fx
function is -1
.
For convenience, the library provides the ReadToFile
and ReadToMemory
functions.
Google Authentication
The Google authentication flow is demonstrated in text.exe.
- The app asks for the Client ID and secret. To use it, you must have an application's client ID and secret (created in Google API Console).
- The app opens a Google permission URL which you click "Allow" in order to get a code.
- The app asks for the code.
- The app then uses REST to get the access token:
if (FAILED(Connect(L"accounts.google.com", true)))
return false;
string u;
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());
The return is then read:
vector<char> out;
ReadToMemory(hi, out);
out.resize(out.size() + 1); char* p = (char*)out.data();
j.Parse(p);
and parsed with JSON, to provide the refresh and access token.
History
- 21-01-2017: Added user+pass to connect and some minor fixes
- 04-01-2017: Fixed a (serious) bug, duh
- 01-01-2017: Happy new year (and first release)