網路程式設計:C++REST SDK簡析

xiaoyou137發表於2020-10-11

http_client程式碼示例

#include <iostream>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif 
#include <cpprest/http_client.h>

using namespace utility;
using namespace web::http;
using namespace web::http::client;
using namespace std::cerr;
using namespace std::endl;

#ifdef _WIN32 //根據平臺來定義tcout,確保多語言的文字能夠正確輸出
#define tcout std::wcout;
#else
#define tcout std::cout;
#endif

//從http_response中取出頭部的字串表示
auto get_headers(http_response resp) { 
	auto headers = resp.to_string();
	auto end = headers.find("\r\n\r\n");
	if (end != string_t::npos) {
		headers.resize(end + 4);
	}
	return headers;
}

auto get_request(string_t uri) {
	http_client client{ uri };
	//用GET的方式發起一個客戶端請求
	//並使用then方法串聯了兩個下一步動作
	//http_client::request的返回值是pplx::task<http_response>.then是pplx::task類别範本的成員函式,引數是能接受其型別引數物件的函式物件,
	//除了最後一個then,其他每個then裡都應該返回一個pplx::task,而task的內部型別,就是下一個then塊裡函式物件接受的引數型別。
	auto request = client.request(methods::GET).then([](http_response resp) {
		if (resp.status_code() != status_codes::OK) {
			//不OK,顯示當前響應資訊
			auto headers = get_headers(resp);
			tcout << headers;
		}
		//進一步取出完整響應
		return resp.extract_string();
		}).then([](string_t str) {
			//輸出到終端
			tcout << str;
			});
		return request;
}
#ifdef _WIN32 //根據平臺定義合適的程式入口
int wmian(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif 
{
#ifdef _WIN32
	_setmode(_fileno(stdout), _O_WTEXT);
#endif
	if (argc != 2) {
		cerr << "A URL is needed!\n";
		return 1;
	}
	//等待請求及其關聯處理全部完成
	try {
		auto request = get_request(argv[1]);
		request.wait();
	}
	//處理請求過程中產生的異常
	catch (const std::exception& e){
		cerr << "Error exception " << e.what() << endl;
		return 1;
	}
}



C++REST SDK是用來開發http客戶端和伺服器的現代非同步C++程式碼庫,支援http伺服器,http客戶端,非同步流,任務,JSON,URI,websocket客戶端。

非同步流
C++REST SDK實現了一套非同步流,能夠實現對檔案的非同步讀寫。
以下程式碼展示了把網路請求響應非同步儲存到檔案result.html中

#include <iostream>
#include <utility>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif 
#include <stddef.h>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>

using namespace utility;
using namespace web::http;
using namespace web::http::client;
using namespace concurrency::streams;
using namespace std::cerr;
using namespace std::endl;

#ifdef _WIN32 //根據平臺來定義tcout,確保多語言的文字能夠正確輸出
#define tcout std::wcout;
#else
#define tcout std::cout;
#endif

//從http_response中取出頭部的字串表示
auto get_headers(http_response resp) { 
	auto headers = resp.to_string();
	auto end = headers.find("\r\n\r\n");
	if (end != string_t::npos) {
		headers.resize(end + 4);
	}
	return headers;
}

auto get_request(string_t uri) {
	http_client client{ uri };
	//用GET的方式發起一個客戶端請求
	//並使用then方法串聯了兩個下一步動作
	//http_client::request的返回值是pplx::task<http_response>.then是pplx::task類别範本的成員函式,引數是能接受其型別引數物件的函式物件,
	//除了最後一個then,其他每個then裡都應該返回一個pplx::task,而task的內部型別,就是下一個then塊裡函式物件接受的引數型別。
	auto request = client.request(methods::GET)
		.then([](http_response resp) {
		if (resp.status_code() == status_codes::OK) {
			//正常的話
			tcout << U("Saving ...\n");
			ostream fs;
			fstream::open_ostream(
				U("result.html"), std::ios_base::out | std::ios_base::trunc
			).then([&fs, resp](ostream os) {
				fs = os;
				//讀取網頁內容到流
				return resp.body().read_to_end(fs.streambuf());
				}).then([&fs](size_t size) {
					fs.close();
					tcout << size << U(" bytes  saved\n");
					}).wait();
		}
		else {
			//否則顯示當前響應資訊
			auto headers = get_headers(resp);
			tcout << headers;
			tcout << resp.extract_string().get();
		}
		});
		return request;
}
#ifdef _WIN32 //根據平臺定義合適的程式入口
int wmian(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif 
{
#ifdef _WIN32
	_setmode(_fileno(stdout), _O_WTEXT);
#endif
	if (argc != 2) {
		cerr << "A URL is needed!\n";
		return 1;
	}
	//等待請求及其關聯處理全部完成
	try {
		auto request = get_request(argv[1]);
		request.wait();
	}
	//處理請求過程中產生的異常
	catch (const std::exception& e){
		cerr << "Error exception " << e.what() << endl;
		return 1;
	}
}



C++REST SDK的物件大部分都是基於shared_ptr實現的,因而可以輕鬆大膽的進行復制

JSON支援
C++REST SDK對JSON有很好的支援,JSON的基本型別有空值型別,布林型別,數字型別和字串型別,複合型別是陣列(array)和物件(object)
在C++REST SDK裡,核心型別是web::json::value

C++REST SDK裡的http_request 和http_response對JSON有原生支援,如可以使用extract_json成員函式來非同步提取http請求或響應體中的JSON內容

http伺服器
C++ REST SDK的http_listener會通過呼叫Boost.Asio和作業系統的底層介面(IOCP,epoll)來完成功能,向使用者隱藏這些細節,提供一個簡單的程式設計介面

//簡單rest伺服器示例程式碼,處理sayHi請求
#include <exception>
#include <iostream>
#include <map>
#include <string>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include <cpprest/http_listener.h>
#include <cpprest/json.h>

using namespace std;
using namespace utility;
using namespace web;
using namespace web::http;
using namespace web::http::experimental::listener;

#ifdef _WIN32
#define tcout std::wcout;
#else
#define tcout std::cout;
#endif

void handle_get(http_request req) {
	//http_request::request_uri函式返回的是uri的引用,因而用auto&來接收
	auto& uri = req.request_uri();
	if (uri.path() != U("/sayHi")) {
		req.reply(status_codes::NotFound);
		return;
	}
	tcout << uri::decode(uri.query()) << endl;

	auto query = uri::split_query(uri.query());
	auto it = query.find(U("name"));
	if (it == query.end()) {
		req.reply(status_codes::BadRequest, U("Missing query info"));
		return;
	}

	//http_request::reply的第二個引數是json::value型別,這會讓HTTP的Content-Type自動設定成 application/json
	auto answer = json::value::object(true);
	answer[U("msg")] = json::value(string_t(U("Hi, ")) + uri::decode(it->second) + U("!"));
	req.reply(status_codes::OK, answer);
}

int main() {
#ifdef _WIN32
	_setmode(_fileno(stdout), _O_WTEXT);
#endif
	http_listener listener(U("http://127.0.0.1:8080/"));
	listener.support(methods::GET, handle_get);
	try {
		listener.open().wait();
		tcout << "Listener. Press Enter to exit.\n";
		string line;
		getline(cin, line);
		listener.close().wait();
	}
	catch (const exception& e) {
		cerr << e.what() << endl;
		return 1;
	}
}

C++REST SDK使用非同步的程式設計模式,使得寫不阻塞的程式碼變得很容易,底層它是用一個執行緒池實現的,預設會開啟40個執行緒,如果使用完了,會導致系統阻塞,執行緒數量程式碼中可控

//設定執行緒池大小為10
#include <pplx/threadpool.h>
crossplat::threadpool::initialize_with_threads(10);

C++REST伺服器應當增加執行緒池大小,並且對併發數量進行統計,在併發數接近執行緒池大小時拒絕新的連線,一般可返回status_codes::ServiceUnavailable,以避免系統阻塞

相關文章