cinatra是一個高性能易用的http框架,它是用modern c++(c++17)開發的,它的目標是提供一個快速開發的c++ http框架。它的主要特點如下:
- 統一而簡單的接口
- header-only
- 跨平台
- 高效
- 支持面向切面編程
cinatra目前支持了http1.1/1.0和websocket, 你可以用它輕易地開發一個http服務器,比如常見的數據庫訪問服務器、文件上傳下載服務器、實時消息推送服務器,你也可以基於cinatra開發一個mqtt服務器。
如何使用
編譯依賴
cinatra是基於boost.asio開發的,所以需要boost庫,同時也需要支持c++17的編譯器,依賴項:
- boost.asio
- c++17編譯器(gcc7.2,clang4.0, vs2017 update15.5)
使用
cinatra是header-only的,直接引用頭文件既可。
快速示例
示例1:一個簡單的hello world
#include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); server.set_http_handler<GET, POST>("/", [](const request& req, response& res) { res.set_status_and_content(status_type::ok, "hello world"); }); server.run(); return 0; }
5行代碼就可以實現一個簡單http服務器了,用戶不需要關注多少細節,直接寫業務邏輯就行了。
示例2:展示如何取header和query以及錯誤返回
#include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); server.set_http_handler<GET, POST>("/test", [](const request& req, response& res) { auto name = req.get_header_value("name"); if (name.empty()) { res.set_status_and_content(status_type::bad_request, "no name"); return; } auto id = req.get_query_value("id"); if (id.empty()) { res.set_status_and_content(status_type::bad_request); return; } res.set_status_and_content(status_type::ok, "hello world"); }); server.run(); return 0; }
示例3:面向切面的http服務器
#include "http_server.hpp" using namespace cinatra; //日志切面 struct log_t { bool before(const request& req, response& res) { std::cout << "before log" << std::endl; return true; } bool after(const request& req, response& res) { std::cout << "after log" << std::endl; return true; } }; //校驗的切面 struct check { bool before(const request& req, response& res) { std::cout << "before check" << std::endl; if (req.get_header_value("name").empty()) { res.set_status_and_content(status_type::bad_request); return false; } return true; } bool after(const request& req, response& res) { std::cout << "after check" << std::endl; return true; } }; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); server.set_http_handler<GET, POST>("/aspect", [](const request& req, response& res) { res.set_status_and_content(status_type::ok, "hello world"); }, check{}, log_t{}); server.run(); return 0; }
本例中有兩個切面,一個校驗http請求的切面,一個是日志切面,這個切面用戶可以根據需求任意增加。本例會先檢查http請求的合法性,如果不合法就會返回bad request,合法就會進入下一個切面,即日志切面,六安廣播電視大學日志切面會打印出一個before表示進入業務邏輯之前的處理,池州先鋒網業務邏輯完成之后會打印after表示業務邏輯結束之后的處理。
示例4:文件上傳
cinatra目前支持了multipart和octet-stream格式的上傳。
multipart文件上傳
#include <atomic> #include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); std::atomic_int n = 0; //http upload(multipart) server.set_http_handler<GET, POST>("/upload_multipart", [&n](const request& req, response& res) { assert(req.get_http_type() == http_type::multipart); auto state = req.get_state(); switch (state) { case cinatra::data_proc_state::data_begin: { auto file_name_s = req.get_multipart_file_name(); auto extension = get_extension(file_name_s); std::string file_name = std::to_string(n++) + std::string(extension.data(), extension.length()); auto file = std::make_shared<std::ofstream>(file_name, std::ios::binary); if (!file->is_open()) { res.set_continue(false); return; } req.get_conn()->set_tag(file); } break; case cinatra::data_proc_state::data_continue: { if (!res.need_continue()) { return; } auto file = std::any_cast<std::shared_ptr<std::ofstream>>(req.get_conn()->get_tag()); auto part_data = req.get_part_data(); file->write(part_data.data(), part_data.length()); } break; case cinatra::data_proc_state::data_end: { std::cout << "one file finished" << std::endl; } break; case cinatra::data_proc_state::data_all_end: { //all the upstream end std::cout << "all files finished" << std::endl; res.set_status_and_content(status_type::ok); } break; case cinatra::data_proc_state::data_error: { //network error } break; } }); server.run(); return 0; }
短短幾行代碼就可以實現一個http文件上傳的服務器了,包含了異常處理和錯誤處理。
octet-stream文件上傳
#include <atomic> #include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); std::atomic_int n = 0; //http upload(octet-stream) server.set_http_handler<GET, POST>("/upload_octet_stream", [&n](const request& req, response& res) { assert(req.get_http_type() == http_type::octet_stream); auto state = req.get_state(); switch (state) { case cinatra::data_proc_state::data_begin: { std::string file_name = std::to_string(n++);; auto file = std::make_shared<std::ofstream>(file_name, std::ios::binary); if (!file->is_open()) { res.set_continue(false); return; } req.get_conn()->set_tag(file); } break; case cinatra::data_proc_state::data_continue: { if (!res.need_continue()) { return; } auto file = std::any_cast<std::shared_ptr<std::ofstream>>(req.get_conn()->get_tag()); auto part_data = req.get_part_data(); file->write(part_data.data(), part_data.length()); } break; case cinatra::data_proc_state::data_end: { std::cout << "one file finished" << std::endl; } break; case cinatra::data_proc_state::data_error: { //network error } break; } }); server.run(); return 0; }
示例5:文件下載
#include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); //http download(chunked) server.set_http_handler<GET, POST>("/download_chunked", [](const request& req, response& res) { auto state = req.get_state(); switch (state) { case cinatra::data_proc_state::data_begin: { std::string filename = "2.jpg"; auto in = std::make_shared<std::ifstream>(filename, std::ios::binary); if (!in->is_open()) { req.get_conn()->on_error(); return; } auto conn = req.get_conn(); conn->set_tag(in); auto extension = get_extension(filename.data()); auto mime = get_mime_type(extension); conn->write_chunked_header(mime); } break; case cinatra::data_proc_state::data_continue: { auto conn = req.get_conn(); auto in = std::any_cast<std::shared_ptr<std::ifstream>>(conn->get_tag()); std::string str; const size_t len = 2*1024; str.resize(len); in->read(&str[0], len); size_t read_len = in->gcount(); if (read_len != len) { str.resize(read_len); } bool eof = (read_len==0|| read_len != len); conn->write_chunked_data(std::move(str), eof); } break; case cinatra::data_proc_state::data_end: { std::cout << "chunked send finish" << std::endl; } break; case cinatra::data_proc_state::data_error: { //network error } break; } }); server.run(); return 0; }
示例6:websocket
#include "http_server.hpp" using namespace cinatra; int main() { http_server server(std::thread::hardware_concurrency()); server.listen("0.0.0.0", "8080"); //web socket server.set_http_handler<GET, POST>("/ws", [](const request& req, response& res) { assert(req.get_http_type() == http_type::websocket); auto state = req.get_state(); switch (state) { case cinatra::data_proc_state::data_begin: { std::cout << "websocket start" << std::endl; } break; case cinatra::data_proc_state::data_continue: { auto part_data = req.get_part_data(); //echo req.get_conn()->send_ws_msg(std::string(part_data.data(), part_data.length())); std::cout << part_data.data() << std::endl; } break; case cinatra::data_proc_state::data_close: { std::cout << "websocket close" << std::endl; } break; case cinatra::data_proc_state::data_error: { std::cout << "network error" << std::endl; } break; } }); server.run(); return 0; }
性能測試
測試用例:
ab測試:ab -c100 -n5000 127.0.0.1:8080/
服務器返回一個hello。
在一個8核心16G的雲主機上測試,qps在9000-13000之間。
對比測試
通過ab測試和boost.beast做對比,二者qps相當,大概是因為二者都是基於boost.asio開發的的原因。cinatra目前還沒做專門的性能優化,還有提升空間。
注意事項
文件上傳下載,websocket的業務函數是會多次進入的,因此寫業務邏輯的時候需要注意,推薦按照示例中的方式去做。
cinatra目前剛開始在生產環境中使用, 還處於開發完善階段,可能還有一些bug,因此不建議現階段直接用於生產環境,建議先在測試環境下試用。
試用沒問題了再在生產環境中使用,試用過程中發現了問題請及時提issue反饋或者郵件聯系我。
測試和使用穩定之后cinatra會發布正式版。
roadmap
- 支持ssl
- 支持斷點續傳
- 支持session和cookie
- 接口優化、性能優化
我希望cinatra有越來越多的人使用並喜歡它,也希望在在使用過程中越來越完善,變成一個強大易用、快速開發的http框架,歡迎大家積極參與cinatra項目,可以提issue也可以發郵件提建議,也可以提pr,形式不限。
這次重構的cinatra幾乎是重寫了一遍,代碼比之前的少了30%以上,接口統一了,http和業務分離,具備更好的擴展性和可維護性。