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}/include
E:/Lib/Crow-master/include
E:/Lib/boost_1_77_0
E:/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 cookies
crow::CookieParser::Cookie("session").max_age(/*one day*/ 24 * 60 * 60).path("/"),
// set session id length (small value only for demonstration purposes)
4,
// init the store
crow::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中的键值对
//返回json
CROW_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) {
//返回404
crow::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=sdfs
CROW_ROUTE(app, "/getfunc")
.methods("GET"_method)
([](const crow::request& req) {
//req.url_params.get("key1") != nullptr
string 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") != nullptr
string 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_cookie
auto 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_cookie
ctx.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%20si
CROW_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的value
auto 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 found
session.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 keys
std::string out;
for (std::string key : keys)
// session.string(key) converts a value of any type to a string
out += "<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的最后一部分