太原市制作网站,专业的免费建站,网页设计html代码大全划掉线,晋宁网站建设原文
基于C20协程的http库--cinatra
cinatra是基于C20无栈协程实现的跨平台,仅头,高性能,易用的http/https库(http1.1),包括httpserver和httpclient,功能完备,不仅支持最普通的getpost等请求,还支持restfulapi,websocket,chunked,ranges,multipart,静态文件服务和反向代理等功…原文
基于C20协程的http库--cinatra
cinatra是基于C20无栈协程实现的跨平台,仅头,高性能,易用的http/https库(http1.1),包括httpserver和httpclient,功能完备,不仅支持最普通的getpost等请求,还支持restfulapi,websocket,chunked,ranges,multipart,静态文件服务和反向代理等功能.
后面会分别介绍这些功能,文末也专门附上benchmark测试代码.
2.基本http请求
2.1.启动http服务器
#include include/cinatra.hpp
using namespace cinatra;
void start_server() {coro_http_server server(/*thread_num*/std::thread::hardware_concurrency(), 9001);server.set_http_handlerGET(/, [](coro_http_request req, coro_http_response resp) {resp.set_status_and_content(status_type::ok, ok); //IO线程中的响应});server.set_http_handlerGET, POST(/in_thread_pool,[](coro_http_request req,coro_http_response resp) - async_simple::coro::Lazyvoid {//在线程池中休息.co_await coro_io::post([] {resp.set_status_and_content(status_type::ok, ok in thread pool);});});server.sync_start();
}几行代码即可创建一个http服务,先设置httpserver工作线程数和端口.然后设置http服务的url,httpmethod和对应的处理函数. 可在io线程或线程池中处理http请求.
2.2.client发请求
#include include/cinatra.hpp
using namespace cinatra;
async_simple::coro::Lazyvoid do_request() {coro_http_client client{};auto result co_await client.async_get(http://127.0.0.1:9001/);assert(result.status 200);assert(result.resp_body ok);for (auto [key, val] : result.resp_headers) {std::cout key : val \n;}result co_await client.async_get(/in_thread_pool);assert(result.status 200);
}httpclient异步请求服务器,返回结果中包括服务端响应的状态码,httpcontent及http头,如果网络错误,可从result.net_err中取错码和错误message.
2.3.restfulapi coro_http_server server(/*thread_num*/std::thread::hardware_concurrency(), 9001);server.set_http_handlercinatra::GET, cinatra::POST(/test2/{}/test3/{},[](coro_http_request req,coro_http_response resp) - async_simple::coro::Lazyvoid {co_await coro_io::post([]() {CHECK(req.matches_.str(1) name);CHECK(req.matches_.str(2) test);resp.set_status_and_content(cinatra::status_type::ok, hello world);});co_return;});server.set_http_handlercinatra::GET, cinatra::POST(R(/numbers/(\d)/test/(\d)),[](coro_http_request req, coro_http_response response) {CHECK(req.matches_.str(1) 100);CHECK(req.matches_.str(2) 200);response.set_status_and_content(status_type::ok, number regex ok);});server.set_http_handlercinatra::GET, cinatra::POST(/user/:id, [](coro_http_request req, coro_http_response response) {CHECK(req.params_[id] cinatra);response.set_status_and_content(status_type::ok, ok);});server.set_http_handlercinatra::GET, cinatra::POST(/user/:id/subscriptions,[](coro_http_request req, coro_http_response response) {CHECK(req.params_[id] subid);response.set_status_and_content(status_type::ok, ok);});server.set_http_handlercinatra::GET, cinatra::POST(/values/:x/:y/:z,[](coro_http_request req, coro_http_response response) {CHECK(req.params_[x] guilliman);CHECK(req.params_[y] cawl);CHECK(req.params_[z] yvraine);response.set_status_and_content(status_type::ok, ok);});server.async_start();coro_http_client client;client.get(http://127.0.0.1:9001/test2/name/test3/test);client.get(http://127.0.0.1:9001/numbers/100/test/200);client.get(http://127.0.0.1:9001/user/cinatra);client.get(http://127.0.0.1:9001/user/subid/subscriptions);client.get(http://127.0.0.1:9001/value/guilliman/cawl/yvraine);
2.4. https访问
#ifdef CINATRA_ENABLE_SSLcoro_http_client client{};result co_await client.async_get(https://www.taobao.com);assert(result.status 200);
#endif访问https网站时,确保已安装了openssl并开启了ENABLE_SSL.
3. websocket cinatra::coro_http_server server(1, 9001);server.set_http_handlercinatra::GET(/ws_echo,[](cinatra::coro_http_request req,cinatra::coro_http_response resp) - async_simple::coro::Lazyvoid {cinatra::websocket_result result{};while (true) {result co_await req.get_conn()-read_websocket();if (result.ec) {break;}if (result.type cinatra::ws_frame_type::WS_CLOSE_FRAME) {REQUIRE(result.data test close);break;}auto ec co_await req.get_conn()-write_websocket(result.data);if (ec) {break;}}});server.sync_start();在协程处理函数中,while循环异步读写websocket数据.
client端: cinatra::coro_http_client client{};std::string message(100, x);client.on_ws_close([](std::string_view reason) {std::cout web socket close reason std::endl;});client.on_ws_msg([message](cinatra::resp_data data) {if (data.net_err) {std::cout ws_msg net error data.net_err.message() \n;return;}std::cout ws msg len: data.resp_body.size() std::endl;REQUIRE(data.resp_body message);});co_await client.async_ws_connect(ws://127.0.0.1:9001/ws_echo);co_await client.async_send_ws(message);co_await client.async_send_ws_close(test close);client设置读回调和close回调分别处理收到的websocket消息和websocketclose消息.
4.静态文件服务 std::string filename temp.txt;create_file(filename, 64);coro_http_server server(1, 9001);std::string virtual_path download;std::string files_root_path ; //当前路径server.set_static_res_dir(virtual_path,files_root_path);//在服务器启动之前设置此项,如果添加新文件,则需要重启服务器.server.async_start();coro_http_client client{};auto result co_await client.async_get(http://127.0.0.1:9001/download/temp.txt);assert(result.status 200);assert(result.resp_body.size() 64);服务端设置虚路径和实际文件路径,下载文件时输入虚路径和实际路径下的文件名即可实现下载.
5.反向代理
假设有3个服务器需要代理,代理服务器根据负载均衡算法来选择其中的一个来访问并把结果返回给客户.
5.1.先启动3个被代理的服务器 cinatra::coro_http_server web_one(1, 9001);web_one.set_http_handlercinatra::GET, cinatra::POST(/,[](coro_http_request req,coro_http_response response) - async_simple::coro::Lazyvoid {co_await coro_io::post([]() {response.set_status_and_content(status_type::ok, web1);});});web_one.async_start();cinatra::coro_http_server web_two(1, 9002);web_two.set_http_handlercinatra::GET, cinatra::POST(/,[](coro_http_request req,coro_http_response response) - async_simple::coro::Lazyvoid {co_await coro_io::post([]() {response.set_status_and_content(status_type::ok, web2);});});web_two.async_start();cinatra::coro_http_server web_three(1, 9003);web_three.set_http_handlercinatra::GET, cinatra::POST(/, [](coro_http_request req, coro_http_response response) {response.set_status_and_content(status_type::ok, web3);});web_three.async_start();5.2.启动代理服务器
设置roundrobin策略的代理服务器: coro_http_server proxy_rr(2, 8091);proxy_rr.set_http_proxy_handlerGET, POST(/rr, {127.0.0.1:9001, 127.0.0.1:9002, 127.0.0.1:9003},coro_io::load_blance_algorithm::RR);proxy_rr.sync_start();设置random策略的代理服务器: coro_http_server proxy_random(2, 8092);proxy_random.set_http_proxy_handlerGET, POST(/random, {127.0.0.1:9001, 127.0.0.1:9002, 127.0.0.1:9003});proxy_random.sync_start();设置weightroundrobin策略的代理服务器: coro_http_server proxy_wrr(2, 8090);proxy_wrr.set_http_proxy_handlerGET, POST(/wrr, {127.0.0.1:9001, 127.0.0.1:9002, 127.0.0.1:9003},coro_io::load_blance_algorithm::WRR, {10, 5, 5});proxy_wrr.sync_start(); 5.3.client请求代理服务器 coro_http_client client_rr;resp_data resp_rr client_rr.get(http://127.0.0.1:8091/rr);assert(resp_rr.resp_body web1);resp_rr client_rr.get(http://127.0.0.1:8091/rr);assert(resp_rr.resp_body web2);resp_rr client_rr.get(http://127.0.0.1:8091/rr);assert(resp_rr.resp_body web3);coro_http_client client_wrr;resp_data resp client_wrr.get(http://127.0.0.1:8090/wrr);assert(resp.resp_body web1);resp client_wrr.get(http://127.0.0.1:8090/wrr);assert(resp.resp_body web1);resp client_wrr.get(http://127.0.0.1:8090/wrr);assert(resp.resp_body web2);resp client_wrr.get(http://127.0.0.1:8090/wrr);assert(resp.resp_body web3); 6.增加切面
6.1.创建任意切面
struct log_t {bool before(coro_http_request , coro_http_response ) {std::cout before log std::endl;return true;}bool after(coro_http_request , coro_http_response res) {std::cout after log std::endl;res.add_header(aaaa, bbcc);return true;}
};
struct get_data {bool before(coro_http_request req, coro_http_response res) {req.set_aspect_data(hello world);return true;}
};切面是实现了before或after函数的类.
6.2.应用切面
async_simple::coro::Lazyvoid use_aspects() {coro_http_server server(1, 9001);server.set_http_handlerGET(/get,[](coro_http_request req, coro_http_response resp) {auto val req.get_aspect_data();assert(val[0] hello world);resp.set_status_and_content(status_type::ok, ok);},log_t{}, get_data{});//设置了两个切面,可按需设置任意个切面server.async_start();coro_http_client client{};auto result co_await client.async_get(http://127.0.0.1:9001/get);assert(result.status 200);
}注册httphandler时设置了两个切面,该url的处理会先进入切面,切面返回true,才会继续往下执行业务逻辑,如果返回false则不会执行后续逻辑,返回false时,要在切面中调用resp.set_status_and_content设置状态码和返回内容.
7.chunked,ranges,multipart
7.1.chunked上传下载
chunked协议适合大文件上传和下载:
async_simple::coro::Lazyvoid chunked_upload_download() {coro_http_server server(1, 9001);server.set_http_handlerGET, POST(/chunked,[](coro_http_request req,coro_http_response resp) - async_simple::coro::Lazyvoid {assert(req.get_content_type() content_type::chunked);chunked_result result{};std::string content;while (true) {result co_await req.get_conn()-read_chunked();if (result.ec) {co_return;}if (result.eof) {break;}content.append(result.data);}std::cout content size: content.size() \n;std::cout content \n;resp.set_format_type(format_type::chunked);resp.set_status_and_content(status_type::ok, chunked ok);});server.set_http_handlerGET, POST(/write_chunked,[](coro_http_request req,coro_http_response resp) - async_simple::coro::Lazyvoid {resp.set_format_type(format_type::chunked);bool ok;if (ok co_await resp.get_conn()-begin_chunked(); !ok) {co_return;}std::vectorstd::string vec{hello, world, ok};for (auto str : vec) {if (ok co_await resp.get_conn()-write_chunked(str); !ok) {co_return;}}ok co_await resp.get_conn()-end_chunked();});server.sync_start();result co_await client.async_get(http://127.0.0.1:9001/write_chunked);assert(result.status 200);assert(result.resp_body hello world ok);
}clientchunked上传文件
coro_http_client client{};std::string filename test.txt;create_file(filename, 1010);coro_io::coro_file file{};co_await file.async_open(filename, coro_io::flags::read_only);std::string buf;detail::resize(buf, 100);auto fn [file, buf]() - async_simple::coro::Lazyread_result {auto [ec, size] co_await file.async_read(buf.data(), buf.size());co_return read_result{buf, file.eof(), ec};};auto result co_await client.async_upload_chunked(http://127.0.0.1:9001/chunkedsv, http_method::POST, std::move(fn));client从读文件到分块上传文件整个过程都是异步的.
clientchunked下载文件: auto result co_await client.async_get(http://127.0.0.1:9001/write_chunked);assert(result.status 200);assert(result.resp_body hello world ok);这样下载到内存中,也可下载到文件中. auto result co_await client.async_download(http://127.0.0.1:9001/write_chunked, download.txt);CHECK(std::filesystem::file_size(download.txt)1010);7.2.ranges下载
async_simple::coro::Lazyvoid byte_ranges_download() {create_file(test_multiple_range.txt, 64);coro_http_server server(1, 8090);server.set_static_res_dir(, );server.async_start();std::this_thread::sleep_for(200ms);std::string uri http://127.0.0.1:8090/test_multiple_range.txt;{std::string filename test1.txt;std::error_code ec{};std::filesystem::remove(filename, ec);coro_http_client client{};resp_data result co_await client.async_download(uri, filename, 1-10);assert(result.status 206);assert(std::filesystem::file_size(filename) 10);filename test2.txt;std::filesystem::remove(filename, ec);result co_await client.async_download(uri, filename, 10-15);assert(result.status 206);assert(std::filesystem::file_size(filename) 6);}{coro_http_client client{};std::string uri http://127.0.0.1:8090/test_multiple_range.txt;client.add_header(Range, bytes1-10,20-30);auto result co_await client.async_get(uri);assert(result.status 206);assert(result.resp_body.size() 21);std::string filename test_ranges.txt;client.add_header(Range, bytes0-10,21-30);result co_await client.async_download(uri, filename);assert(result.status 206);assert(fs::file_size(filename) 21);}
}7.3.multipart上传下载 coro_http_server server(1, 8090);server.set_http_handlercinatra::PUT, cinatra::POST(/multipart_upload,[](coro_http_request req,coro_http_response resp) - async_simple::coro::Lazyvoid {assert(req.get_content_type() content_type::multipart);auto boundary req.get_boundary();multipart_reader_t multipart(req.get_conn());while (true) {auto part_head co_await multipart.read_part_head();if (part_head.ec) {co_return;}std::cout part_head.name \n;std::cout part_head.filename \n;std::shared_ptrcoro_io::coro_file file;std::string filename;if (!part_head.filename.empty()) {file std::make_sharedcoro_io::coro_file();filename std::to_string(std::chrono::system_clock::now().time_since_epoch().count());size_t pos part_head.filename.rfind(.);if (pos ! std::string::npos) {auto extent part_head.filename.substr(pos);filename extent;}std::cout filename \n;co_await file-async_open(filename, coro_io::flags::create_write);if (!file-is_open()) {resp.set_status_and_content(status_type::internal_server_error, file open failed);co_return;}}auto part_body co_await multipart.read_part_body(boundary);if (part_body.ec) {co_return;}if (!filename.empty()) {auto ec co_await file-async_write(part_body.data.data(), part_body.data.size());if (ec) {co_return;}file-close();CHECK(fs::file_size(filename) 1024);}else {std::cout part_body.data \n;}if (part_body.eof) {break;}}resp.set_status_and_content(status_type::ok, ok);co_return;});server.async_start();std::string filename test_1024.txt;create_file(filename);coro_http_client client{};std::string uri http://127.0.0.1:8090/multipart_upload;client.add_str_part(test, test value);client.add_file_part(test file, filename);auto result async_simple::coro::syncAwait(client.async_upload_multipart(uri));CHECK(result.status 200);8.benchmarkcode
8.1. brpc http benchmark code
DEFINE_int32(port, 9001, TCP Port of this server);
DEFINE_int32(idle_timeout_s, -1,Connection will be closed if there is no read/write operations during the last idle_timeout_s);
class HttpServiceImpl : public HttpService {
public:HttpServiceImpl() {}virtual ~HttpServiceImpl() {}void Echo(google::protobuf::RpcController *cntl_base, const HttpRequest *,HttpResponse *, google::protobuf::Closure *done) {brpc::ClosureGuard done_guard(done);brpc::Controller *cntl static_castbrpc::Controller *(cntl_base);std::string date_str{get_gmt_time_str()};cntl-http_response().SetHeader(Date, date_str);cntl-http_response().SetHeader(Server, brpc);cntl-http_response().set_content_type(text/plain);butil::IOBufBuilder os;os hello, world!;os.move_to(cntl-response_attachment());}
};
int main(int argc, char *argv[]) {GFLAGS_NS::ParseCommandLineFlags(argc, argv, true);brpc::Server server;example::HttpServiceImpl http_svc;if (server.AddService(http_svc, brpc::SERVER_DOESNT_OWN_SERVICE) ! 0) {LOG(ERROR) Fail to add http_svc;return -1;}brpc::ServerOptions options;options.idle_timeout_sec FLAGS_idle_timeout_s;if (server.Start(FLAGS_port, options) ! 0) {LOG(ERROR) Fail to start HttpServer;return -1;}server.RunUntilAskedToQuit();return 0;
}8.2.drogonbenchmarkcode
#include drogon/drogon.h
using namespace drogon;
int main() {app().setLogPath(./).setLogLevel(trantor::Logger::kWarn).addListener(0.0.0.0, 9001).setThreadNum(0).registerSyncAdvice([](const HttpRequestPtr req) - HttpResponsePtr {auto response HttpResponse::newHttpResponse();response-setBody(Hello, world!);return response;}).run();
}8.3.nginxhttp配置
user nginx;
worker_processes auto;
worker_cpu_affinity auto;
error_log stderr error;
#worker_rlimit_nofile 1024000;
timer_resolution 1s;
daemon off;
events {worker_connections 32768;multi_accept off; #default
}
http {include /etc/nginx/mime.types;access_log off;server_tokens off;msie_padding off;sendfile off; #defaulttcp_nopush off; #defaulttcp_nodelay on; #defaultkeepalive_timeout 65;keepalive_disable none; #default msie6keepalive_requests 300000; #default 100server {listen 9001 default_server reuseport deferred fastopen4096;root /;location /plaintext {default_type text/plain;return 200 Hello, World!;}}
}8.4.cinatrabenchmarkcode
#include cinatra.hpp
using namespace cinatra;
using namespace std::chrono_literals;
int main() {coro_http_server server(std::thread::hardware_concurrency(), 8090);server.set_http_handlerGET(/plaintext, [](coro_http_request req, coro_http_response resp) {resp.get_conn()-set_multi_buf(false);resp.set_content_typeresp_content_type::txt();resp.set_status_and_content(status_type::ok, Hello, world!);});server.sync_start();
}