C++下web框架corw的完全使用手册(实现中文支持)

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的最后一部分

猜你喜欢

转载自blog.csdn.net/a486259/article/details/134624530
今日推荐