Skip to content

WinINet wrapper - tiny windows HTTPS library, no dependencies. 封装了WinInet的强大http请求库 make POST and GET requests

License

Notifications You must be signed in to change notification settings

hack-tramp/WNetWrap

Repository files navigation

WNetWrap

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
 }

Features

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

Usage

Just put wnetwrap.h and wnetwrap.cpp in your project folder. That's it!

Documentation

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.

中文文档稍后会贴在这里但是现在只有英文的,对不起。

GET requests

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

Downloading a file

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.

POST Requests

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.

Authentication

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"
 * }
 */

Response Objects

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".

Request Headers

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"
 * }
 */

Setting Timeouts

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).

Using Proxies

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!"});

Sending & Receiving Cookies

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"
 *   }
 * }
 */

Security Certificate Info

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

Additional Options

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.