wfrest框架文档
wfrest是一个快速🚀, 高效⌛️, 简单易用的💥 c++ 异步web框架.
wfrest基于✨C++ Workflow✨开发. C++ Workflow 是一个设计轻盈优雅的企业级程序引擎.
简介
你可以用来:
- 快速搭建http服务器:
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.GET("/hello", [](const HttpReq *req, HttpResp *resp)
{
resp->String("world\n");
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
使用中有疑问?
可以先查看Discussions和issues列表,看看是否能找到答案。
非常欢迎将您使用中遇到的问题发送到issues。
也可以通过QQ群:884394197 联系我们。
编译 && 安装
需求
- workflow, 版本大于等于 v0.9.9
- Linux , 比如ubuntu 18.04 或者更新版本
- Cmake
- zlib1g-dev
- libssl-dev
- libgtest-dev
- gcc 和 g++ 或者 llvm + clang
如果你在ubuntu 20.04,你可以用以下命令安装
apt-get install build-essential cmake zlib1g-dev libssl-dev libgtest-dev -y
cmake
git clone --recursive https://github.com/wfrest/wfrest
cd wfrest
make
sudo make install
编译例子:
make example
测试:
make check
Docker
docker build -t wfrest ./docker/ubuntu/
如果你用podman
podman build -t wfrest ./docker/ubuntu/
你也可以从dockerhub中拖拉镜像
docker pull wfrest/wfrest
API示例
路由中的参数
路径中的参数
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 此处理程序将匹配 /user/chanchan 但不会匹配 /user/ 或 /user
// curl -v "ip:port/user/chanchan/"
svr.GET("/user/{name}", [](const HttpReq *req, HttpResp *resp)
{
// 引用:不复制
const std::string& name = req->param("name");
// resp->set_status(HttpStatusOK); // 自动设置
resp->String("你好 " + name + "\n");
});
// wildcast/chanchan/action... (前缀)
svr.GET("/wildcast/{name}/action*", [](const HttpReq *req, HttpResp *resp)
{
const std::string& name = req->param("name");
const std::string& match_path = req->match_path();
resp->String("[name : " + name + "] [match path : " + match_path + "]\n");
});
// 请求将保存路由定义
svr.GET("/user/{name}/match*", [](const HttpReq *req, HttpResp *resp)
{
// 如果 /user/chanchan/match1234
// full_path : /user/{name}/match*
// current_path : /user/chanchan/match1234
// match_path : match1234
const std::string &full_path = req->full_path();
const std::string ¤t_path = req->current_path();
std::string res;
if (full_path == "/user/{name}/match*")
{
res = full_path + " 匹配 : " + current_path;
} else
{
res = full_path + " 不匹配";
}
resp->String(res);
});
// 此处理程序将为 /user/groups 添加一个新路由。
// 无论定义的顺序如何,精确路由都会在参数路由之前解析。
// 以 /user/groups 开头的路由永远不会被解释为 /user/{name}/... 路由
svr.GET("/user/groups", [](const HttpReq *req, HttpResp *resp)
{
resp->String(req->full_path());
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
url请求参数
查询字符串参数
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 请求响应匹配的URL:/query_list?username=chanchann&password=yyy
svr.GET("/query_list", [](const HttpReq *req, HttpResp *resp)
{
const std::map<std::string, std::string>& query_list = req->query_list();
for (auto &query: query_list)
{
fprintf(stderr, "%s : %s\n", query.first.c_str(), query.second.c_str());
}
});
// 请求响应匹配的URL:/query?username=chanchann&password=yyy
svr.GET("/query", [](const HttpReq *req, HttpResp *resp)
{
const std::string& user_name = req->query("username");
const std::string& password = req->query("password");
const std::string& info = req->query("info"); // 没有这个字段
const std::string& address = req->default_query("address", "china");
resp->String(user_name + " " + password + " " + info + " " + address + "\n");
});
// 请求响应匹配的URL:/query_has?username=chanchann&password=
// 判断参数是否存在的逻辑是,如果参数值为空,则认为参数存在
// 只有参数未提交时才认为参数不存在
svr.GET("/query_has", [](const HttpReq *req, HttpResp *resp)
{
if (req->has_query("password"))
{
fprintf(stderr, "存在password查询参数\n");
}
if (req->has_query("info"))
{
fprintf(stderr, "存在info查询参数\n");
}
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
表单数据
表单提交
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// URL编码表单
// curl -v http://ip:port/post \
// -H "body-type:application/x-www-form-urlencoded" \
// -d 'user=admin&pswd=123456'
svr.POST("/post", [](const HttpReq *req, HttpResp *resp)
{
if (req->content_type() != APPLICATION_URLENCODED)
{
resp->set_status(HttpStatusBadRequest);
return;
}
std::map<std::string, std::string> &form_kv = req->form_kv();
for (auto &kv: form_kv)
{
fprintf(stderr, "键 %s : 值 %s\n", kv.first.c_str(), kv.second.c_str());
}
});
// curl -X POST http://ip:port/form \
// -F "file=@/path/file" \
// -H "Content-Type: multipart/form-data"
svr.POST("/form", [](const HttpReq *req, HttpResp *resp)
{
if (req->content_type() != MULTIPART_FORM_DATA)
{
resp->set_status(HttpStatusBadRequest);
return;
}
/*
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
// <name ,<filename, body>>
using Form = std::map<std::string, std::pair<std::string, std::string>>;
*/
const Form &form = req->form();
for (auto &it: form)
{
auto &name = it.first;
auto &file_info = it.second;
fprintf(stderr, "%s : %s = %s",
name.c_str(),
file_info.first.c_str(),
file_info.second.c_str());
}
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
多部分表单编码器
使用MultiPartEncoder来编码多部分数据格式内容并发送。
svr.GET("/form_send", [](const HttpReq *req, HttpResp *resp)
{
MultiPartEncoder encoder;
encoder.add_param("Filename", "1.jpg");
encoder.add_file("test_1.txt", "./www/test_1.txt");
encoder.add_file("test_2.txt", "./www/test_2.txt");
resp->String(std::move(encoder));
});
HTTP头部字段
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.POST("/post", [](const HttpReq *req, HttpResp *resp)
{
const std::string& host = req->header("Host");
const std::string& content_type = req->header("Content-Type");
if (req->has_header("User-Agent"))
{
fprintf(stderr, "存在User-Agent...");
}
resp->String(host + " " + content_type + "\n");
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
发送文件
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 单个文件
svr.GET("/file1", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt");
});
svr.GET("/file2", [](const HttpReq *req, HttpResp *resp)
{
resp->File("html/index.html");
});
svr.GET("/file3", [](const HttpReq *req, HttpResp *resp)
{
resp->File("/html/index.html");
});
svr.GET("/file4", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt", 0);
});
svr.GET("/file5", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt", 0, 10);
});
svr.GET("/file6", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt", 5, 10);
});
svr.GET("/file7", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt", 5, -1);
});
svr.GET("/file8", [](const HttpReq *req, HttpResp *resp)
{
resp->File("todo.txt", -5, -1);
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
保存文件
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// curl -v -X POST "ip:port/file_write1" -F "file=@filename" -H "Content-Type: multipart/form-data"
svr.POST("/file_write1", [](const HttpReq *req, HttpResp *resp)
{
std::string& body = req->body(); // multipart/form - body有边界
resp->Save("test.txt", std::move(body));
});
svr.GET("/file_write2", [](const HttpReq *req, HttpResp *resp)
{
std::string content = "1234567890987654321";
resp->Save("test1.txt", std::move(content));
});
// 您可以指定保存文件成功时返回给客户端的消息
// 默认是 "Save File success\n"
svr.GET("/file_write3", [](const HttpReq *req, HttpResp *resp)
{
std::string content = "1234567890987654321";
resp->Save("test2.txt", std::move(content), "测试通知测试成功");
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
上传文件
#include "wfrest/HttpServer.h"
#include "wfrest/PathUtil.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 将文件上传到父目录是非常危险的:
// curl -v -X POST "ip:port/upload" -F "file=@demo.txt; filename=../demo.txt" -H "Content-Type: multipart/form-data"
// 然后你会发现文件被存储在父目录中,这是危险的
svr.POST("/upload", [](const HttpReq *req, HttpResp *resp)
{
Form &form = req->form();
if (form.empty())
{
resp->set_status(HttpStatusBadRequest);
} else
{
for(auto& part : form)
{
const std::string& name = part.first;
// 文件名 : 文件内容
std::pair<std::string, std::string>& fileinfo = part.second;
// file->filename 不应该被信任。参见MDN上的Content-Disposition
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#directives
// 文件名始终是可选的,不应该被应用程序盲目使用:
// 应该去除路径信息,并转换为服务器文件系统规则。
if(fileinfo.first.empty())
{
continue;
}
fprintf(stderr, "文件名 : %s\n", fileinfo.first.c_str());
resp->Save(PathUtil::base(fileinfo.first), std::move(fileinfo.second));
}
}
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
内置json (wfrest::Json) 用法
我们提供内置的json,强烈推荐使用。
如果您想了解更多json API用法,可以查看 json api
#include "wfrest/HttpServer.h"
#include "wfrest/Json.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// curl -v http://ip:port/json1
svr.GET("/json1", [](const HttpReq *req, HttpResp *resp)
{
Json json;
json["test"] = 123;
json["json"] = "test json";
resp->Json(json);
});
// 接收json
// curl -X POST http://ip:port/json2
// -H 'Content-Type: application/json'
// -d '{"login":"my_login","password":"my_password"}'
svr.POST("/json2", [](const HttpReq *req, HttpResp *resp)
{
if (req->content_type() != APPLICATION_JSON)
{
resp->String("不是APPLICATION_JSON");
return;
}
fprintf(stderr, "Json : %s", req->json().dump(4).c_str());
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
计算型Handler
#include "wfrest/HttpServer.h"
using namespace wfrest;
void Fibonacci(int n, HttpResp *resp)
{
unsigned long long x = 0, y = 1;
if (n <= 0 || n > 94)
{
fprintf(stderr, "invalid parameter");
return;
}
for (int i = 2; i < n; i++)
{
y = x + y;
x = y - x;
}
if (n == 1)
y = 0;
resp->String("fib(" + std::to_string(n) + ") is : " + std::to_string(y) + "\n");
}
int main()
{
HttpServer svr;
// 第二个参数表示此计算队列ID为1
// 然后这个处理器成为一个计算任务
// curl -v http://ip:port/compute_task?num=20
svr.GET("/compute_task", 1, [](const HttpReq *req, HttpResp *resp)
{
int num = std::stoi(req->query("num"));
Fibonacci(num, resp);
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
计算任务接口
如果您想启动一个计算任务,只需使用resp->Compute()
#include "wfrest/HttpServer.h"
using namespace wfrest;
void Factorial(int n, HttpResp *resp)
{
unsigned long long factorial = 1;
for(int i = 1; i <= n; i++)
{
factorial *= i;
}
resp->String("fac(" + std::to_string(n) + ") = " + std::to_string(factorial));
}
int main()
{
HttpServer svr;
svr.GET("/compute", [](const HttpReq *req, HttpResp *resp)
{
// 第一个参数是队列ID
resp->Compute(1, Factorial, 10, resp);
});
if (svr.track().start(8888) == 0)
{
svr.list_routes();
wait_group.wait();
getchar();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
系列型Handler
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.GET("/series", [](const HttpReq *req, HttpResp *resp, SeriesWork* series)
{
auto *timer = WFTaskFactory::create_timer_task(5000000, [](WFTimerTask *) {
printf("定时器任务完成(5秒)。\n");
});
series->push_back(timer);
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
压缩算法
// 服务器
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// 1. 您可以使用 `./13_compess_client`
// 2. 或者使用Python脚本 `python3 13_compress_client.py`
// 3. echo '{"testgzip": "gzip compress data"}' | gzip | \
// curl -v -i --data-binary @- -H "Content-Encoding: gzip" http://ip:port/gzip
svr.POST("/gzip", [](const HttpReq *req, HttpResp *resp)
{
// 我们自动解压从客户端发送的压缩数据
// 目前仅支持gzip和br格式
std::string& data = req->body();
fprintf(stderr, "ungzip data : %s\n", data.c_str());
resp->set_compress(Compress::GZIP);
resp->String("测试服务器发送gzip数据\n");
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
// 客户端
#include "workflow/WFTaskFactory.h"
#include "wfrest/Compress.h"
#include "wfrest/ErrorCode.h"
using namespace protocol;
using namespace wfrest;
struct CompessContext
{
std::string data;
};
void http_callback(WFHttpTask *task)
{
const void *body;
size_t body_len;
task->get_resp()->get_parsed_body(&body, &body_len);
std::string decompress_data;
int ret = Compressor::ungzip(static_cast<const char *>(body), body_len, &decompress_data);
fprintf(stderr, "解压数据 : %s", decompress_data.c_str());
delete static_cast<CompessContext *>(task->user_data);
}
int main()
{
signal(SIGINT, sig_handler);
std::string url = "http://127.0.0.1:8888";
WFHttpTask *task = WFTaskFactory::create_http_task(url + "/gzip",
4,
2,
http_callback);
std::string content = "客户端发送测试Gzip";
auto *ctx = new CompessContext;
int ret = Compressor::gzip(&content, &ctx->data);
if(ret != StatusOK)
{
ctx->data = std::move(content);
}
task->user_data = ctx;
task->get_req()->set_method("POST");
task->get_req()->add_header_pair("Content-Encoding", "gzip");
task->get_req()->append_output_body_nocopy(ctx->data.c_str(), ctx->data.size());
task->start();
getchar();
}
关于如何编写客户端,您可以参考workflow,它目前支持HTTP、Redis、MySQL和Kafka协议。您可以使用它来编写高效的异步客户端。
蓝图(BluePrint)
wfrest支持Flask风格的蓝图。
您可以参考 Flask蓝图到底是什么?
蓝图是一个有限制的wfrest服务器。它不能处理网络通信,但可以处理路由。
对于更大的项目,您的所有代码不应该在同一个文件中。相反,您可以将较大的代码分割或拆分到不同的文件中,这使您的项目更具模块化。
#include "wfrest/HttpServer.h"
using namespace wfrest;
// 您可以将不同的业务逻辑拆分到不同的文件/模块中
void set_admin_bp(BluePrint &bp)
{
bp.GET("/page/new/", [](const HttpReq *req, HttpResp *resp)
{
fprintf(stderr, "New page\n");
});
bp.GET("/page/edit/", [](const HttpReq *req, HttpResp *resp)
{
fprintf(stderr, "Edit page\n");
});
}
int main()
{
HttpServer svr;
svr.POST("/page/{uri}", [](const HttpReq *req, HttpResp *resp)
{
fprintf(stderr, "Blog Page\n");
});
BluePrint admin_bp;
set_admin_bp(admin_bp);
svr.register_blueprint(admin_bp, "/admin");
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
静态文件服务
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.Static("/static", "./www/static");
svr.Static("/public", "./www");
svr.Static("/", "./www/index.html");
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
Cookie
以下是设置和获取cookie的示例。
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// curl --cookie "name=chanchan, passwd=123" "http://ip:port/cookie"
svr.GET("/cookie", [](const HttpReq *req, HttpResp *resp)
{
const std::map<std::string, std::string> &cookie = req->cookies();
if(cookie.empty()) // 没有cookie
{
HttpCookie cookie;
// 您可以设置的内容:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
cookie.set_path("/").set_http_only(true);
resp->add_cookie(std::move(cookie));
resp->String("设置Cookie\n");
}
fprintf(stderr, "cookie :\n");
for(auto &c : cookie)
{
fprintf(stderr, "%s : %s\n", c.first.c_str(), c.second.c_str());
}
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
以下是一个更具体的示例,您可以查看教程
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.GET("/login", [](const HttpReq *req, HttpResp *resp)
{
HttpCookie cookie;
cookie.set_key("user")
.set_value("chanchan")
.set_path("/")
.set_domain("localhost")
.set_http_only(true);
resp->add_cookie(std::move(cookie));
resp->String("登录成功");
});
svr.GET("/home", [](const HttpReq *req, HttpResp *resp)
{
const std::string &cookie_val = req->cookie("user");
if(cookie_val != "chanchan")
{
resp->set_status(HttpStatusUnauthorized);
std::string err = R"(
{
"error": "未授权"
}
)";
resp->Json(err);
} else
{
std::string success = R"(
{
"home": "data"
}
)";
resp->Json(success);
}
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
如何向用户代理设置多个cookie?
参考:https://www.rfc-editor.org/rfc/rfc6265#section-5.4
如下一个例子所示,服务器可以在用户代理上存储多个cookie。 例如,服务器可以通过返回两个Set-Cookie头字段来存储会话标识符以及用户的首选语言。 请注意,服务器使用Secure和HttpOnly属性为更敏感的会话标识符提供额外的安全保护(参见第4.1.2节)。
== 服务器 -> 用户代理 ==
Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly Set-Cookie: lang=en-US; Path=/; Domain=example.com
你可以这样编写代码:
svr.GET("/multi", [](const HttpReq *req, HttpResp *resp)
{
// 设置cookie
HttpCookie cookie;
cookie.set_key("user")
.set_value("chanchan")
.set_path("/")
.set_domain("localhost")
.set_http_only(true);
resp->add_cookie(std::move(cookie));
HttpCookie cookie2;
cookie2.set_key("animal")
.set_value("panda");
resp->add_cookie(std::move(cookie2));
resp->set_status(HttpStatusOK);
resp->String("登录成功");
});
自定义服务器配置
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
svr.GET("/config", [](const HttpReq *req, HttpResp *resp)
{
resp->String("config");
});
if (svr.max_connections(4000)
.peer_response_timeout(20 * 1000)
.keep_alive_timeout(30 * 1000)
.track()
.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
配置项
配置项如下:
struct WFServerParams
{
size_t max_connections;
int peer_response_timeout; /* 每个读写操作的超时时间 */
int receive_timeout; /* 接收整个消息的超时时间 */
int keep_alive_timeout;
size_t request_size_limit;
int ssl_accept_timeout; /* 如果不是ssl,这将被忽略 */
};
默认值为:
static constexpr struct WFServerParams SERVER_PARAMS_DEFAULT =
{
.max_connections = 2000,
.peer_response_timeout = 10 * 1000,
.receive_timeout = -1,
.keep_alive_timeout = 60 * 1000,
.request_size_limit = (size_t)-1,
.ssl_accept_timeout = 10 * 1000,
};
跟踪
您可以打开跟踪日志。
格式:
[WFREST] 2022-01-13 18:00:04 | 200 | 127.0.0.1 | GET | "/data" | --
[WFREST] 2022-01-13 18:00:08 | 200 | 127.0.0.1 | GET | "/hello" | --
[WFREST] 2022-01-13 18:00:17 | 404 | 127.0.0.1 | GET | "/hello1" | --
您还可以设置自己的跟踪记录器:
svr.track([](HttpTask *server_task){
spdlog::info(...);
// BOOST_LOG_TRIVIAL(info) << "Status : ";
// LOG(ERROR) << "time : " << time;
...
});
面向切面编程
在计算机领域,面向切面编程(AOP)是一种旨在通过允许分离横切关注点来增加模块化的编程范式。
有关更多信息,您可以查看 什么是AOP,
#include "wfrest/HttpServer.h"
#include "wfrest/Aspect.h"
using namespace wfrest;
// 日志切面
struct LogAop : public Aspect
{
bool before(const HttpReq *req, HttpResp *resp) override
{
fprintf(stderr, "before \n");
return true;
}
// 'after()' 应该在回复后被调用
bool after(const HttpReq *req, HttpResp *resp) override
{
fprintf(stderr, "After log\n");
return true;
}
};
int main()
{
HttpServer svr;
svr.GET("/aop", [](const HttpReq *req, HttpResp *resp)
{
resp->String("aop");
}, LogAop());
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
从切面传输数据到HTTP处理器:
#include "wfrest/HttpServer.h"
#include "wfrest/Aspect.h"
using namespace wfrest;
struct TransferAop : public Aspect
{
bool before(const HttpReq *req, HttpResp *resp) override
{
auto *content = new std::string("transfer data");
resp->user_data = content;
return true;
}
// 如果需要删除resp的'user_data',在'after()'中删除它。
bool after(const HttpReq *req, HttpResp *resp) override
{
fprintf(stderr, "state : %d\terror : %d\n",
resp->get_state(), resp->get_error());
delete static_cast<std::string *>(resp->user_data);
return true;
}
};
int main()
{
HttpServer svr;
svr.GET("/aop", [](const HttpReq *req, HttpResp *resp)
{
auto *content = static_cast<std::string *>(resp->user_data);
resp->String(std::move(*content));
}, TransferAop());
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
全局切面
注册全局切面的方式如下:
svr.Use(FirstAop());
svr.Use(SecondAop(), ThirdAop());
Https 服务器
设置Https服务器非常简单。
http和https之间的唯一区别是https需要您提供SSL密钥的路径和SSL证书的路径。
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main(int argc, char **argv)
{
// 在cert文件中
// 执行sudo ./gen.sh 生成crt / key文件
if (argc != 3)
{
fprintf(stderr, "%s [cert file] [key file]\n",
argv[0]);
exit(1);
}
HttpServer svr;
svr.GET("/https", [](const HttpReq *req, HttpResp *resp)
{
resp->String("测试Https\n");
});
if (svr.start(8888, argv[1], argv[2]) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server\n");
exit(1);
}
return 0;
}
代理服务
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main()
{
HttpServer svr;
// curl -v http://ip:port/proxy
svr.GET("/proxy", [](const HttpReq *req, HttpResp *resp)
{
resp->Http("http://www.baidu.com");
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server");
exit(1);
}
return 0;
}
MySQL
#include "workflow/MySQLResult.h"
#include "wfrest/HttpServer.h"
using namespace wfrest;
using namespace protocol;
int main(int argc, char **argv)
{
HttpServer svr;
svr.GET("/mysql00", [](const HttpReq *req, HttpResp *resp)
{
resp->MySQL("mysql://root:111111@localhost", "SHOW DATABASES");
});
svr.GET("/mysql01", [](const HttpReq *req, HttpResp *resp)
{
std::string url = "mysql://root:111111@localhost";
resp->MySQL(url, "SHOW DATABASES", [resp](Json *json)
{
Json js;
js["rows"] = (*json)["result_set"][0]["rows"];
resp->String(js.dump());
});
});
svr.GET("/mysql02", [](const HttpReq *req, HttpResp *resp)
{
std::string url = "mysql://root:111111@localhost";
resp->MySQL(url, "SHOW DATABASES", [resp](MySQLResultCursor *cursor)
{
std::string res;
std::vector<MySQLCell> arr;
while (cursor->fetch_row(arr))
{
res.append(arr[0].as_string());
res.append("\n");
}
resp->String(std::move(res));
});
});
svr.GET("/multi", [](const HttpReq *req, HttpResp *resp)
{
resp->MySQL("mysql://root:111111@localhost/wfrest_test", "SHOW DATABASES; SELECT * FROM wfrest");
});
svr.POST("/client", [](const HttpReq *req, HttpResp *resp)
{
std::string &sql = req->body();
resp->MySQL("mysql://root:111111@localhost/wfrest_test", std::move(sql));
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server\n");
exit(1);
}
return 0;
}
Redis
#include "wfrest/HttpServer.h"
using namespace wfrest;
int main(int argc, char **argv)
{
HttpServer svr;
svr.GET("/redis0", [](const HttpReq *req, HttpResp *resp)
{
resp->Redis("redis://127.0.0.1/", "SET", {"test_val", "test_key"});
});
svr.GET("/redis1", [](const HttpReq *req, HttpResp *resp)
{
resp->Redis("redis://127.0.0.1/", "GET", {"test_val"});
});
if (svr.start(8888) == 0)
{
getchar();
svr.stop();
} else
{
fprintf(stderr, "Cannot start server\n");
exit(1);
}
return 0;
}
默认返回格式为json。
Redis URL格式
redis://:password@host:port/dbnum?query#fragment
如果使用SSL,请使用:
rediss://:password@host:port/dbnum?query#fragment
密码是可选的。默认端口是6379;默认dbnum是0,其范围是0到15。 query和fragment在工厂中不使用,您可以自己定义它们。例如,如果您想使用上游选择,可以定义自己的query和fragment。有关详细信息,请参阅上游文档。 Redis URL示例:
redis://127.0.0.1/
redis://:12345678@redis.some-host.com/1
json api
To create a json object by reading a JSON file:
You can use c++ file stream:
#include "Json.h"
#include <fstream>
using namespace wfrest;
std::ifstream f("example.json");
Json data = Json::parse(f);
Or you can use FILE*
FILE* fp = fopen("example.json", "r");
Json data = Json::parse(fp);
fclose(fp);
Creating json objects from string
Using (raw) string literals and json::parse
Json data = Json::parse(R"(
{
"pi": 3.141,
"happy": true
}
)");
Creating json objects by initializer list
Json data = Json::Object{
{"null", nullptr},
{"integer", 1},
{"float", 1.3},
{"boolean", true},
{"string", "something"},
{"array", Json::Array{1, 2}},
{"object",
Json::Object{
{"key", "value"},
{"key2", "value2"},
}},
};
Create simple json value
Json null1 = nullptr;
Json num1 = 1;
Json num2 = 1.0;
Json bool1 = true;
Json bool2 = false;
Json str1 = "string";
Json obj1 = Json::Object();
Json arr1 = Json::Array();
Explict declare Json type
If you want to be explicit or express the json type is array or object, the functions Json::Array and Json::Object will help:
Json empty_object_explicit = Json::Object();
Json empty_array_explicit = Json::Array();
Parse and dump
Json js = Json::parse(R"({"test1":false})");
Json data;
data["key"]["chanchan"] = 1;
// default compact json string
{"key":{"chanchan":1}}
// using member function
std::string dump_str = data.dump();
// streams
std::ostringstream os;
os << data;
std::cout << os.str() << std::endl;
// For pretty stringification, there is the option to choose the identation size in number of spaces:
{
"key": {
"chanchan": 1
}
}
std::string dump_str_pretty = data.dump(2);
STL-like access
Example JSON arrays
- Create an array using push_back
Json data;
data.push_back(3.141);
data.push_back(true);
data.push_back("chanchan");
data.push_back(nullptr);
Json arr;
arr.push_back(42);
arr.push_back("answer");
data.push_back(arr);
- iterate the array
for (Json::iterator it = data.begin(); it != data.end(); it++)
{
std::cout << it->value() << std::endl;
}
for (auto it = data.begin(); it != data.end(); ++it)
{
std::cout << *it << std::endl;
}
for (const auto& it : data)
{
std::cout << it << std::endl;
}
-iterate the array reversely
for (Json::reverse_iterator it = data.rbegin(); it != data.rend(); it++)
{
std::cout << it->value() << std::endl;
}
- access by using operator[index]
Json data;
data.push_back(1); // 0
data.push_back(2.1); // 1
data.push_back(nullptr); // 2
data.push_back("string"); // 3
data.push_back(true); // 4
data.push_back(false); // 5
// You can user index to access the array element
int num_int = data[0].get<int>();
double num_double = data[1].get<double>();
std::nullptr_t null = data[2].get<std::nullptr_t>();
std::string str = data[3].get<std::string>();
bool bool_true = data[4].get<bool>();
bool bool_false = data[5].get<bool>();
// implicit conversion
int num_int = data[0];
double num_double = data[1];
std::nullptr_t null = data[2];
std::string str = data[3];
bool bool_true = data[4];
bool bool_false = data[5];
// Object
Json::Object obj;
obj["123"] = 12;
obj["123"]["1"] = "test";
data.push_back(obj); // 6
Json::Object obj1 = data[6].get<Json::Object>();
// implicit conversion
Json::Object obj2 = data[6];
// Array
Json::Array arr;
arr.push_back(1);
arr.push_back(nullptr);
data.push_back(arr);
Json::Array arr1 = data[7].get<Json::Array>();
// implicit conversion
Json::Array arr2 = data[7];
- Update element
arr[1] = true; // update method
- Erase element
arr.erase(1);
Example JSON objects
- use push_back
Json data;
data.push_back("pi", 3.141);
data.push_back("happy", true);
data.push_back("name", "chanchan");
data.push_back("nothing", nullptr);
Json answer;
answer.push_back("everything", 42);
data.push_back("answer", answer);
- use operator[]
The JSON values can be constructed (comfortably) by using standard index operators:
Use operator[] to assign values to JSON objects:
// create an empty structure (null)
Json data;
std::cout << "empty structure is " << data << std::endl;
// add a number that is stored as double (note the implicit conversion of j
// to an object)
data["pi"] = 3.141;
// add a Boolean that is stored as bool
data["happy"] = true;
// add a string that is stored as std::string
data["name"] = "chanchan";
// add another null object by passing nullptr
data["nothing"] = nullptr;
// add an object inside the object
data["answer"]["everything"] = 42;
- Update element
data["key"] = 1;
data["key"] = 2.0;
- Erase element
data.erase("key");
- iterate object elements
for (Json::iterator it = data.begin(); it != data.end(); it++)
{
std::cout << it->key() << " : " << it->value() << std::endl;
}
for (auto it = data.begin(); it != data.end(); it++)
{
// equal to it->value()
std::cout << *it << std::endl;
}
for (const auto& it : data)
{
std::cout << it.key() << " : " << it.value() << std::endl;
}
- iterate object elements reversely
for (Json::reverse_iterator it = data.rbegin(); it != data.rend(); it++)
{
std::cout << it->key() << " : " << it->value() << std::endl;
}