A tiny, dependency-free wrapper around WinINet for developers targeting Windows only, who need a lightweight native solution. Inspired by the excellent CPR library, it has mostly identical function names, and will likewise work with random parameter order.
Below is a basic GET request - for detailed examples see the documentation below.
#include <iostream>
#include "wnetwrap.h"
int main()
{ //GET method and firefox user agent used by default
wrap::Response r = wrap::HttpsRequest(wrap::Url{"https://www.example.com/"});
std::cout << r.text << std::endl; // basic parser
std::cout << r.status_code << std::endl; // 200
}
Implemented | Upcoming |
---|---|
Custom headers | Asynchronous requests |
Url encoded parameters | Callbacks |
Url encoded POST values | NTLM authentication |
Multipart form POST upload | Digest authentication |
File POST upload | PUT, PATCH and DELETE methods |
Basic authentication | |
Bearer authentication | |
Connection and request timeout | |
Cookie support | |
Proxy support |
Just put wnetwrap.h
and wnetwrap.cpp
in your project folder. That's it!
For now it's all here on the readme, but it will eventually be put on a different page to make navigation more user friendly. To navigate through it use the table of contents dropdown menu.
中文文档稍后会贴在这里但是现在只有英文的,对不起。
Making a GET request with WNetWrap is simple - the GET method is used by default so doesn't need to be specified:
#include <wnetwrap.h>
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"});
This gives us a Response
object which we’ve called r. There’s a lot of useful stuff in there:
std::cout << r.url << std::endl; // http://www.httpbin.org/get
std::cout << r.status_code << std::endl; // 200
std::cout << r.header["content-type"] << std::endl; // application/json
std::cout << r.text << std::endl;
/*
* {
* "args": {},
* "headers": {
* ..
* },
* "url": "http://httpbin.org/get"
* }
*/
To add URL-encoded parameters, add a Parameters
object to the HttpsRequest
call:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"},
wrap::Parameters{{"hello", "world"}});
std::cout << r.url << std::endl; // http://www.httpbin.org/get?hello=world
std::cout << r.text << std::endl;
/*
* {
* "args": {
* "hello": "world"
* },
* "headers": {
* ..
* },
* "url": "http://httpbin.org/get?hello=world"
* }
*/
Parameters
is an object with a map-like interface. You can construct it using a list of key/value pairs inside the HttpsRequest
or have it outlive HttpsRequest
by constructing it outside:
// Constructing it in place
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"},
wrap::Parameters{{"hello", "world"}, {"stay", "cool"}});
std::cout << r.url << std::endl; // http://www.httpbin.org/get?hello=world&stay=cool
std::cout << r.text << std::endl;
/*
* {
* "args": {
* "hello": "world"
* "stay": "cool"
* },
* "headers": {
* ..
* },
* "url": "http://httpbin.org/get?hello=world&stay=cool"
* }
*/
// Constructing it outside
wrap::Parameters parameters = wrap::Parameters{{"hello", "world"}, {"stay", "cool"}};
wrap::Response r_outside = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"}, parameters);
std::cout << r_outside.url << std::endl; // http://www.httpbin.org/get?hello=world&stay=cool
std::cout << r_outside.text << std::endl; // Same text response as above
To download the contents of the request you simply add a Download
parameter to HttpsRequest
. If this parameter's value is blank then the file is downloaded with its original filename, otherwise the value provided will be the new file's name. For example, to download the CPR library:
wrap::HttpsRequest(wrap::Url{ "https://github.com/whoshuu/cpr/archive/refs/tags/1.6.0.zip" }, wrap::Download{});
When you download a file, the .raw
and .text
properties of the response object will be returned empty.
Making a POST request is just a matter of specifying the HTTP method:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/post"},
wrap::Payload{{"key", "value"}},
wrap::Method{"POST"});
std::cout << r.text << std::endl;
/*
* {
* "args": {},
* "data": "",
* "files": {},
* "form": {
* "key": "value"
* },
* "headers": {
* ..
* "Content-Type": "application/x-www-form-urlencoded",
* ..
* },
* "json": null,
* "url": "http://www.httpbin.org/post"
* }
*/
This sends up "key=value"
as a "x-www-form-urlencoded"
pair in the POST request. To send data raw and unencoded, use Body
instead of Payload
:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/post"},
wrap::Body{"This is raw POST data"},
wrap::Header{{"Content-Type", "text/plain"}},
wrap::Method{"POST"});
std::cout << r.text << std::endl;
/*
* {
* "args": {},
* "data": "This is raw POST data",
* "files": {},
* "form": {},
* "headers": {
* ..
* "Content-Type": "text/plain",
* ..
* },
* "json": null,
* "url": "http://www.httpbin.org/post"
* }
*/
Here you will notice that the "Content-Type"
is being set explicitly to "text/plain"
. This is because by default, "x-www-form-urlencoded"
is used for raw data POSTs. For cases where the data being sent up is small, either "x-www-form-urlencoded"
or "text/plain"
is suitable. If the data package is large or contains a file, it’s more appropriate to use a Multipart
upload. In this example we are uploading a textfile to file.io:
wrap::Response r = wrap::HttpsRequest(wrap::Url{ "file.io" },
wrap::Multipart{ {"file:sample1","sample.txt"} },
wrap::Method{ "POST" });
std::cout << r.text << std::endl;
/*
{"success":true,"status":200,"id":"0a1dc4a0-d056-11eb-b8a8-95e106f75f99","key":"JBDaFwjAneQH","name":"sample.txt","link":"https://
file.io/JBDaFwjAneQH","private":false,"expires":"2021-07-02T16:55:52.042Z","downloads":0,"maxDownloads":1,"autoDelete":true,"size"
:53,"mimeType":"text/plain","created":"2021-06-18T16:55:52.042Z","modified":"2021-06-18T16:55:52.042Z"}
*/
Notice how the text file, which in this case was passed as sample1
, had file:
prefixed before it - this tells WNetWrap that this is a file and not a key - value pair.
To use Basic Authentication which uses a username and password, just add Authentication
to the call:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/basic-auth/user/pass"},
wrap::Authentication{"user", "pass"});
std::cout << r.text << std::endl;
/*
* {
* "authenticated": true,
* "user": "user"
* }
*/
Authentication via an OAuth - Bearer Token can be done using the Bearer
authentication object:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/bearer"},
wrap::Bearer{"ACCESS_TOKEN"});
std::cout << r.text << std::endl;
/*
* {
* "authenticated": true,
* "token": "ACCESS_TOKEN"
* }
*/
A Response
has these public fields and methods:
std::string status_code; // The HTTP status code for the request
std::string raw; // The body of the HTTP response
std::string text; // The text body in case of HTML response - if not HTML, same as raw above
std::map header; // A map of the header fields received
std::map sent_headers; // A map of the headers sent
std::map secinfo; // A map of certificate information strings (HTTPS only)
std::string url; // The effective URL of the ultimate request
std::string err; // An error string containing the error code and a message
unsigned long uploaded_bytes; // How many bytes have been sent to the server
unsigned long downloaded_bytes; // How many bytes have been received from the server
unsigned long redirect_count; // How many redirects occurred
The header
is a map with an important modification. Its keys are case insensitive as required by RFC 7230:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"});
std::cout << r.header["content-type"] << std::endl;
std::cout << r.header["Content-Type"] << std::endl;
std::cout << r.header["CoNtEnT-tYpE"] << std::endl;
All of these should print the same value, "application/json"
.
Using Header
in your HttpsRequest
you can specify custom headers:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/headers"},
wrap::Header{{"accept", "application/json"}});
std::cout << r.text << std::endl;
/*
* "headers": {
* "Accept": "application/json",
* "Host": "www.httpbin.org",
* "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"
* }
*/
It’s possible to set a timeout for your request if you have strict timing requirements:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"},
wrap::Timeout{1000}); // will timeout after 1000 ms
Setting the Timeout
option sets the maximum allowed time the connection or request operation can take in milliseconds. By default a Timeout will only apply to the request itself, but you can specify either one by adding either connection
or request
, or both with all
:
wrap::Timeout{1000,"connection"}
Since WNetWrap is built on top of WinINet, it’s important to know what setting this Timeout
does to the request. It creates a worker thread which executes the connection or request call. This thread is then monitored and killed if it takes longer than the timeout specified. The reason this approach is taken is that the normal method of setting a timeout with WinINet does not work, due to a 20+ year old MS bug. You can find out more about this workaround here. What it means in practical terms is that Timeout
cannot be set to a value higher than WinINet's default (currently 1 hour).
Setting up a proxy is easy:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"},
wrap::Proxy{"http://www.fakeproxy.com"});
std::cout << r.url << std::endl; // Prints http://www.httpbin.org/get, not the proxy url
You have the option of specifying a username and password for the proxy:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"},
wrap::Proxy{"http://www.fakeproxy.com", "UserName" , "p455w0rd!"});
Accessing received cookies is done like this:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/cookies/set?cookies=yummy"});
std::cout << r.cookies["cookies"] << std::endl; // Prints yummy
std::cout << r.cookies["Cookies"] << std::endl; // Prints nothing
To send new cookies just use Cookies
in the request:
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/cookies"},
wrap::Cookies{{"ice cream", "is delicious"}});
std::cout << r.text << std::endl;
/*
* {
* "cookies": {
* "ice%20cream": "is%20delicious"
* }
* }
*/
By default Cookies
and their values will be URL-encoded. Although this is recommend, it is not mandatory for Cookies
to be URL-encoded.
[...] To maximize compatibility with user agents, servers that wish to store arbitrary data in a cookie-value SHOULD encode that data, for example, using Base64 [RFC4648]. [...]
Source: RFC6265
URL-encoding for Cookies
can be disabled by setting encode cookies to false or off in the Options
constructor (see more on Options
below).
wrap::Response r = wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/cookies"},
wrap::Cookies{{"ice cream", "is delicious"}}, Options{ {"encode cookies","off"}});
std::cout << r.text << std::endl;
/*
* {
* "cookies": {
* "ice cream": "is delicious"
* }
* }
*/
To see the response's security info, you will need to access the secinfo
map. For example, to get the security certificate:
r.secinfo["certificate"]
For www.example.com
this returns:
Subject:
US
California
Los Angeles
Internet Corporation for Assigned Names and Numbers
www.example.org
Issuer:
US
DigiCert Inc
DigiCert TLS RSA SHA256 2020 CA1
Effective Date: 24/11/2020 00:00:00
Expiration Date: 25/12/2021 23:59:59
Security Protocol: (null)
Signature Type: (null)
Encryption Type: (null)
Privacy Strength: High (128 bits)
cipher : AES 128-bit encryption algorithm
Due to WinInet limitations, some data such as the protocol and encryption type may appear as (null)
- however this may be found in other parts of the certificate, such as under Issuer
above. This can also be found as one of several additional elements in the secinfo
map:
cout << r.secinfo["protocol"]; // example.com: Transport Layer Security 1.2 client-side
Cycling through the secinfo
map will show all other available security info:
cout << "security info:" << endl;
for (auto elem : r.secinfo)
{
cout << elem.first + " : " + elem.second + "\r\n";
}
This gives the map keys and values (I've omitted the certificate):
cipher : AES 128-bit encryption algorithm
cipher_strength : 128
hash : SHA hashing algorithm
hash_strength : 128
key_exch : RSA key exchange
key_exch_strength : 2048
protocol : Transport Layer Security 1.2 client-side
The Options
constructor allows you to specify additional options for the request. For example, to turn off redirects (which are on by default and handled automatically by WNetWrap):
wrap::HttpsRequest(wrap::Url{"http://www.httpbin.org/get"}, wrap::Options{"redirect" , "off"});
To enable the option you can use either on
or true
, and to disable either false
or off
will do.