corw是一个开源、轻量化的c++web库,在使用上与python的flask是类似的。本文档为corw的完整使用文档,含项目配置(基于cmakelist)、路由绑定、返回数据(json、文本、response对象、静态资源、模板文件)、接口请求处理(REST请求,url参数绑定、json请求、GET参数和POST参数)和各种高级操作(Cookie操作、Session操作、文件上传操作、文件下载操作、websocket操作、自定义loghandler)。此外,还对各类参数请求、结果返回过程中对中文的支持(如get参数、post参数、url参数、json结果中中文参数的正确解读)
1 基本设置
crow库官网:https://crowcpp.org/master/guides/app/
crow库源码:https://gitcode.net/mirrors/CrowCpp/Crow
1.1 Camkelist
项目的cmake文件如下,需要添加对asio、boost库的依赖,其中还补充了msysql库依赖(需要安装mysql)。
Asio库----》https://think-async.com/Asio/
下载地址:https://nchc.dl.sourceforge.net/project/asio/asio/1.26.0%20%28Stable%29/asio-1.26.0.zip
boost库----》https://www.boost.org/
下载地址:https://www.boost.org/users/news/
cmake_minimum_required(VERSION 3.5.1)
#生成生成项目的名称
project(CmakeTest)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_SUPPRESS_REGENERATION FALSE)
#设置生成release项目
set(CMAKE_BUILE_TYPE Release)
set(CMAKE_CXX_STANDARD 17)
# It prevents the decay to cpp98 when the compiler does not support cpp14
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# It disables the use of compiler-specific extensions
# e.g. -std=cpp14 rather than -std=gnu++14
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")file(GLOB SOURCE_FILES src/*.cpp)set(msysql_dir "C:/Program Files/MySQL/MySQL Server 8.0")
include_directories(${msysql_dir}/includeE:/Lib/Crow-master/includeE:/Lib/boost_1_77_0E:/Lib/asio-1.26.0/include${PROJECT_SOURCE_DIR}/include
)
link_directories(E:/Lib/boost_1_77_0/stage/lib${msysql_dir}/lib${PROJECT_SOURCE_DIR}/include
)
add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES})
target_link_libraries(${CMAKE_PROJECT_NAME}libmysql.lib${Opencv_lib}
)
1.2 起步设置
起步设置,设置对session的支持,
using Session = crow::SessionMiddleware<crow::InMemoryStore>;crow::App<crow::CookieParser, Session> app{ Session{// customize cookiescrow::CookieParser::Cookie("session").max_age(/*one day*/ 24 * 60 * 60).path("/"),// set session id length (small value only for demonstration purposes)4,// init the storecrow::InMemoryStore{}} };app.loglevel(crow::LogLevel::Warning);
官网完整起步代码,gitcode地址 https://gitcode.net/mirrors/CrowCpp/Crow
#include "crow.h"int main()
{crow::SimpleApp app;CROW_ROUTE(app, "/")([](){return "Hello world";});app.port(18080).multithreaded().run();
}
1.3 首页绑定
可以用于绑定静态文件
//首页绑定CROW_ROUTE(app, "/")([](const crow::request&, crow::response& res) {res.set_static_file_info("templates/index.html");res.end();//return u8"你好 世界!";});
1.4 静态文件支持
在做路由函数外部,设置静态文件目录,可以实现全局下的静态资源访问
//设置对静态文件的支持crow::response response;response.set_static_file_info("./");
2、返回各种数据
2.1 返回字符串
//返回字符串CROW_ROUTE(app, "/api1")([]() {return "Hello world";});
2.2 返回json
{{“name”, “lisi”},{“age”,“32”}}对应json字符串{“name”:“lisi”,“age”:“32”},在crow中以以{name,value}构成json中的键值对
//返回jsonCROW_ROUTE(app, "/json")([] {crow::json::wvalue x({ {"message", "Hello, World!"} });//添加新字段x["message2"] = "Hello, World.. Again!";return x.dump();//return x;});//返回json数组CROW_ROUTE(app, "/jsonarr")([] {crow::json::wvalue x({ {{"name", "lisi"},{"age","32"}},{{"name", "zhangsan"},{"age","28"}}});x[0]["message2"] = "new ";return x;});
2.3 返回response对象
前面的各种返回其实也是返回response对象,只是没有显性表示。这里可以实例化response对象,然后设置返回的具体对象
//response细节CROW_ROUTE(app, "/response/<int>")([](int count) {//返回404crow::response resp;resp.code = 404;resp.body = "response body";//return resp;//返回重定向crow::response res;res.redirect("/");//return res;if (count > 100) {//// response(int code, std::string contentType, std::string body)return crow::response(500);//return crow::response(400);}std::ostringstream os;os << count << " bottles of beer!";return crow::response(os.str());});
2.4 返回静态页面
在路由函数中返回静态页面
CROW_ROUTE(app, "/")([](){auto page = crow::mustache::load_text("fancypage.html");return page;});
2.5 返回静态资源
在路由函数中返回静态资源文件
CROW_ROUTE(app, "/imgs/<string>")([](string fname){string final_path = "static/" + fname + ".jpg";//cout << final_path << endl;crow::response res;res.set_static_file_info(final_path);return res;});
3、参数请求
3.1 url参数绑定及模板参数注入
模板文件需存储在templates目录下,html文件中的模板参数使用{{}}进行包裹
<p>Hello {{user}}</p><p>体重 {{count}} kg</p>
url参数在请求时以,等形式描述,用/进行分割,在路由lambda函数的参数中又对相应类型顺序的参数进行命名,以便于在代码中使用
//模板参数绑定CROW_ROUTE(app, "/set_name/<string>/<int>")([](std::string name,int count111) {auto page = crow::mustache::load("index.html");//ctx用于模板参数注入,以{name,value}构成参数对,再以{参数对,参数对,...}的形式构成参数集crow::mustache::context ctx({ {"user", "{set_name}:"+name},{"count", count111} });return page.render(ctx);});
3.2 处理 JSON Requests
CROW_ROUTE(app, "/add_json").methods("POST"_method)([](const crow::request& req) {auto x = crow::json::load(req.body);if (!x)return crow::response(crow::status::BAD_REQUEST); // same as crow::response(400)int sum = x["a"].i() + x["b"].i();std::ostringstream os;os << sum;return crow::response{ os.str() };});
3.3 GET参数及POST参数
GET参数的格式为url?key=value,是附加在url路径中的,同时可能还存在一个key对应多个value的情况,具体可查询官网https://crowcpp.org/master/guides/query-string/
GET参数获取示意,可以看到url_params.get用于获取一个值,url_params.get_list用于或者一组值
//get接口测试 127.0.0.1:18000/getfunc?key1=123&key2=342fsaj&keys=123&keys=sdfsCROW_ROUTE(app, "/getfunc").methods("GET"_method)([](const crow::request& req) {//req.url_params.get("key1") != nullptrstring key1=req.url_params.get("key1");string key2=req.url_params.get("key2");string keys0=req.url_params.get_list("keys")[0];string keys1=req.url_params.get_list("keys")[1];});
POST参数通常是通过表单传递的,表单格式如下:
<form action="/postfunc" method="POST" enctype="multipart/form-data">文件:<input type="file" name="myfile00" /><br/>name:<input type="input" name="name" /><br/>size:<input type="input" name="size" /><br/><input type="submit" value="提交" /></form>
POST接收数据的后端代码如下所示:
CROW_ROUTE(app, "/postfunc").methods("POST"_method)([](const crow::request& req) {crow::multipart::message x(req);//req.url_params.get("key1") != nullptrstring name = x.get_part_by_name("name").body;string size = x.get_part_by_name("size").body;string file = x.get_part_by_name("myfile00").body;//post表单中的文件只需要将其以二进制格式写入文件即可实现文件上传std::string saveFilePath = "upname";// fileName + "." + fileExt;std::ofstream saveFile;//一定要以二进制格式写入文件saveFile.open(saveFilePath, std::ofstream::binary);saveFile << file;saveFile.close();});
3.4 REST接口区分测试
在绑定url时同时指定methods参数,然后在函数体内部依据req.method区分具体请求类型
//rest接口测试CROW_ROUTE(app, "/pathToApi/<int>").methods("GET"_method, "POST"_method, "DELETE"_method)([](const crow::request& req, const int& id) {if (req.method == "GET"_method){if ((req.url_params.get("v") != nullptr) & (req.url_params.get("q") != nullptr)){// ...}return crow::response(200, "You used GET");}else if (req.method == "POST"_method){return crow::response(200, "You used POST");}else if (req.method == "DELETE"_method){return crow::response(200, "You used DELETE");}else{return crow::response(404);}});
4、高级操作
4.1 Cookie操作
CROW_ROUTE(app, "/read")([&](const crow::request& req) {auto& ctx = app.get_context<crow::CookieParser>(req);// Read cookies with get_cookieauto value = ctx.get_cookie("key");return "value: " + value;});CROW_ROUTE(app, "/write")([&](const crow::request& req) {auto& ctx = app.get_context<crow::CookieParser>(req);// Store cookies with set_cookiectx.set_cookie("key", "word")// configure additional parameters.path("/").max_age(120).httponly();return "ok!";});
4.2 Session操作
在实现session操作时,切记要使用crow::App<crow::CookieParser, Session>方法初始app对象
//session测试//http://127.0.0.1:18080/session_set?key=a2&value=li%20si//http://127.0.0.1:18080/session_set?key=a2&value=li%20siCROW_ROUTE(app, "/session_set")([&](const crow::request& req) {auto& session = app.get_context<Session>(req);//get只能获取url|get参数中的一个auto key = req.url_params.get("key");//get_list可以获取url|get参数中多个相同的key的valueauto value = req.url_params.get_list("value", false)[0];session.set(key, value);//在设置session后不能立即使用seesion.get(),要在下一次请求中生效。return "set "+ std::string(key)+"="+ std::string(value);});CROW_ROUTE(app, "/session_get")([&](const crow::request& req) {auto& session = app.get_context<Session>(req);session.get("key", "not-found"); // get string by key and return "not-found" if not foundsession.get("int", -1);session.get<bool>("flag"); // returns default value(false) if not found//删除特定session值session.remove("key");auto keys = session.keys();// return list of keysstd::string out;for (std::string key : keys)// session.string(key) converts a value of any type to a stringout += "<p> " + key + " = " + session.get(key, "_NOT_FOUND_"); +"</p>";return out;});
4.3 上传文件操作
在上传文件时,获取附件名称时存在bug,无法准确的获取中文名称
//upload测试CROW_ROUTE(app, "/uploader").methods("POST"_method)([](const crow::request& req) {crow::multipart::message x(req);std::string upname;std::string fileName;std::string fileExt;auto ss = x.get_part_by_name("myfile00");//获取前端from中input标签中name为myfile00的输入auto shead = ss.get_header_object("content-disposition");if (shead.params.size() != 0){upname = getFileNameFull(shead.params);//std::string fnf());int found(upname.find('.'));fileName = std::string(upname.substr(0, found));fileExt = std::string(upname.substr(found + 1));}std::string saveFilePath = upname;// fileName + "." + fileExt;std::ofstream saveFile;//一定要以二进制格式写入文件saveFile.open(saveFilePath, std::ofstream::binary);saveFile << ss.body;saveFile.close();return upname;});
上述代码中getFileNameFull的实现方式如下
inline std::string getFileNameFull(std::unordered_map<std::string, std::string>& map)
{for (auto pair : map){if (pair.first == "filename"){return pair.second;}}return "";
}
上传文件页面中的表单的写法
<form action="/uploader" method="POST" enctype="multipart/form-data"><input type="file" name="myfile00" /><input type="file" name="myfile11" /><input type="submit" value="提交" /></form>
4.4 文件下载操作
在crow中文件下载与返回静态资源基本上是一样的,只是在返回文件下载流时要设置Content-Type与Content-Disposition,若不设置在输出图片或文本文件时会被浏览器强制解析,而无法弹出下载框。这里实现的下载并不支持多线程分段下载。
//下载图片CROW_ROUTE(app, "/download/<string>/<string>")([&](string dir,string name) {string final_path= dir + "/" + name;//cout << final_path << endl;crow::response res;res.set_header("Content-Type","application/octet-stream");res.set_header("Content-Disposition", "attachment; filename="+ name);res.set_static_file_info(final_path);return res;});
若要实现文件的多线程分段下载,首先要从http请求头中获取Range信息,然后解析出该http请求所对应的文件起始位置和结束位置。在crow中返回的Range是一个字符串,需要自行进行字符串分割,然后将字符串转换为int。
req.get_header_value("Range");//Range: bytes=0-1199 其中位置0,结束位置1199
然后加载二进制文件,按照strat位置和end位置,读取二进制数据到bufer中(一般为char或byte数组)
最后将二进制转string,具体可参考https://www.jb51.net/article/55960.htm,将string赋值给resp.body,并设置好相应的http状态
res.body = bin2str(bin_data);
res.set_header("Content-Length","1200");//响应http请求头中Range的长度
res.set_header("Content-Range","bytes 0-1199/5000");//响应http请求头中Range的位置
res.set_header("Content-Type","application/octet-stream");
res.set_header("Content-Disposition", "attachment; filename="+ name);
前端通过多线程优化图像加载可以参考https://blog.csdn.net/frontend_frank/article/details/109506324
4.5 websocket操作
#include "crow.h"
#include <unordered_set>
#include <mutex>int main()
{crow::SimpleApp app;std::mutex mtx;std::unordered_set<crow::websocket::connection*> users;CROW_WEBSOCKET_ROUTE(app, "/ws").onopen([&](crow::websocket::connection& conn) {CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip();std::lock_guard<std::mutex> _(mtx);users.insert(&conn);}).onclose([&](crow::websocket::connection& conn, const std::string& reason) {CROW_LOG_INFO << "websocket connection closed: " << reason;std::lock_guard<std::mutex> _(mtx);users.erase(&conn);}).onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary) {std::lock_guard<std::mutex> _(mtx);//给当前用户发信息conn.send_text(data);//给所有用户发信息for (auto u : users){if (is_binary){u->send_binary(data);}else{u->send_text(data);}}});CROW_ROUTE(app, "/")([] {char name[256];gethostname(name, 256);crow::mustache::context x;x["servername"] = name;auto page = crow::mustache::load("ws.html");return page.render(x);});app.port(18080).multithreaded().run();
}
模板代码如下,在templates目录下创建ws.html
<!doctype html>
<html>
<head><script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
</head>
<body><input id="msg" type="text"></input><button id="send">Send</button><BR><textarea id="log" cols=100 rows=50></textarea><script>
var sock = new WebSocket("ws://{{servername}}:18080/ws");sock.onopen = ()=>{console.log('open')
}
sock.onerror = (e)=>{console.log('error',e)
}
sock.onclose = (e)=>{console.log('close', e)
}
sock.onmessage = (e)=>{$("#log").val(e.data +"\n" + $("#log").val());
}
$("#msg").keypress(function(e){if (e.which == 13){sock.send($("#msg").val());$("#msg").val("");}
});
$("#send").click(()=>{sock.send($("#msg").val());$("#msg").val("");
});</script>
</body>
</html>
4.6 自定义loghandler
使用crow输出log信息时,时区与中国东八区差了8个小时,为修正log信息时间的准确性,可以自定义loghandler;也可也查看logging.h,修改关键代码(如添加#define CROW_USE_LOCALTIMEZONE,使用localtime_s函数生成时间)
//获取指定格式的时间字符串
inline string get_now_time(string format= "%Y-%m-%d %H:%M:%S") {time_t rawtime;struct tm* info;char buffer[80];time(&rawtime);info = localtime(&rawtime);strftime(buffer, 80, format.c_str(), info);string stime(buffer);return stime;
}
//设置log记录器
class CustomLogger : public crow::ILogHandler {
public:int loglevel;CustomLogger(int loglevel) {this->loglevel = loglevel;}void log(std::string message, crow::LogLevel level) {// "message" doesn't contain the timestamp and loglevel// prefix the default logger does and it doesn't end// in a newline.int intlevel = (int)level;if (loglevel >= intlevel) {string time = get_now_time();std::cerr << time <<" " << message << std::endl;}}
};//---------------//绑定log记录器CustomLogger logger(0);crow::logger::setHandler(&logger);//app.loglevel(crow::LogLevel::Warning);//使用默认loghandler
//--------------
5、中文支持
post参数中文支持、 url|get参数中文支持、json结果中文支持请参考https://download.csdn.net/download/a486259/87471152的最后一部分