目录
6.Query Strings(查询字符串,一般指的是url中"?"后的部分)
1.Included Middlewares(包含中间件) (已测试)
Different parts of Crow
4.Templating (Mustache) 模板引擎
允许你返回带有自定义数据的 HTML 页面,Crow 支持 Mustache 模板引擎,通过 crow::mustache
实现。
实现主要包括两个组件:
Page:这是模板文件,包含 HTML 结构和占位符,用于插入动态数据。
Context:上下文对象,包含要插入模板中的数据。
①其中Page 指的是包含Mustache标签的HTML模板文件,模板文件通常加载到 crow::mustache::template_t
对象中,并且需要放在名为 templates
的目录中,该目录应位于 Crow 可执行文件的当前工作目录内。
②使用crow::mustache::set_base("new_templates_directory")来为特定路由设置模板目录; 使用crow::mustache::set_global_base("new_templates_directory") 来全局设置模板目录。
include "crow.h"
int main() {
crow::SimpleApp app;
// 全局设置模板目录
crow::mustache::set_global_base("global_templates");
CROW_ROUTE(app, "/")
([]{
crow::mustache::context ctx;
ctx["name"] = "Global Directory";
auto page = crow::mustache::load("example.html");
return page.render(ctx);
});
// 为这个特定路由设置不同的模板目录
CROW_ROUTE(app, "/special")
([]{
crow::mustache::set_base("special_templates");
crow::mustache::context ctx;
ctx["name"] = "Special Directory";
auto page = crow::mustache::load("example.html");
return page.render(ctx);
});
app.port(18080).multithreaded().run();
}
默认情况下,所有路由都会使用 global_templates 目录来查找模板,也就是说会默认在global_template文件夹下查找;
但是 /special 路由会在 special_templates 目录中查找模板文件。
③在 Crow 中,crow::mustache::context是一个 JSON 对象,用于在模板中传递数据。可以将数据作为键值对添加到context中。
1.假设有一个模板文件 templates/example.html:
<p>{{greeting}}</p>
<p>{{custom_message}}</p>
2.#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/")
([]{
crow::mustache::context ctx; // crow::mustache::context 对象
ctx["greeting"] = "Hello, World!";
// 使用 Lambda 表达式来生成动态内容
ctx["custom_message"] = [](std::string name){
return "Welcome to Crow, " + name + "!";
};
auto page = crow::mustache::load("example.html");
return page.render(ctx); // Crow 模板系统中的一个方法,用于将模板与上下文数据结合在一起,并生成最终的 HTML 内容。
});
app.port(18080).multithreaded().run();
}
5.Multipart
①Multipart一种 HTTP 请求或响应的格式,允许在一次请求中包含多个不同的数据部分,这些部分可能有不同的数据类型。
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="text_field"
Hello, World!
------WebKitFormBoundary
Content-Disposition: form-data; name="file1"; filename="example1.txt"
Content-Type: text/plain //表示了该部分的内容类型为纯文本
(This is the content of example1.txt)
------WebKitFormBoundary
Content-Disposition: form-data; name="file2"; filename="example2.jpg"
Content-Type: image/jpeg //表示该部分的内容是JPEG格式的图片数据。
(binary content of example2.jpg)
------WebKitFormBoundary--
1.Header: Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
表示这是一个multipart 请求,boundary 是各部分的分隔符。
2.Content-Disposition:指定内容的展示方式。form-data 表示它是表单提交的数据。
3.name:指定表单字段的名称。在服务器端,可以使用这个名称来访问该字段的数据。
4.filename:指定上传文件的原始名称。
②在Crow中,可以使用 crow::multipart::message 和 crow::multipart::message_view 来处理 multipart 请求和响应。crow::multipart::message
负责管理消息的内容,而 crow::multipart::message_view
提供对各个部分的视图访问(也就是只读权限)。
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/upload").methods(crow::HTTPMethod::POST)([](const crow::request& req){
crow::multipart::message msg(req);
for (auto& part : msg.parts) {
std::string part_name = part.name;
std::string part_filename = part.filename;
std::string part_content = part.body; // 获取文件内容
}
return "Files received";
});
app.port(18080).multithreaded().run();
}
6.Query Strings(查询字符串,一般指的是url中"?"后的部分)
可以通过crow::request::url_params来处理查询字符串
get(name): 根据给定的键获取对应的值。如果找不到键,返回 nullptr。
pop(name): 与 get 类似,但获取值后会将其从查询字符串中移除。由于 url_params 是 const,你需要复制它才能使用 pop。
get_list(name): 获取一个键对应的多个值,返回一个 std::vector<std::string>。例如,处理 URL ?key[]=value1&key[]=value2。
pop_list(name): 与 get_list 类似,但会移除获取的所有值。
get_dict(name): 从查询字符串中获取键值对,返回一个 std::unordered_map<std::string, std::string>。适用于处理类似 ?key[sub_key]=value 的查询字符串。
pop_dict(name): 与 get_dict 类似,但会移除获取的键值对。
7.Middleware(中间件)
在 Crow 框架中,中间件是一种在处理客户端请求之前或之后执行代码的机制。你可以用它来在请求到达处理程序之前检查或修改请求内容,或者在响应发送回客户端之前做一些处理。
1.主要组成:
①Context(上下文):用于存储请求期间的临时数据
②before_handle():在请求处理之前执行,可以在这里进行设置默认值等操作。
③after_handle():在请求处理程序之后运行,通常用于修改响应或记录日志。
2. 工作原理:
①请求处理前 (before_handle
):
当服务器接收到请求时,Crow 首先会执行所有注册的 Middleware 的 before_handle
方法。可以在这里对请求进行预处理,比如检查用户身份、验证参数、记录日志等。
②路由处理:
请求通过所有 before_handle
方法后,会到达相应的路由处理函数,并生成 响应。
③响应处理后(after_handle
):
路由处理完毕后,Crow 会执行所有 Middleware 的 after_handle
方法。你可以在这 里对响应进行修改,如添加额外的响应头、记录响应日志等。
3.实例说明:
①基本处理:访问当前中间件的上下文:
假设开发一个博客应用,你希望在每次有人提交评论时,检查他们是否被标记为垃圾评论用户(比如以前的评论中过于频繁地使用某些词汇)。你可以使用中间件来实现:
struct SpamFilterMiddleware {
struct context {
bool is_spammer = false;
};
// 在处理请求之前,检查用户是否是垃圾评论者
void before_handle(crow::request& req, crow::response& res, context& ctx) {
if (req.url_params.get("user") == "spammer123") {
ctx.is_spammer = true;
res.code = 403;
res.end("You are banned from commenting.");
}
}
// 在处理请求之后记录日志
void after_handle(crow::request& req, crow::response& res, context& ctx) {
if (ctx.is_spammer) {
CROW_LOG_INFO << "Blocked a spammer: " << req.url_params.get("user");
}
}
};
// 注册中间件
crow::App<SpamFilterMiddleware> app;
②模板处理:访问其他中间件的上下文
假设有两个中间件,一个用于验证用户身份(AuthMiddleware),另一个用于记录用户行为(LoggingMiddleware)。在 LoggingMiddleware 中访问 AuthMiddleware 的上下文,以获取用户身份信息并记录相关日志。
struct AuthMiddleware {
struct context {
std::string user_id;
bool authenticated = false;
};
void before_handle(crow::request& req, crow::response& res, context& ctx) {
// 伪代码:检查用户是否登录
ctx.authenticated = true;
ctx.user_id = "user123"; // 假设用户已登录,并设置 user_id
}
};
struct LoggingMiddleware {
struct context {};
template <typename AllContext>
void before_handle(crow::request& req, crow::response& res, context& ctx, AllContext& all_ctx) {
auto& auth_ctx = all_ctx.template get<AuthMiddleware>();
if (auth_ctx.authenticated) {
CROW_LOG_INFO << "User " << auth_ctx.user_id << " is making a request.";
} else {
CROW_LOG_INFO << "Unauthenticated request.";
}
}
};
③并且可以在同一个APP中注册多个中间件。
crow::App<AuthMiddleware, LoggingMiddleware> app;
AuthMiddleware 和 LoggingMiddleware 会按照定义的顺序依次执行。LoggingMiddleware 可以通过模板签名访问 AuthMiddleware 的上下文,从而实现跨中间件的数据共享和处理。
④全局中间件(会对所有请求生效,无论请求的路径是什么。)和本地中间件(只会在访问 /local
路由时生效,而不会影响 /global
路由。)
#include "crow.h"
// 全局中间件
struct GlobalMiddleware {
struct context {};
void before_handle(crow::request& req, crow::response& res, context& ctx) {
CROW_LOG_INFO << "Global Middleware: Before handling request";
}
void after_handle(crow::request& req, crow::response& res, context& ctx) {
CROW_LOG_INFO << "Global Middleware: After handling request";
}
};
// 本地中间件
struct LocalMiddleware : public crow::ILocalMiddleware {
struct context {};
void before_handle(crow::request& req, crow::response& res, context& ctx) {
CROW_LOG_INFO << "Local Middleware: Before handling request";
}
void after_handle(crow::request& req, crow::response& res, context& ctx) {
CROW_LOG_INFO << "Local Middleware: After handling request";
}
};
int main() {
// 创建一个 Crow 应用并注册全局中间件
crow::App<GlobalMiddleware> app;
// 定义一个带有本地中间件的路由
CROW_ROUTE(app, "/local")
.CROW_MIDDLEWARES(app, LocalMiddleware)
([]() {
return "This route has local middleware!";
});
// 定义一个不带本地中间件的全局路由
CROW_ROUTE(app, "/global")
([]() {
return "This route only has global middleware!";
});
// 启动服务器
app.port(18080).multithreaded().run();
}
8.Blueprints(蓝图功能)(代码已经测试)
1.基本概念:一种类似 Flask 风格的功能,用于模块化管理 Crow 应用。它们不能独立处理网络请求,但可以定义路由,使得应用程序更易于组织和扩展,可以进行模块化开发。
2.基本使用方式:
①基本流程:定义Blueprint--->注册Blueprint
#include crow_all.h
int main() {
crow::SimpleApp app;
初始化蓝图
crow::Blueprint sub_bp("bp2", "csstat", "cstemplate");
// 在 Blueprint 中定义路由
CROW_BP_ROUTE(sub_bp, "/1")
([]() {
return "456";
});
// 在应用程序中定义路由
CROW_ROUTE(app, "/2")
([]() {
return "ASD";
});
// 注册蓝图到应用程序中
app.register_blueprint(sub_bp);
// 设置路由
app.port(18080).multithreaded().run();
}
注意事项: CROW_BP_ROUTE直接使用会报错,因为这种宏设计上需要在特定的函数或代码块中执行,通常是在初始化过程中或者在某个函数中定义蓝图时使用。应该将蓝图的路由配置放入一个函数和类中,并在程序调用该函数。确保蓝图实例正确构建,并且上下文完整。
②实现模块化的具体方式
//可以将不同的模块注册成蓝图,之后通过包含头文件来执行
项目结构
project/
│
├── main.cpp 主执行
├── blue1.h 蓝图模块1
├── blue1.cpp
├── blue2.h 蓝图模块2
├── blue2t.cpp
//blue1.h文件 蓝图模块1
#pragma once
#include "crow_all.h"
class Blue1 {
public:
crow::Blueprint bp;
Blue1();
};
//blue1.cpp文件
#include "blue1.h"
//用类去封装独立的蓝图
Blue1::Blue1() : bp("test1") {
CROW_BP_ROUTE(bp, "/1")([] {
return "蓝图模块1.1";
});
CROW_BP_ROUTE(bp, "/2")([] {
return "蓝图模块1.2";
});
}
//blue2.h 蓝图模块2
#pragma once
#include "crow_all.h"
class Blue2 {
public:
crow::Blueprint bp;
Blue2();
};
/blue2.cpp
#include "blue2.h"
Blue2::Blue2() : bp("test2") {
CROW_BP_ROUTE(bp, "/3")([] {
return "蓝图模块2.1";
});
CROW_BP_ROUTE(bp, "/4")([] {
return "蓝图模块2.2";
});
}
/main.cpp
#include "crow_all.h"
#include "blue1.h"
#include "blue2.h"
int main()
{
crow::SimpleApp app;
Blue1 blue1;
Blue2 blue2;
app.register_blueprint(blue1.bp);
app.register_blueprint(blue2.bp);
app.port(18080).multithreaded().run();
}
③蓝图可以有自己的静态目录和模板目录
#include "crow.h"
crow::Blueprint user_bp("user", "user_static", "user_templates");
其中,静态文件:user_bp Blueprint 会从 user_static 目录中查找静态文件
模板文件:user_bp Blueprint 会从 user_templates 目录加载模板文件。
int main() {
CROW_BP_ROUTE(user_bp, "/profile")
([]() {
return crow::mustache::load("profile.html").render();
});
crow::SimpleApp app;
app.register_blueprint(user_bp);
app.port(18080).multithreaded().run();
}
如果你不指定的话,就会默认设定静态模板目录为static,html文件目录为templates,和非蓝图路由一致。
④可以在蓝图中定义捕获所有路由,处理未匹配的请求
CROW_BP_CATCHALL_ROUTE(parent_bp)
([](const crow::request& req) {
return "Parent Blueprint Catchall Route";
});
当请求路径 /parent/unknown_path 无法匹配时,捕获所有路由会被触发,返回 "Parent Blueprint Catchall Route"。
⑤ 蓝图可以实现嵌套:可以在一个 Blueprint 中注册另一个 Blueprint。这样,子 Blueprint 的路由会继承父 Blueprint 的前缀。例如,如果父 Blueprint 的前缀是 /parent
,子 Blueprint 的前缀是 /sub
,那么最终的路由会是 /parent/sub/route_path
。
#include crow_all.h
int main() {
crow::Blueprint order_bp("order");
CROW_BP_ROUTE(order_bp, "/status")
([]() {
return "Order Status Page";
});
// 定义产品管理的父 Blueprint,并注册订单管理 Blueprint
crow::Blueprint product_bp("product");
CROW_BP_ROUTE(product_bp, "/list")
([]() {
return "Product List Page";
});
product_bp.register_blueprint(order_bp);
crow::SimpleApp app;
// 注册产品管理 Blueprint
app.register_blueprint(product_bp);
app.port(18080).multithreaded().run();
}
//访问形式:http://localhost:18080/product/order/status 子蓝图
9.Websockets(双通)
① 启用websocket时定义路由的方式
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_WEBSOCKET_ROUTE(app, "/ws")
.onaccept([&](const crow::request& req, void** userdata) {
return true; // 接受 WebSocket 连接
})
.onopen([&](crow::websocket::connection& conn) {
CROW_LOG_INFO << "WebSocket 连接已打开";
})
.onmessage([&](crow::websocket::connection& conn, const std::string& message, bool is_binary) {
CROW_LOG_INFO << "收到消息: " << message;
conn.send_text("收到的消息: " + message); // 回显消息
})
.onerror([&](crow::websocket::connection& conn, const std::string& error_message) {
CROW_LOG_ERROR << "WebSocket 错误: " << error_message;
})
.onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t status_code) {
CROW_LOG_INFO << "WebSocket 关闭, 原因: " << reason << ", 状态码: " << status_code;
});
app.port(18080).multithreaded().run();
}
Using CROW
1.Included Middlewares(包含中间件) (已测试)
在Crow中提供几种可以直接使用的中间件文件
①session会话中间件(在Crow-master\examples\middlewares中可以找到他的cpp文件)
int main() {
// Define the session middleware using an in-memory store
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
crow::App<crow::CookieParser, Session> app{ Session() };
// 设置键值对
CROW_ROUTE(app, "/set")
([&](const crow::request& req) {
auto& session = app.get_context<Session>(req);
session.set("username", "admin");
return "Username set to admin";
});
// 通过key获取值
CROW_ROUTE(app, "/get")
([&](const crow::request& req) {
auto& session = app.get_context<Session>(req);
std::string username = session.get("username", "Not found");
return "Username: " + username;
});
//删除
// Remove a value from the session
CROW_ROUTE(app, "/remove")
([&](const crow::request& req) {
auto& session = app.get_context<Session>(req);
session.remove("username");
return "Username removed";
});
// Start the server
app.port(18080).multithreaded().run();
return 0;
}
1.http://localhost:18080/set?key=username&value=admin。
设置键 username 为值 admin。
2.http://localhost:18080/get?key=username。
返回之前设置的值 "admin"。
3.http://localhost:18080/remove?key=username。
删除 username 键
②cookie中间件-CookieParser
中间件用于读取和写入 cookies。当启用这个中间件后,它会解析所有传入的 cookies。
int main()
{
crow::App<crow::CookieParser> app;
const std::string valid_username = "operator";
const std::string valid_password = "securepass";
CROW_ROUTE(app, "/login")
([&](const crow::request& req, crow::response& res) {
std::string username = req.url_params.get("username") ? req.url_params.get("username") : "";
std::string password = req.url_params.get("password") ? req.url_params.get("password") : "";
if (username == valid_username && password == valid_password) {
auto& ctx = app.get_context<crow::CookieParser>(req);
ctx.set_cookie("authenticated", "true")
.path("/")
.max_age(3600); // 有效期 1 小时
res.write("Login successful!");
}
else {
res.code = 401; // 未授权
res.write("Invalid username or password.");
}
res.end();
});
CROW_ROUTE(app, "/protected")
([&](const crow::request& req, crow::response& res) {
auto& ctx = app.get_context<crow::CookieParser>(req);
std::string auth = ctx.get_cookie("authenticated");
if (auth == "true") {
res.write("Welcome to the protected area!");
}
else {
res.code = 403; // 禁止访问
res.write("Access denied. Please log in first.");
}
res.end();
});
// 退出登录,清除认证 Cookie
CROW_ROUTE(app, "/logout")
([&](const crow::request& req, crow::response& res) {
auto& ctx = app.get_context<crow::CookieParser>(req);
ctx.set_cookie("authenticated", "")
.path("/")
.max_age(-1); // 删除 Cookie
res.write("Logged out successfully.");
res.end();
});
app.port(18080).multithreaded().run();
return 0;
}
1.访问 http://localhost:18080/login?username=operator&password=securepass 登录。
2.访问 http://localhost:18080/protected 查看受保护内容,如果已登录成功,显示欢迎信息,否则,会显示“拒绝访问”。
3.访问 http://localhost:18080/logout 退出登录。
③cors(用于跨域访问的)