diff --git a/README.md b/README.md index 14d4a19d..eec5eaaa 100644 --- a/README.md +++ b/README.md @@ -75,74 +75,164 @@ cmake -DENABLE_SIMD=AARCH64 .. # arm环境下,启用neon指令集 # 快速示例 ## 示例1:一个简单的hello world - - #include "cinatra.hpp" +```c++ + #include "include/cinatra.hpp" using namespace cinatra; int main() { int max_thread_num = std::thread::hardware_concurrency(); - http_server server(max_thread_num); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/", [](request& req, response& res) { + coro_http_server server(max_thread_num, 8080); + server.set_http_handler("/", [](coro_http_request& req, coro_http_response& res) { res.set_status_and_content(status_type::ok, "hello world"); }); - server.run(); + server.sync_start(); return 0; } +``` 5行代码就可以实现一个简单http服务器了,用户不需要关注多少细节,直接写业务逻辑就行了。 -## 示例2:展示如何取header和query以及错误返回 +## 示例2:基本用法 +```c++ +#include "cinatra.hpp" - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/test", [](request& req, response& res) { - auto name = req.get_header_value("name"); - if (name.empty()) { - res.set_status_and_content(status_type::bad_request, "no name"); - return; - } - - auto id = req.get_query_value("id"); - if (id.empty()) { - res.set_status_and_content(status_type::bad_request); - return; - } - - res.set_status_and_content(status_type::ok, "hello world"); - }); +struct person_t { + void foo(coro_http_request &, coro_http_response &res) { + res.set_status_and_content(status_type::ok, "ok"); + } +}; + +async_simple::coro::Lazy basic_usage() { + coro_http_server server(1, 9001); + server.set_http_handler( + "/get", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/coro", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }); + + server.set_http_handler( + "/in_thread_pool", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + // will respose in another thread. + co_await coro_io::post([&] { + // do your heavy work here when finished work, response. + resp.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/post", [](coro_http_request &req, coro_http_response &resp) { + auto req_body = req.get_body(); + resp.set_status_and_content(status_type::ok, std::string{req_body}); + }); + + server.set_http_handler( + "/headers", [](coro_http_request &req, coro_http_response &resp) { + auto name = req.get_header_value("name"); + auto age = req.get_header_value("age"); + assert(name == "tom"); + assert(age == "20"); + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/query", [](coro_http_request &req, coro_http_response &resp) { + auto name = req.get_query_value("name"); + auto age = req.get_query_value("age"); + assert(name == "tom"); + assert(age == "20"); + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, coro_http_response &response) { + assert(req.params_["userid"] == "ultramarines"); + assert(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + person_t person{}; + server.set_http_handler("/person", &person_t::foo, person); + + server.async_start(); + std::this_thread::sleep_for(300ms); // wait for server start - server.run(); - return 0; - } + coro_http_client client{}; + auto result = co_await client.async_get("http://127.0.0.1:9001/get"); + assert(result.status == 200); + assert(result.resp_body == "ok"); + for (auto [key, val] : result.resp_headers) { + std::cout << key << ": " << val << "\n"; + } -## 示例3:面向切面的http服务器 + result = co_await client.async_get("/coro"); + assert(result.status == 200); + + result = co_await client.async_get("/in_thread_pool"); + assert(result.status == 200); + + result = co_await client.async_post("/post", "post string", + req_content_type::string); + assert(result.status == 200); + assert(result.resp_body == "post string"); + + client.add_header("name", "tom"); + client.add_header("age", "20"); + result = co_await client.async_get("/headers"); + assert(result.status == 200); + + result = co_await client.async_get("/query?name=tom&age=20"); + assert(result.status == 200); + + result = co_await client.async_get( + "http://127.0.0.1:9001/users/ultramarines/subscriptions/guilliman"); + assert(result.status == 200); + // make sure you have installed openssl and enable CINATRA_ENABLE_SSL +#ifdef CINATRA_ENABLE_SSL + coro_http_client client2{}; + result = co_await client2.async_get("https://baidu.com"); + assert(result.status == 200); +#endif +} + +int main() { + async_simple::coro::syncAwait(basic_usage()); +} +``` + +## 示例3:面向切面的http服务器 +```c++ #include "cinatra.hpp" using namespace cinatra; //日志切面 - struct log_t + struct log_t : public base_aspect { - bool before(request& req, response& res) { + bool before(coro_http_request& req, coro_http_response& res) { std::cout << "before log" << std::endl; return true; } - bool after(request& req, response& res) { + bool after(coro_http_request& req, coro_http_response& res) { std::cout << "after log" << std::endl; return true; } }; //校验的切面 - struct check { - bool before(request& req, response& res) { + struct check : public base_aspect { + bool before(coro_http_request& req, coro_http_response& res) { std::cout << "before check" << std::endl; if (req.get_header_value("name").empty()) { res.set_status_and_content(status_type::bad_request); @@ -151,186 +241,41 @@ cmake -DENABLE_SIMD=AARCH64 .. # arm环境下,启用neon指令集 return true; } - bool after(request& req, response& res) { + bool after(coro_http_request& req, coro_http_response& res) { std::cout << "after check" << std::endl; return true; } }; //将信息从中间件传输到处理程序 - struct get_data { - bool before(request& req, response& res) { + struct get_data : public base_aspect { + bool before(coro_http_request& req, coro_http_response& res) { req.set_aspect_data("hello", std::string("hello world")); return true; } } int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/aspect", [](request& req, response& res) { + coro_http_server server(std::thread::hardware_concurrency(), 8080); + server.set_http_handler("/aspect", [](coro_http_request& req, coro_http_response& res) { res.set_status_and_content(status_type::ok, "hello world"); - }, check{}, log_t{}); + }, std::vector{std::make_shared(), std::make_shared()}); - server.set_http_handler("/aspect/data", [](request& req, response& res) { + server.set_http_handler("/aspect/data", [](coro_http_request& req, coro_http_response& res) { std::string hello = req.get_aspect_data("hello"); res.set_status_and_content(status_type::ok, std::move(hello)); - }, get_data{}); + }, std::vector{std::make_shared()}); - server.run(); + server.sync_start(); return 0; } +``` 本例中有两个切面,一个校验http请求的切面,一个是日志切面,这个切面用户可以根据需求任意增加。本例会先检查http请求的合法性,如果不合法就会返回bad request,合法就会进入下一个切面,即日志切面,日志切面会打印出一个before表示进入业务逻辑之前的处理,业务逻辑完成之后会打印after表示业务逻辑结束之后的处理。 -## 示例4:文件上传 -cinatra目前支持了multipart和octet-stream格式的上传。 - -### multipart文件上传 - - #include - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - - //http upload(multipart) - server.set_http_handler("/upload_multipart", [](request& req, response& res) { - assert(req.get_content_type() == content_type::multipart); - - auto& files = req.get_upload_files(); - for (auto& file : files) { - std::cout << file.get_file_path() << " " << file.get_file_size() << std::endl; - } - - res.set_status_and_content(status_type::ok, "multipart finished"); - }); - - server.run(); - return 0; - } - -短短几行代码就可以实现一个http文件上传的服务器了,包含了异常处理和错误处理。 - -### octet-stream文件上传 - - #include - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - - //http upload(octet-stream) - server.set_http_handler("/upload_octet_stream", [](request& req, response& res) { - assert(req.get_content_type() == content_type::octet_stream); - auto& files = req.get_upload_files(); - for (auto& file : files) { - std::cout << file.get_file_path() << " " << file.get_file_size() << std::endl; - } - - res.set_status_and_content(status_type::ok, "octet-stream finished"); - }); - - server.run(); - return 0; - } - -## 示例5:文件下载 - - cinatra提供下载功能非常简单,不需要编写代码,具体方法: - 1. 启动cinatra server - 2. 将要下载的文件放到http server同一级的www目录下即可。 - 3. 如何下载:如果你把test.txt放到www之后,那么直接通过http://127.0.0.1:8090/test.txt下载即可。 - //chunked download - //cinatra will send you the file, if the file is big file(more than 5M) the file will be downloaded by chunked. support continues download - - http下载还提供了两个函数来优化其功能: - 1. 通过set_http_file_server(std::string path)函数来将cinatra转换为一个http文件下载服务器。当访问 http://ip:port/path 的时候会展示能够下载的文件 - 2. 通过set_file_mapping(std::size_t file_max_size)函数可以建立请求路径与文件缓存的映射,开启此选项server初始化时会读取设置的静态文件路径下小于file_max_size的所有文件,当客户端访问时服务器不会读文件而是直接返回文件缓存。此选项可以通过内存来优化服务器性能。 - - 示例如下: - - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - http_server server(std::thread::hardware_concurrency()); - server.set_file_mapping(); - server.set_http_file_server("http_file_server"); - server.listen("0.0.0.0", "8080"); - // 略 - } - - 此时访问当前服务器的`http_file_server`路径时会在浏览器展示所有可下载文件,点击即可下载。 - -## 示例6:websocket - - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - - //web socket - server.set_http_handler("/ws", [](request& req, response& res) { - assert(req.get_content_type() == content_type::websocket); - - req.on(ws_open, [](request& req){ - std::cout << "websocket start" << std::endl; - }); - - req.on(ws_message, [](request& req) { - auto part_data = req.get_part_data(); - //echo - std::string str = std::string(part_data.data(), part_data.length()); - req.get_conn()->send_ws_string(std::move(str)); - std::cout << part_data.data() << std::endl; - }); - - req.on(ws_error, [](request& req) { - std::cout << "websocket pack error or network error" << std::endl; - }); - }); - - server.run(); - return 0; - } - -## 示例7:io_service_inplace -本代码演示如何使用io_service_inplace,然后自己控制http server的运行线程以及循环。 -使用 [http://[::1]:8080/close] (IPv6) 或者 [http://127.0.0.1:8080/close] (IPv4) 来关闭http server。 - - #include "cinatra.hpp" - using namespace cinatra; - - int main() { - - bool is_running = true; - http_server_ server; - server.listen("8080"); - - server.set_http_handler("/", [](request& req, response& res) { - res.set_status_and_content(status_type::ok, "hello world"); - }); - - server.set_http_handler("/close", [&](request& req, response& res) { - res.set_status_and_content(status_type::ok, "will close"); - - is_running = false; - server.stop(); - }); - - while(is_running) - server.poll_one(); - - return 0; - } +## 示例4:文件上传、下载、websocket +见[example中的例子](example/main.cpp) -## 示例8:RESTful服务端路径参数设置 +## 示例5:RESTful服务端路径参数设置 本代码演示如何使用RESTful路径参数。下面设置了两个RESTful API。第一个API当访问,比如访问这样的url`http://127.0.0.1:8080/numbers/1234/test/5678`时服务器可以获取到1234和5678这两个参数,第一个RESTful API的参数是`(\d+)`是一个正则表达式表明只能参数只能为数字。获取第一个参数的代码是`req.get_matches()[1]`。因为每一个req不同所以每一个匹配到的参数都放在`request`结构体中。 同时还支持任意字符的RESTful API,即示例的第二种RESTful API`"/string/{:id}/test/{:name}"`,要获取到对应的参数使用`req.get_query_value`函数即可,其参数只能为注册的变量(如果不为依然运行但是有报错),例子中参数名是id和name,要获取id参数调用`req.get_query_value("id")`即可。示例代码运行后,当访问`http://127.0.0.1:8080/string/params_1/test/api_test`时,浏览器会返回`api_test`字符串。 @@ -340,8 +285,7 @@ cinatra目前支持了multipart和octet-stream格式的上传。 int main() { int max_thread_num = std::thread::hardware_concurrency(); - http_server server(max_thread_num); - server.listen("0.0.0.0", "8080"); + coro_http_server server(max_thread_num, 8080); server.set_http_handler( R"(/numbers/(\d+)/test/(\d+))", [](request &req, response &res) { @@ -359,7 +303,7 @@ cinatra目前支持了multipart和octet-stream格式的上传。 res.set_status_and_content(status_type::ok, std::string(req.get_query_value("name"))); }); - server.run(); + server.sync_start(); return 0; } diff --git a/example/main.cpp b/example/main.cpp index 89fb722d..eab0f4ce 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -19,19 +19,6 @@ void create_file(std::string filename, size_t file_size = 64) { } } -struct log_t : public base_aspect { - bool before(coro_http_request &, coro_http_response &) { - std::cout << "before log" << std::endl; - return true; - } - - bool after(coro_http_request &, coro_http_response &res) { - std::cout << "after log" << std::endl; - res.add_header("aaaa", "bbcc"); - return true; - } -}; - async_simple::coro::Lazy byte_ranges_download() { create_file("test_multiple_range.txt", 64); coro_http_server server(1, 8090); @@ -62,7 +49,7 @@ async_simple::coro::Lazy byte_ranges_download() { std::string uri = "http://127.0.0.1:8090/test_multiple_range.txt"; client.add_header("Range", "bytes=1-10,20-30"); - auto result = client.get(uri); + auto result = co_await client.async_get(uri); assert(result.status == 206); assert(result.resp_body.size() == 21); @@ -244,15 +231,38 @@ async_simple::coro::Lazy static_file_server() { assert(result.resp_body.size() == 64); } +struct log_t : public base_aspect { + bool before(coro_http_request &, coro_http_response &) { + std::cout << "before log" << std::endl; + return true; + } + + bool after(coro_http_request &, coro_http_response &res) { + std::cout << "after log" << std::endl; + res.add_header("aaaa", "bbcc"); + return true; + } +}; + +struct get_data : public base_aspect { + bool before(coro_http_request &req, coro_http_response &res) { + req.set_aspect_data("hello", std::string("hello world")); + return true; + } +}; + async_simple::coro::Lazy use_aspects() { coro_http_server server(1, 9001); - std::vector> aspects{std::make_shared()}; server.set_http_handler( "/get", [](coro_http_request &req, coro_http_response &resp) { + std::optional val = + req.get_aspect_data("hello"); + assert(*val == "hello world"); resp.set_status_and_content(status_type::ok, "ok"); }, - aspects); + std::vector>{std::make_shared(), + std::make_shared()}); server.async_start(); std::this_thread::sleep_for(300ms); // wait for server start diff --git a/include/cinatra/coro_http_request.hpp b/include/cinatra/coro_http_request.hpp index 478bd1cb..cedf44a8 100644 --- a/include/cinatra/coro_http_request.hpp +++ b/include/cinatra/coro_http_request.hpp @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include "async_simple/coro/Lazy.h" @@ -13,7 +15,7 @@ namespace cinatra { inline std::vector> parse_ranges(std::string_view range_str, size_t file_size, - bool& is_valid) { + bool &is_valid) { range_str = trim_sv(range_str); if (range_str.empty()) { return {{0, file_size - 1}}; @@ -90,12 +92,12 @@ inline std::vector> parse_ranges(std::string_view range_str, class coro_http_connection; class coro_http_request { public: - coro_http_request(http_parser& parser, coro_http_connection* conn) + coro_http_request(http_parser &parser, coro_http_connection *conn) : parser_(parser), conn_(conn) {} std::string_view get_header_value(std::string_view key) { auto headers = parser_.get_headers(); - for (auto& header : headers) { + for (auto &header : headers) { if (iequal0(header.name, key)) { return header.value; } @@ -119,9 +121,9 @@ class coro_http_request { std::span get_headers() const { return parser_.get_headers(); } - const auto& get_queries() const { return parser_.queries(); } + const auto &get_queries() const { return parser_.queries(); } - void set_body(std::string& body) { + void set_body(std::string &body) { body_ = body; auto type = get_content_type(); if (type == content_type::urlencoded) { @@ -175,7 +177,7 @@ class coro_http_request { return content_type.substr(content_type.rfind("=") + 1); } - coro_http_connection* get_conn() { return conn_; } + coro_http_connection *get_conn() { return conn_; } bool is_upgrade() { auto h = get_header_value("Connection"); @@ -200,13 +202,32 @@ class coro_http_request { return true; } + void set_aspect_data(const std::string &&key, const std::any &data) { + aspect_data_[key] = data; + } + + template + std::optional get_aspect_data(const std::string &&key) { + auto it = aspect_data_.find(key); + if (it == aspect_data_.end()) { + return std::optional{}; + } + + try { + return std::any_cast(it->second); // throws + } catch (const std::bad_any_cast &e) { + return std::optional{}; + } + } + std::unordered_map params_; std::smatch matches_; private: - http_parser& parser_; + http_parser &parser_; std::string_view body_; - coro_http_connection* conn_; + coro_http_connection *conn_; bool is_websocket_; + std::unordered_map aspect_data_; }; } // namespace cinatra \ No newline at end of file diff --git a/lang/english/README.md b/lang/english/README.md index 291bf181..4caa5e3d 100644 --- a/lang/english/README.md +++ b/lang/english/README.md @@ -60,282 +60,211 @@ cmake -DENABLE_SIMD=AARCH64 .. # enable neon instruction set in aarch64 ### Example 1: A simple "Hello World" -```cpp -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - int max_thread_num = std::thread::hardware_concurrency(); - http_server server(max_thread_num); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/", [](request& req, response& res) { - res.set_status_and_content(status_type::ok, "hello world"); - }); +```c++ + #include "include/cinatra.hpp" + using namespace cinatra; + + int main() { + int max_thread_num = std::thread::hardware_concurrency(); + coro_http_server server(max_thread_num, 8080); + server.set_http_handler("/", [](coro_http_request& req, coro_http_response& res) { + res.set_status_and_content(status_type::ok, "hello world"); + }); - server.run(); - return 0; -} + server.sync_start(); + return 0; + } ``` ### Example 2: Access to request header, query parameter, and response -```cpp +```c++ #include "cinatra.hpp" -using namespace cinatra; -int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/test", [](request& req, response& res) { - auto name = req.get_header_value("name"); - if (name.empty()) { - res.set_status_and_content(status_type::bad_request, "no name"); - return; - } +struct person_t { + void foo(coro_http_request &, coro_http_response &res) { + res.set_status_and_content(status_type::ok, "ok"); + } +}; - auto id = req.get_query_value("id"); - if (id.empty()) { - res.set_status_and_content(status_type::bad_request); - return; - } +async_simple::coro::Lazy basic_usage() { + coro_http_server server(1, 9001); + server.set_http_handler( + "/get", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/coro", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_status_and_content(status_type::ok, "ok"); + co_return; + }); + + server.set_http_handler( + "/in_thread_pool", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + // will respose in another thread. + co_await coro_io::post([&] { + // do your heavy work here when finished work, response. + resp.set_status_and_content(status_type::ok, "ok"); + }); + }); + + server.set_http_handler( + "/post", [](coro_http_request &req, coro_http_response &resp) { + auto req_body = req.get_body(); + resp.set_status_and_content(status_type::ok, std::string{req_body}); + }); + + server.set_http_handler( + "/headers", [](coro_http_request &req, coro_http_response &resp) { + auto name = req.get_header_value("name"); + auto age = req.get_header_value("age"); + assert(name == "tom"); + assert(age == "20"); + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/query", [](coro_http_request &req, coro_http_response &resp) { + auto name = req.get_query_value("name"); + auto age = req.get_query_value("age"); + assert(name == "tom"); + assert(age == "20"); + resp.set_status_and_content(status_type::ok, "ok"); + }); + + server.set_http_handler( + "/users/:userid/subscriptions/:subid", + [](coro_http_request &req, coro_http_response &response) { + assert(req.params_["userid"] == "ultramarines"); + assert(req.params_["subid"] == "guilliman"); + response.set_status_and_content(status_type::ok, "ok"); + }); + + person_t person{}; + server.set_http_handler("/person", &person_t::foo, person); + + server.async_start(); + std::this_thread::sleep_for(300ms); // wait for server start - res.set_status_and_content(status_type::ok, "hello world"); - }); + coro_http_client client{}; + auto result = co_await client.async_get("http://127.0.0.1:9001/get"); + assert(result.status == 200); + assert(result.resp_body == "ok"); + for (auto [key, val] : result.resp_headers) { + std::cout << key << ": " << val << "\n"; + } - server.run(); - return 0; -} -``` + result = co_await client.async_get("/coro"); + assert(result.status == 200); -### Example 3: Aspect-oriented HTTP server + result = co_await client.async_get("/in_thread_pool"); + assert(result.status == 200); -```cpp -#include "cinatra.hpp" -using namespace cinatra; + result = co_await client.async_post("/post", "post string", + req_content_type::string); + assert(result.status == 200); + assert(result.resp_body == "post string"); -// Logging aspect -struct log_t -{ - bool before(request& req, response& res) { - std::cout << "before log" << std::endl; - return true; - } + client.add_header("name", "tom"); + client.add_header("age", "20"); + result = co_await client.async_get("/headers"); + assert(result.status == 200); - bool after(request& req, response& res) { - std::cout << "after log" << std::endl; - return true; - } -}; + result = co_await client.async_get("/query?name=tom&age=20"); + assert(result.status == 200); -// Checking aspect -struct check { - bool before(request& req, response& res) { - std::cout << "before check" << std::endl; - if (req.get_header_value("name").empty()) { - res.set_status_and_content(status_type::bad_request); - return false; - } - return true; - } + result = co_await client.async_get( + "http://127.0.0.1:9001/users/ultramarines/subscriptions/guilliman"); + assert(result.status == 200); - bool after(request& req, response& res) { - std::cout << "after check" << std::endl; - return true; - } -}; - -// transfer data from aspect to http handler -struct get_data { - bool before(request& req, response& res) { - req.set_aspect_data("hello", std::string("hello world")); - return true; - } + // make sure you have installed openssl and enable CINATRA_ENABLE_SSL +#ifdef CINATRA_ENABLE_SSL + coro_http_client client2{}; + result = co_await client2.async_get("https://baidu.com"); + assert(result.status == 200); +#endif } int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - server.set_http_handler("/aspect", [](request& req, response& res) { - res.set_status_and_content(status_type::ok, "hello world"); - }, check{}, log_t{}); - - server.set_http_handler("/aspect/data", [](request& req, response& res) { - std::string hello = req.get_aspect_data("hello"); - res.set_status_and_content(status_type::ok, std::move(hello)); - }, get_data{}); - - server.run(); - return 0; + async_simple::coro::syncAwait(basic_usage()); } ``` -In this example, there are two aspects: one is to check the validity of the http request, and the other is the logging aspect. You can add pass as many aspects as needed to the `set_http_handler` method. - -The order of execution of the aspects depends on the order that they are passed to the `set_http_handler` method. In this example, the aspect to check the validity of the http request is called first. If the request is not valid, it will return a Bad Request error. If it is valid, the next aspect (that is, the log aspect) will be called. The log aspect, through the `before` method, will print a log indicating the processing before entering the business logic. After the business logic is completed, the log aspect's `after` method will print to indicate the processing after the end of the business logic. - -### Example 4: File upload - -Cinatra currently supports uploading of multipart and octet-stream formats. - -#### Multi-part file upload - -```cpp -#include -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); +### Example 3: Aspect-oriented HTTP server - //http upload(multipart) - server.set_http_handler("/upload_multipart", [](request& req, response& res) { - assert(req.get_content_type() == content_type::multipart); - - auto& files = req.get_upload_files(); - for (auto& file : files) { - std::cout << file.get_file_path() << " " << file.get_file_size() << std::endl; +```c++ + #include "cinatra.hpp" + using namespace cinatra; + + //日志切面 + struct log_t : public base_aspect + { + bool before(coro_http_request& req, coro_http_response& res) { + std::cout << "before log" << std::endl; + return true; } - - res.set_status_and_content(status_type::ok, "multipart finished"); - }); - - server.run(); - return 0; -} -``` - -As you can see, a few lines of code can be used to implement a http file upload server, including exception handling and error handling. - -#### Octet-stream file upload - -```cpp -#include -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - - //http upload(octet-stream) - server.set_http_handler("/upload_octet_stream", [](request& req, response& res) { - assert(req.get_content_type() == content_type::octet_stream); - auto& files = req.get_upload_files(); - for (auto& file : files) { - std::cout << file.get_file_path() << " " << file.get_file_size() << std::endl; + + bool after(coro_http_request& req, coro_http_response& res) { + std::cout << "after log" << std::endl; + return true; } + }; + + //校验的切面 + struct check : public base_aspect { + bool before(coro_http_request& req, coro_http_response& res) { + std::cout << "before check" << std::endl; + if (req.get_header_value("name").empty()) { + res.set_status_and_content(status_type::bad_request); + return false; + } + return true; + } + + bool after(coro_http_request& req, coro_http_response& res) { + std::cout << "after check" << std::endl; + return true; + } + }; - res.set_status_and_content(status_type::ok, "octet-stream finished"); - }); - - server.run(); - return 0; -} -``` - -### Example 5: File download - -cinatra support chunked download files. - -Make sure the files are in your resource dictionary(which you could set in the server, such as "./public/static"), and then you could download the files directly. - -Here is the example: - -Assume the file "show.jpg" is in the "./purecpp/static/" of the server, you just need to typing the address of the image, and you could download the image immediately. -``` -//chunked download -http://127.0.0.1:8080/purecpp/static/show.jpg -//cinatra will send you the file, if the file is big file(more than 5M) the file will be downloaded by chunked. support continues download -``` - -1. Use the `set_http_file_server(std::string path)` function to convert cinatra into an http file download server. When accessing http://ip:port/path, files that can be downloaded will be displayed. -2. The `set_file_mapping(std::size_t file_max_size)` function can be used to establish the mapping between the request path and the file cache. After turning on this option, the server will read all files smaller than file_max_size in the set static file path. When the client accesses the server, the server will directly return the file cache. This option optimizes server performance through memory. - -```cpp -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - http_server server(std::thread::hardware_concurrency()); - server.set_file_mapping(); - server.set_http_file_server("http_file_server"); - server.listen("0.0.0.0", "8080"); - // ...... -} -``` - -In this time, when accessing the `http_file_server` path of the current server, all downloadable files will be displayed in the browser, and you can download them by clicking on them. - -### Example 6: WebSocket - -```cpp -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - http_server server(std::thread::hardware_concurrency()); - server.listen("0.0.0.0", "8080"); - - //web socket - server.set_http_handler("/ws", [](request& req, response& res) { - assert(req.get_content_type() == content_type::websocket); - - req.on(ws_open, [](request& req){ - std::cout << "websocket start" << std::endl; - }); + //将信息从中间件传输到处理程序 + struct get_data : public base_aspect { + bool before(coro_http_request& req, coro_http_response& res) { + req.set_aspect_data("hello", std::string("hello world")); + return true; + } + } - req.on(ws_message, [](request& req) { - auto part_data = req.get_part_data(); - //echo - std::string str = std::string(part_data.data(), part_data.length()); - req.get_conn()->send_ws_string(std::move(str)); - std::cout << part_data.data() << std::endl; - }); + int main() { + coro_http_server server(std::thread::hardware_concurrency(), 8080); + server.set_http_handler("/aspect", [](coro_http_request& req, coro_http_response& res) { + res.set_status_and_content(status_type::ok, "hello world"); + }, std::vector{std::make_shared(), std::make_shared()}); - req.on(ws_error, [](request& req) { - std::cout << "websocket pack error or network error" << std::endl; - }); - }); + server.set_http_handler("/aspect/data", [](coro_http_request& req, coro_http_response& res) { + std::string hello = req.get_aspect_data("hello"); + res.set_status_and_content(status_type::ok, std::move(hello)); + }, std::vector{std::make_shared()}); - server.run(); - return 0; -} + server.sync_start(); + return 0; + } ``` -### Example 7: io_service_inplace - -This code demonstrates how to use io_service_inplace and then control the running thread and loop of the http server itself. Use http://[::1]:8080/close (IPv6) or http://127.0.0.1:8080/close (IPv4) to shut down the http server. - -```cpp -#include "cinatra.hpp" -using namespace cinatra; - -int main() { - - bool is_running = true; - http_server_ server; - server.listen("8080"); - - server.set_http_handler("/", [](request& req, response& res) { - res.set_status_and_content(status_type::ok, "hello world"); - }); - - server.set_http_handler("/close", [&](request& req, response& res) { - res.set_status_and_content(status_type::ok, "will close"); +In this example, there are two aspects: one is to check the validity of the http request, and the other is the logging aspect. You can add pass as many aspects as needed to the `set_http_handler` method. - is_running = false; - server.stop(); - }); +The order of execution of the aspects depends on the order that they are passed to the `set_http_handler` method. In this example, the aspect to check the validity of the http request is called first. If the request is not valid, it will return a Bad Request error. If it is valid, the next aspect (that is, the log aspect) will be called. The log aspect, through the `before` method, will print a log indicating the processing before entering the business logic. After the business logic is completed, the log aspect's `after` method will print to indicate the processing after the end of the business logic. - while(is_running) - server.poll_one(); +### Example 4: File upload, download, websocket - return 0; -} -``` +see[example](../../example/main.cpp) -### Example 8: set RESTful API path parameters +### Example : set RESTful API path parameters This code demonstrates how to use RESTful path parameters. Two RESTful APIs are set up below. When accessing the first API, such as the url `http://127.0.0.1:8080/numbers/1234/test/5678`, the server can get the two parameters of 1234 and 5678, the first RESTful API The parameter is `(\d+)`, which is a regex expression, which means that the parameter can only be a number. The code to get the first parameter is `req.get_matches ()[1]`. Because each req is different, each matched parameter is placed in the `request` structure. @@ -371,7 +300,7 @@ int main() { } ``` -### Example 9: cinatra client usage +### Example : cinatra client usage #### sync_send get/post message