Paozhu (π§¨) is a high-performance C++20 web framework built on Asio, with native support for HTTP/1, HTTP/2, and a built-in C++ ORM. It makes writing web programs in C++ straightforward and elegant β something that used to be painful.
Paozhu implements the full stack of web infrastructure: multi-domain binding, SSL certificate management, cookies & sessions, HTML form handling, URL routing, file uploads & downloads, HttpClient, ORM with coroutines, WebSocket support, logging, and an HTML template engine.
The framework is built on C++20, so you get modern language features and full compiler support. It ships with ASAN enabled and all compiler warnings turned on, which makes bugs easier to catch during development; stress tests help surface race conditions. At runtime, you can pair it with coredumpctl to inspect thread stacks. Smart pointers and RAII patterns throughout mean memory leaks and buffer overflows are practically eliminated β we haven't seen any crashes in production.
We chose C++ because it integrates naturally with large-model ecosystems: CUDA, file storage, networking, and raw memory operations are all first-class citizens here. To be fair, every language has tradeoffs. Rust has a steep learning curve and verbose syntax. Go is simple but the ecosystem around AI and large models is shallower. C++ isn't perfect either β but with modern tooling (ASAN, coredumpctl, strict compiler warnings), it's very manageable.
Paozhu runs on macOS, Linux, and Windows.
Required dependencies:
OpenSSL Zlib Brotli ASIO
Optional dependencies:
GD qrencode
Paozhu requires a C++20-capable compiler.
MacOS MacOS 11 BigSur above, suggestion MacOS 14 SonomaUbuntu 22.04 aboveRockyLinux AlmaLinux 9.1 aboveWindows Win10 Compiler 19.25 or aboveγThe framework uses an M:N concurrency model:
+-----------------------+ +----------------------------+
| asio coroutine thread | | thread pool(business code) |
+-----------------------+ +----------------------------+
+-----------------------+ +-----------------------+ +----------------------------+
| thread accept(80 443) | | asio coroutine thread | | thread pool(business code) |
+-----------------------+ +-----------------------+ +----------------------------+
+-----------------------+ +----------------------------+
| asio coroutine thread | | thread pool(business code) |
+-----------------------+ +----------------------------+
Paozhu uses ASIO's thread pool, which is tuned for IO-heavy work like network I/O. Separately, there is a dedicated business thread pool that handles CPU-bound logic.
At any given moment, two threads are listening on ports 80 and 443 for new connections. When a client connects, the server performs the TLS handshake (for HTTPS), parses the incoming HTTP request, and matches the URL to a registered handler. If the handler is a coroutine, it runs directly on an ASIO worker thread. If it's a regular function, it gets dispatched to the business thread pool. Static file responses are served straight away without going through business threads.
C++20 coroutines are contagious: if your handler is a coroutine, every ORM call inside it must use the async async_* versions. Conversely, regular handler functions must use the synchronous ORM methods β you cannot mix them.
Avoid calling sleep_for or similar blocking calls inside either thread pool, as it stalls other connections on the same thread.
Paozhu is not a separate CLI tool that you install globally β the framework lives inside your project source tree. Every new project should include the full framework codebase.
.
βββ asio ASIO library file, the framework comes with an ASIO.zip
βββ asio.zip
βββ bin Compiled executable file
βββ build
βββ cache Cache some files, such as compressed HTML and JS files
βββ CMakeLists.txt The configuration file for CMake
βββ common The public files, such as annotation functions, compilation constants, etc
βββ conf Framework configuration files such as server.conf, orm.conf, SSL certificate
βββ controller Router code entry, which can store business code.
βββ docs
βββ libs Store your own business code repository
βββ LICENSE
βββ log Framework access and error logs
βββ models ORM Object Business Code
βββ module Storage location for pluggable SO
βββ orm ORM and database table entity mapping classes
βββ README_CN.md
βββ README_EN.md
βββ README.md
βββ startup The main function entry of the framework distinguishes between debugging products and Docker compilation, etc
βββ temp Framework accepts temporary storage for uploaded files
βββ vcpkg-configuration.json
βββ vcpkg.json
βββ vendor Storage of framework main program files and third-party packages
βββ view Framework view files, mainly used for storing HTML templates
βββ viewsrc Framework compiled view file
βββ websockets WebSocket program object storage location
βββ www
βββ xmake.lua XMake configuration
Download from GitHub
https://github.com/hggq/paozhu
Paozhu Config file at conf/server.conf
You can first understand a few variables to get the project running.
[default]
threadmax=1024
threadmin=5
httpport=80
httpsport=443
cothreadnum=8 ;Coroutines run on thread num
http2_enable=1
debug_enable=1
deamon_enable=0
mainhost=www.host.com
certificate_chain_file=www.host.com.pem
private_key_file=www.host.com.key
tmp_dh_file=dh4096.pem
reboot_password=e10adc3949ba59abbe56e057f20f883e ;md5(md5("123456")+"rand_char"+md5("123456"))
reboot_cron =d180h5 ;MDSW+Hhours reboot process M month D day S season (1 4 7 10) W week
clean_cron =m5t600 ;5-minute interval clean 600 seconds ago inactive connection
links_restart_process =n9998877ts1te5 ;More than 15000 connections, restart the process from 1:00 am to 5:00 am
session_type=1 ;session save type 0.file 1.memory 2.redis 3.memcache 4.reserve
static_file_compress_cache=1 ;1 enable, Cache static file compress(gzip,br) content to cache directory
modelspath=/Users/hzq/paozhu/models
serverpath=/Users/hzq/paozhu
viewpath=/Users/hzq/paozhu/view
viewsopath=/Users/hzq/paozhu/module/view
controlpath=/Users/hzq/paozhu/controller
controlsopath=/Users/hzq/paozhu/module/controller
temppath=/Users/hzq/paozhu/temp
logpath=/Users/hzq/paozhu/log
wwwpath=/Users/hzq/paozhu/www/default
pluginspath=/Users/hzq/paozhu/plugins
libspath=/Users/hzq/paozhu/libs
directorylist=1
index=index.html
;usehtmlcache=1
;usehtmlcachetime=3600
staticfile_cache_num=0 ;0 not cache, min 10 begin
rewrite_404=0 ;1 file 2 action url path
rewrite_404_action=index.html
method_pre=
method_after=
show_visitinfo=0
upload_max_size=16777216
siteid=1
groupid=0
alias_domain=
init_func=
[www.host.com]
wwwpath=/Users/hzq/paozhu/www/host.com
http2_enable=1
;rewrite_404=1
;rewrite_404_action=index.html|psy/index.html|exam/index.html
;controlsopath=/Users/hzq/paozhu/docs/controller
static_pre=downloadfileauth|upload
method_pre= ;api/dev/hostcors
method_after=
isuse_php=0
rewrite_php=/Users/hzq/www/mpdftest|index.php
fastcgi_host=127.0.0.1 ;/run/php/php8.1-fpm.sock
fastcgi_port=9000
upload_max_size=16777216
siteid=1
groupid=0
alias_domain=
init_func=
threadmax=1024 Maximum number of business threads
threadmin=5 Minimum number of business threads
httpport=80 Server listening port
httpsport=443 SSL server listening port
cothreadnum=8 ;Coroutines run on thread num
http2_enable=1 Whether to enable HTTP/2, each domain can have its own settings
SSL certificate file, after successful application, it should be placed in the conf directory. For production environments, a copy should be placed in /var/local/etc/paozhu.
mainhost=www.host.com
certificate_chain_file=www.host.com.pem
private_key_file=www.host.com.key
reboot_cron Automatic restart process cycle day D week W month M season S h time hour 180 days 5 am restart process
links_restart_process If the number of connections is reached, you can also restart the number of connections after n, The ts start period and te end period will restart if they are idle during this period.
reboot_cron =d180h5 ;MDSW+Hhours reboot process M month D day S season (1 4 7 10) W week
clean_cron =m5t600 ;5-minute interval clean 600 seconds ago inactive connection
links_restart_process =n9998877ts1te5 ;More than 15000 connections, restart the process from 1:00 am to 5:00 am
directorylist=1 Display directory. If the requested static file is a directory name and there is no index.html file in the directory, a directory list will be displayed.
It is recommended to set the production environment to 0
wwwpath=/Users/hzq/paozhu/www/default /Users/hzq/paozhu Suggest changing to your current directory name, Under the project, 'www/default' is a static file directory. If it is a multi domain, it is recommended to create a 'www/host.com' setting for SAAS services.
upload_max_size=16777216 Upload file size, default is 16M
conf/server.conf In this configuration file, wwwpath It is a path for storing static web files, which can provide WWW services without writing programs. Generally, it stores HTML, JS, CSS, and JPG files, which can be directly opened and accessed using a browser.
All domain websites are stored in the www directory, with a structure similar to the following.
www
βββdefault
βββenglish
βββhost.com
β βββindex.html
βββjapan
http://www.host.com/index.html The browser opens index.html in the host.com directory.
The following are SAAS settings that can be ignored for now .
siteid=1
groupid=0
alias_domain=
init_func=
themes =
client.get_siteid(); get siteid
client.get_groupid(); get groupid
client.get_theme(); get theme
client.theme_view(contt std::string &); set theme view file
Beginners only need to understand httpport httpsport wwwpath logpath Ensure that the settings are correct, Replace all '/Users/hzq/paozhu' with your directory.
Paozhu uses annotation to associate URLs with functions, turning a function into an annotated function with just one line.
//@urlpath(null,plaintext)
Example of controller/src/techepower.cpp file in project directory
#include "techempower.h"
//@urlpath(null,plaintext)
asio::awaitable<std::string> techempowerplaintext(std::shared_ptr<httppeer> peer)
{
peer->type("text/plain; charset=UTF-8");
peer->set_header("Date", get_gmttime());
peer->output = "Hello, World!";
co_return "";
}
All controller files must live under controller/src/. Each .cpp file must #include a header matching its own filename (e.g. techempower.cpp includes "techempower.h") β that header is auto-generated during the build.
Here's how the magic works: CMake runs a preprocessor binary ahead of the real compilation. That tool scans every file in controller/src/, looks for functions tagged with //@urlpath(null, plaintext), generates the corresponding header files, and writes the URL β function mapping into common/autocontrolmethod.hpp.
If this feels familiar β it's basically Spring Boot's annotation-based routing, ported to C++. No manual registration, no hand-written headers.
//@urlpath(pre, path)The first argument is a pre-filter β a function that runs before the main handler. null means "no pre-filter". So //@urlpath(null, plaintext) means "register the function below at /plaintext, no prerequisites."
Now take a protected admin page: //@urlpath(islogin, admin/main) means "run islogin first; if it returns ok, proceed to admin/main. Otherwise the request is rejected."
//@urlpath(null, islogin)
asio::awaitable<std::string> checklogin(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
if(peer->session["userid"].to_int()>0)
{
co_return "ok";
}
client << "You must login";
co_return "";
}
A few notes on the code above:
asio::awaitable<std::string> marks this handler as a coroutine β it runs directly on an ASIO worker thread, never on the business thread pool. Keep heavy computation out of coroutines; that's what regular functions are for.peer->session["userid"] reads a value from the user's session (set at login).client << "You must login"; uses the overloaded << operator; the string ends up in peer->output.co_return "" exits the coroutine. Always return an empty string from coroutine handlers unless you know what you're doing.Here's the regular-function version. The return type is plain std::string, which tells the framework to dispatch it to the business thread pool (one thread per connection). This is the safe default for CPU-heavy work or blocking ORM calls.
For CPU-intensive tasks, prefer the regular-function route β otherwise you'll starve the IO threads and hurt overall concurrency. And never call std::this_thread::sleep_for(...) inside a coroutine; it blocks the ASIO worker for everyone.
//@urlpath(null,hello)
std::string testhello(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << " Hello world! 𧨠Paozhu c++ web framework ";
return "";
}
For more code usage cases, please refer to the examples in the controller/src directory
Operate in the root directory of your project.
mkdir build
cd build
cmake ..
make
The compilation is complete, and the server program can now be started. Please pay attention to the execution permissions of the program.
cd ..
MacOS #./bin/paozhu
Linux #sudo ./bin/paozhu
http::obj_val hval;
hval["aaa"]=3344;
hval["bbb"]="1234567890";
std::cout<<"ll:"<<hval["aaa"].to_int()<<std::endl;
std::cout<<"vv:|"<<static_cast<int>(hval["bbb"].get_type())<<"|"<<std::endl;
if(hval["bbb"].is_string())
{
std::cout<<"str:"<<hval["bbb"].to_string()<<std::endl;
std::cout<<"str:"<<hval["bbb"].str_view()<<std::endl;
std::cout<<"str:"<<hval["bbb"].str_view(2,5)<<std::endl;
}
http::obj_val nval;
nval.from_json("{\"bba\":[[[111,222],[333,444],[555,666]],[[777,888],[999,1111],[2222,3333]],[[4444,5555],[6666,7777],[8888,9999]]]}");
std::cout<<"json out:"<<nval.to_json()<<std::endl;
std::string bbb=nval.to_json();
http::obj_val pval;
pval.from_json(bbb);
http::obj_val is a built-in micro object in the framework that can be used to import and export from_json and to_json object conversions.
Does it have a scripting language flavor? It can save values and strings, and can also save tree structures.
http::obj_val is 16bytes sizeοΌone KV Object 64bytes sizeγ
hval["aaa"] is KV Objectγ
http::obj_val Built in methods for objects
set_array() set is array and push(v)set_obj() set is KV and use push(k,v) or obj[key]=valueset_object() set is KV and use push(k,v) or obj[key]=valueis_array() it is an array or notis_obj() it is an object or notis_object() it is an object or notis_string() it is an string or notis_number() it is an number or not client.val["list"].set_array();
obj_val temp;
for (unsigned int i = 0; i < topicm.record.size(); i++)
{
temp["id"] = topicm.record[i].topicid;
temp["parentid"] = topicm.record[i].parentid;
temp["value"] = topicm.record[i].title;
client.val["list"].push(temp);
}
<%c for(auto &a:obj["infos"].as_array()){ %>
<tr>
<td><%c echo<<a["userid"].to_string(); %></td>
<td><%c echo<<a["nickname"].to_string(); %></td>
<td><%c echo<<a["name"].to_string(); %></td>
<td><%c if(a["isopen"].to_int()==1){ %>Open<%c }else{ %>Close<%c } %></td>
<td><a href="/superadmin/edituser?userid=<%c echo<<a["userid"].to_string(); %>">Edit</a> | <a href="/superadmin/deleteuser?userid=<%c echo<<a["userid"].to_string(); %>" onclick="return confirm('Are you sure deleteοΌ');">Delete</a></td>
</tr>
<%c } %>
obj_val multi_sort(std::string_view key,unsigned char order); 2D array key name sorting, order is SORT_ASC or SORT_DESC.obj_val multi_sort(std::string_view key,unsigned char order,std::string_view key2,unsigned char order2); 2D array key name sorting, order is SORT_ASC or SORT_DESC, If there is the same secondary sorting,need key2 and order2.
http::obj_val zval4;
zval4.set_array();
float s[6]={83.3,77.75,60.45,83.3,77.75,45.6};
for(unsigned int jjj=0;jjj<6;jjj++)
{
http::obj_val z4;
z4.set_object();
z4["name"]="sss"+std::to_string(jjj);
z4["score"]=s[jjj];
z4["id"]=jjj+10;
zval4.push(z4);
}
http::obj_val zval5=zval4.multi_sort("score",SORT_ASC);
std::cout<<"------"<<std::endl;
if(zval5.is_array())
{
std::cout<<"multi_sort score result: "<<zval5.size()<<std::endl;
for(auto &a:zval5.as_array())
{
if(a.is_object())
{
for(auto &[aa,bb]:a.as_object())
{
std::cout<<"zval5["<<aa<<"]="<< bb.to_string()<<std::endl;
}
}
std::cout<<std::endl;
}
}
http::obj_val zval6=zval4.multi_sort("score",SORT_ASC,"id",SORT_DESC);
std::cout<<"------"<<std::endl;
if(zval6.is_array())
{
std::cout<<"multi_sort score id result: "<<zval6.size()<<std::endl;
for(auto &a:zval6.as_array())
{
if(a.is_object())
{
for(auto &[aa,bb]:a.as_object())
{
std::cout<<"zval6["<<aa<<"]="<< bb.to_string()<<std::endl;
}
}
std::cout<<std::endl;
}
}
std::cout<<"------"<<std::endl;
Mainly used for complex object swapping programs, if it is not a complex object, C++ built-in POD, class objects, or STL library methods can be used.
The difference between Session and Cookie is that session data is stored on the server side, cookie data is stored on the browser side, and cookie data has a limited size of 4Kb.
Example file controller/src/testsessionid.cpp
//@urlpath(null,testsetsession)
std::string testsetsession(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "hello world! add cookie ";
client << client.get_hosturl();
int semid = client.get["aaa"].to_int();
if (semid != 0)
{
client.session["aaa"] = semid;
client << "<br />set session 2222 ";
}
else
{
client.session["aaa"] = 1111;
}
client.save_session();
client << "<p><a href=\"" << client.get_hosturl() << "/testsetsession?aaa=2222\">2222 session</a></p>";
client.set_cookie("ccbb", "123456abc", 7200 * 24);
client.set_cookie("same", "samesite", 7200 * 24, "/", client.host, false, true, "Lax");
client.set_header("server", "cpphttp");
client << "<p><a href=\"" << client.get_hosturl() << "/testshowsession\">show</a></p>";
client << "<p><a href=\"" << client.get_hosturl() << "/testshowsession?sessionid=" << client.get_session_id()
<< "\">session(" << client.get_session_id() << ")</a></p>";
client << "<p>session aaa:|" << client.session.to_json() << "|</p>";
return "";
}
//@urlpath(null,testshowsession)
std::string testshowsession(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "hello world! show cookie<br/>";
client << "<p><a href=\"" << client.get_hosturl() << "/testsetsession\">add cookie</a></p>";
client << "<p>session aaa:|" << client.session.to_json() << "|</p>";
client << "<p>session aaa:|" << client.session["aaa"].to_string() << "|</p>";
client << "<p>session_id:|" << client.get_session_id() << "|</p>";
client << "<p>cookie same:" << client.cookie["same"] << "</p>";
client << "<p>cookie ccbb:" << client.cookie["ccbb"] << "</p>";
std::string semid = client.get["sessionid"].as_string();
if (!semid.empty())
{
client.set_session_id(semid);
client << "<p>new session_id:|" << client.get_session_id() << "|</p>";
client << "<p>new session aaa:|" << client.session["aaa"] << "|</p>";
client << "<p>cookie same:" << client.cookie["same"] << "</p>";
client << "<p>cookie ccbb:" << client.cookie["ccbb"] << "</p>";
client << "<p>session aaa:|" << client.session.to_json() << "|</p>";
}
client.view("cookie/show");
return "";
}
save_session() save session values to disk or memory.
The framework has three built-in methods for handling HTML data transmission and processing, all of which are built-in micro objects of type http::obj_val, The three objects are client.get[] client.post[] client.files[]
URL parameter value transmission, use client.get[] get values, example http://www.xxx.com/aa?username=root&userid=1111 use client.get["username"].to_string() and client.get["userid"].to_int() get the values.
HTML form post data use client.post[] get the values
Example file www/default/testpost.html, The HTML code is as follows.
<form method="post" enctype="multipart/form-data" name="form1" id="form1" action="/tfilepost">
<p>
<label for="textfield">Text Field:</label>
<input type="text" name="textfield" id="textfield">
</p>
<p>
<label for="textfield">Text Field:</label>
<input type="text" name="aa[]" id="textfield1">
</p>
<p>
<label for="textfield">Text Field:</label>
<input type="text" name="aa[]" id="textfield2">
</p>
<p>
<label for="textfield">Text Field:</label>
<input type="text" name="aa[8]" id="textfield3">
</p>
<p>
<label for="textarea">Text Area:</label>
<textarea name="textarea" id="textarea"></textarea>
</p>
<p>
<label for="fileField">File:</label>
<input type="file" name="fileField" id="fileField">
</p>
<p> </p>
<p>
<input type="submit" name="submit" id="submit" value="Submit">
</p>
</form>
Server side receives form data code, Pay attention to the usage of the value of client.post["aa"].
Example file controller/src/testformpost.cpp
//@urlpath(null,tfilepost)
std::string testformmultipart(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "hello world! multipart/form-data ";
try
{
client << "<p>--------------</p>";
client << "<p>";
client << client.post.to_json();
client << "</p>";
client << "<p>textfield:" << client.post["textfield"] << "</p>";
client << "<p>aa[]:" << client.post["aa"][0] << "</p>";
client << "<p>aa[]:" << client.post["aa"][1] << "</p>";
client << "<p>aa[8]:" << client.post["aa"][8] << "</p>";
client << "<p>textarea:" << client.post["textarea"] << "</p>";
client << "<p>fileField:name:" << client.files["fileField"]["name"] << "</p>";
client << "<p>fileField:filename:" << client.files["fileField"]["filename"] << "</p>";
client << "<p>fileField:tempfile:" << client.files["fileField"]["tempfile"] << "</p>";
client << "<p>fileField:size:" << client.files["fileField"]["size"] << "</p>";
client << "<p>fileField:error:" << client.files["fileField"]["error"] << "</p>";
client << "<p>submit:" << client.post["submit"] << "</p>";
client << "<p>--------------</p>";
}
catch (std::exception &e)
{
client << "<p>" << e.what() << "</p>";
return "";
}
catch (const char *e)
{
client << "<p>" << e<< "</p>";
return "";
}
return "";
}
client.files["fileField"]["tempfile"] save to temporary path on the server side, use std::filesystem::rename(client.files["fileField"]["tempfile"].as_string(), newfilepath) function, Save to the correct server side location.
The data imported in the installation environment section.
The paozhu framework comes with built-in ORM objects and utilizes the built-in MySQL protocol library, eliminating the need for third-party libraries and facilitating integration with ORM. It also supports C++ Coroutine mode. The MySQL official client does not support Coroutines.
Config file at conf/orm.conf
[default]
type=main
host=127.0.0.1
port=3306
dbname=hello_world
user=benchmarkdbuser
password=benchmarkdbpass
pretable=
maxpool=5
dbtype=mysql
charset=utf8mb4
#ssl=ON
type=second
host=127.0.0.1
port=3306
dbname=hello_world
user=benchmarkdbuser
password=benchmarkdbpass
pretable=
maxpool=20
dbtype=mysql
charset=utf8mb4
#ssl=ON
type The type is Master-Slave Replication and cache synchronization can be developed in the future. Currently, master-slave is preferred. Host '127.0.0.1' fills in the local address, MySQL must be greater than or equal to version 8 because MySQL 9 does not support old authentication, So Paozhu C++ ORM only supports new authentication methods.
pretable It is a table prefix, sometimes only one database permission can store multiple businesses in one database.
dbtype Currently supports MySQL.
SSL can be opened, especially when connecting remotely, enabling encrypted connections. Local SSL connections will not be enabled.
The framework currently supports database to create cpp file conversion, so modifying the database can regenerate ORM files.
After the project is successfully compiled, the following method can be used to generate cpp files.
# ./bin/paozhu_cli
paozhu_cli(1495,0x7ff85ac38fc0) malloc: nano zone abandoned due to inability to reserve vm space.
model ο½ view | viewtocpp | control
Welcome to use cli to manage your MVC filesγ
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:
Enter m to select and generate ORM model file
paozhu_cli(1495,0x7ff85ac38fc0) malloc: nano zone abandoned due to inability to reserve vm space.
model ο½ view | viewtocpp | control
Welcome to use cli to manage your MVC filesγ
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:m
π current path: /Users/hzq/paozhu
1 hello_world
3 paozhu_docs
5 cppcms
select db index:
Enter 1 to select the hello-world database to generate the file.
paozhu_cli(1495,0x7ff85ac38fc0) malloc: nano zone abandoned due to inability to reserve vm space.
model ο½ view | viewtocpp | control
Welcome to use cli to manage your MVC filesγ
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:m
π current path: /Users/hzq/paozhu
1 hello_world
3 paozhu_docs
5 cppcms
select db index:1
show tables;
create fortune table to models π
create table metainfo file: /Users/hzq/paozhu/orm/include/fortune_base.h
create world table to models π
create table metainfo file: /Users/hzq/paozhu/orm/include/world_base.h
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:x
Enter x to exit
You can see the generated files in orm directory.
orm
βββ include
β βββ fortune_mysql.h
β βββ fortune_base.h
β βββ world_mysql.h
β βββ world_base.h
βββ orm.h
β
models
βββ Fortune.cpp
βββ World.cpp
βββ include
βββ Fortune.h
βββ World.h
The ORM directory is an entity file, consisting of two files: one is the end of the mysql.h for operating database query interfaces, and the other is the end of the data table entity mapping base.h, which is also an interface for operating query results. When updating this directory file, it will be overwritten because it corresponds one-to-one with the database table structure
The models directory is the business code file, and we actually manipulate the files in the models directory. These two files, one cpp and one h, are automatically generated, but they are only generated once and will not be overwritten. In our own project system, we can add business code to them.
For example
auto myworld = orm::World(); models directory World.h
This is the default database, such as the built-in CMS demo auto users = orm::cms::Sysuser();
orm is namespae, Mainly used for ORM business CMS is the orm.conf file database configuration tag and also the generated CMS namespace, which facilitates database isolation and transformation. If it is [default], default can be omitted
Why is it designed in this way? Actually, it is also a unique design of the Paozhu framework compared to other Framework.
So orm::cms::Sysuser(); Using CMS to isolate the 'sysuser' table entity C++ class will not conflict with tables with the same name in other databases.
For example, orm::erp::Sysuser(); ERP is an ERP database tag, and the name of its database is not important,
It doesn't matter if there is also a sysuser table.
The reason for this design is that it is a business system, a single server program that can have many databases, and their table names can have duplicate names. In the future, there can be different databases, such as MySQL PG, which can be distributed on other machines. They can mix clusters, and individual server programs can also be clustered, because they have built-in read-write separation, and the write servers can be the same, Querying can allocate database servers in a fixed or dynamic manner, and the framework can be suitable for large business systems. Currently, the focus is on integrating a server with a database and business programs.
ORM does not have multi table join queries and requires manual writing of multi table queries. This may result in one or more additional queries at the beginning of the design, mainly due to fast in and fast out, which makes it easy to hit the cache and is in line with the current storage computing separation design. It also performs smoothly under high pressure, and novice programmers will not encounter any problems when writing business code.
The table model has two important member variables
Table Data Structure and Data table entity structure
mysql> select * from world where 1 limit 1;
+----+--------------+
| id | randomNumber |
+----+--------------+
| 1 | 3562 |
+----+--------------+
Reference file orm/include/world_base.h
struct meta{
unsigned int id = 0;
int randomnumber = 0;
} data;
std::vector<world_base::meta> record;
//@urlpath(null,queries)
asio::awaitable<std::string> techempowerqueries(std::shared_ptr<httppeer> peer)
{
peer->type("application/json; charset=UTF-8");
peer->set_header("Date", get_gmttime());
unsigned int get_num = peer->get["queries"].to_int();
if (get_num == 0)
{
get_num = 1;
}
else if (get_num > 500)
{
get_num = 500;
}
auto myworld = orm::World();
myworld.record.reserve(get_num);
myworld.lock_conn();
for (unsigned int i = 0; i < get_num; i++)
{
myworld.wheresql.clear();
unsigned int rd_num = rand_range(1, 10000);
myworld.where("id", rd_num);
co_await myworld.async_fetch_append();
}
myworld.unlock_conn();
peer->output = myworld.to_json();
co_return "";
}
orm::World(); It is an ORM table model with default connections, so there is no need for defaultmyworld.lock_conn(); It's about fixing a connection, because using ORM connection pool method will rotate the use of connections.myworld.record.reserve(get_num); The std::vector reserveγmyworld.wheresql.clear(); Recommend using myworld.clearWhere() Method to clear conditionsγmyworld.where("id", rd_num); Add whereγco_await myworld.async_fetch_append(); It is an advanced usage that will attach data to the record object. Generally, co_await myworld.async_fetch(); is used` It will clear the record object and then reassign it.peer->output = myworld.to_json(); Directly output JSON to the browser.
Normal business code runs in a thread, one thread by one connect at a time, which is a classic way.
//@urlpath(admin_islogin,admin/addtopic)
std::string admin_addtopic(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
try
{
auto topicm = orm::cms::Topic();
topicm.where("userid", client.session["userid"].to_int()).asc("parentid").fetch();
client.val["list"].set_array();
obj_val temp;
for (unsigned int i = 0; i < topicm.record.size(); i++)
{
temp["id"] = topicm.record[i].topicid;
temp["parentid"] = topicm.record[i].parentid;
temp["value"] = topicm.record[i].title;
client.val["list"].push(temp);
}
}
catch (std::exception &e)
{
client.val["code"] = 1;
}
peer->view("admin/addtopic");
return "";
}
httppeer &client = peer->get_peer(); Reference connection object
auto topicm = orm::cms::Topic(); Create an ORM model object, with the model source file located in the models directory,Topic is the database table nameοΌIf a prefix is set, the framework will automatically remove it,topicm.where add sql whereοΌasc("parentid").fetch() order parentid asc query οΌ.fetch() send query commandγ
If you don't want so much data to be paginated and retrieved, especially for network services that require bandwidth or CPU resourcesοΌIt is best to use it as needed .select("fieldname1,fieldname2") Retrieve field data, use .limit(pos,offset) for range.
Use .page(page, 10, 5) It has been automatically added .limit(pos,offset) to sql string,now only input page number.
auto [bar_min, bar_max, current_page, total_page] = artmodel.page(page, 10, 5);
client.val["pageinfo"].set_object();
client.val["pageinfo"]["min"] = bar_min;
client.val["pageinfo"]["max"] = bar_max;
client.val["pageinfo"]["current"] = current_page;
client.val["pageinfo"]["total"] = total_page;
.page(page, 10, 5) Return four numerical values,bar_min bar_max The range is determined by the input of 5, It consists of two pages of windows, with 10 per page,
ORM where Other Methods
whereLike(fieldname,searchword) It's equivalent to and fieldname '%searchword%' alias
whereAnd It's equivalent to and fieldname = val alias
whereIn iIt's equivalent to and fieldname IN(val) alias
whereLT(const std::string &wq, const std::string &val) It's equivalent to and fieldname < val alias
whereLE(const std::string &wq, const std::string &val) It's equivalent to and fieldname <= val alias
whereOrLT(const std::string &wq, const std::string &val) It's equivalent to OR fieldname < val alias
whereOrLE(const std::string &wq, const std::string &val) It's equivalent to OR fieldname <= val alias
whereBT(const std::string &wq, const std::string &val) It's equivalent to and fieldname > val alias
whereBE(const std::string &wq, const std::string &val) It's equivalent to and fieldname >= val alias
whereOrBT(const std::string &wq, const std::string &val) It's equivalent to OR fieldname > val alias
whereOrBE(const std::string &wq, const std::string &val) It's equivalent to OR fieldname >= val alias
ORM Execute Command
Synchronous mode
fetch() Get multiple results to variable record
fetch_one() Get a result to variable data
save() Insert variable data into database table
update("field1,field2...") Update fields in variable data to the database
remove() Delete data from the table and match it with the where condition
Coroutine mode
async_fetch() Get multiple results to variable record using coroutine method
async_fetch_one() Get a result to variable data through coroutine method
async_save() Insert variable data into database table using coroutine method
async_update("field1,field2...") Update fields in variable data to the database using coroutine method
async_remove() Delete data from the table and match it with the where condition using coroutine method
For more information, please refer to the methods in the file ending with mysql.h in the ORM directory.
Reference documents controller/src/superadmin/supermain.cpp Example of data insertion.
//@urlpath(superadmin_islogin,superadmin/adduserpost)
std::string superadmin_adduserpost(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
unsigned int sid = client.post["sid"].to_int();
auto stinfo = orm::cms::Siteinfo();
stinfo.where("agentid", client.session["superid"].to_int()).whereAnd("sid", sid).fetch_one();
if (stinfo.data.userid == 0)
{
client.goto_url("/superadmin/welcome", 3, "No site information!");
return "";
}
std::string expdate = str_trim(client.post["expectdate"].to_string());
if (!check_isodate(expdate))
{
expdate = get_date("%Y-%m-%d");
}
auto uinfo = orm::cms::Sysuser();
std::string username = client.post["username"].to_string();
std::string password = client.post["password"].to_string();
uinfo.where("name", username).fetch_one();
std::string urlmsg = "/superadmin/adduser?sid=";
urlmsg.append(std::to_string(sid));
if (uinfo.data.adminid > 0)
{
client.goto_url(urlmsg, 3, "The username already exists!");
return "";
}
if (username.size() > 26 || username.size() < 4)
{
client.goto_url(urlmsg, 3, "The username must be between 4 and 20!");
return "";
}
if (password.size() > 20 || password.size() < 6)
{
client.goto_url(urlmsg, 3, "The password must be between 4 and 20!");
return "";
}
uinfo.clear();
uinfo.data.name = username;
uinfo.data.password = md5(password);
uinfo.data.isopen = client.post["isopen"].to_int() == 1 ? 1 : 0;
uinfo.data.companyid = sid;
uinfo.data.textword = password;
uinfo.data.created_at = timeid();
uinfo.data.enddate = strtotime(expdate);
uinfo.data.nickname = client.post["nickname"].to_string();
uinfo.data.mobile = client.post["mobile"].to_string();
uinfo.data.email = client.post["email"].to_string();
auto [effect_num, last_id] = uinfo.save();
if (effect_num == 0)
{
client.goto_url(urlmsg, 3, "Save error!");
return "";
}
urlmsg = "/superadmin/listuser?sid=";
urlmsg.append(std::to_string(sid));
client.goto_url(urlmsg, 3, "User added successfully!");
return "";
}
auto [effect_num, last_id] = uinfo.save(); The method returns a tuple value, where effectod_num represents the number of rows inserted, and last_id represents the self increasing id of the first inserted data.
Reference documents controller/src/admin/topics.cpp Example of data update.
//@urlpath(admin_isloginjson,admin/updatetopicview)
std::string admin_updatetopicview(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
try
{
auto topicm = orm::cms::Topic();
unsigned char isview = client.post["isview"].to_int() > 0 ? 1 : 0;
topicm.setIsview(isview);
topicm.where("userid", client.session["userid"].to_int()).whereAnd("topicid", client.get["id"].to_int());
client.val["code"] = topicm.update("isview");
client.val["msg"] = "ok";
}
catch (std::exception &e)
{
client.val["code"] = 0;
}
client.out_json();
return "";
}
client.val["code"] = topicm.update("isview"); update("filed1,field2...") Indicates the need to update fields
Paozhu, as a C++web framework framework, is the MVC pattern, which now refers to the view function of V
It is the content displayed to the browser, which is actually HTML content, similar to PHP scripting language output. So how do we operate C++? We put C++code in HTML and compile it into a C++. cpp file, which is resident in memory. This is also very fast. Hundreds of view files only require a few MB of memory, and one company project is about 1000 pages. It is estimated to have less than 10MB of memory, and if running in a scripting language, it will require a large amount of I/O to read files. The framework compiles it directly into a program without the need for I/O reading, which is extremely fast.
We are in the view directory
Create a login directory and then create a login.html file in the login directory
view
βββ login
βββ login.html
Probably like this
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.101.0">
<title>Content Management System</title>
<link href="/assets/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="text-center">
<%c include_sub("home/header",obj); %>
<div class="container text-center">
<div class="row">
<div class="col-3"></div>
<div class="col-6">
<h2 id="horizontal-form">CMS Admin </h2>
<form action="/cms/loginpost" method="post">
<div class="row mb-3">
<label for="username" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="username" name="username" value="admin">
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-sm-2 col-form-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="password" name="password" value="123456">
</div>
</div>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>
</div>
</div>
</div>
</body>
</html>
include_sub("home/header",obj);
The description also includes a subpage, and we will create a subpage directory called home in the view
view
βββ home
βΒ Β βββ header.html
βββ login
βββ login.html
Probably like this
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
border-right:1px solid #bbb;
}
li:last-child {
border-right: none;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #111;
}
.active {
background-color: #4CAF50;
}
</style>
<ul>
<li><a class="active" href="/">HOME</a></li>
<li><a href="#news">News</a></li>
<li><a href="#contact">Contact</a></li>
<li style="float:right"><a href="#about">About</a></li>
</ul>
<h3> subpage include_sub("home/header")Nav test |<%c echo<<vinfo.get["aa"].to_string(); %>| </h3>
echo<<vinfo.get["aa"].to_string();
This is the view tag C++code included in <%c %>
echo is a buffer, which is assembled together with the external HTML
Convert to C++ code
It's like this
echo<<"Nav test |";
echo<<vinfo.get["aa"].to_string();
echo<<"| </h3>";
You can refer to the viewsrc/view/home/head.cpp code for details.
Run in the root directory of the website
./bin/paozhu_cli
Select 'f' to generate files
./bin/paozhu_cli
./bin/paozhu_cli model ο½ view | viewtocpp | control
π Welcome to use cli to manage your MVC filesγ
(m)model (v)view (f)viewtocpp or (c)control,x or q to exit[input m|v|f|c|]:f
π current path: /Users/hzq/paozhu
1 [+] view/home/header.html time: 2022-12-12 13:1:41
2 [+] view/login/login.html time: 2022-12-12 13:1:26
please input number to parse to cpp file.
a update all , example: 1 3 4 5 enter key, q or x to exit,r reload
input number:a
We input f and compile it directly with the framework.
Then enter a, and all modified or newly created view files will be converted to cpp files
The file is saved in the viewsrc directory
viewsrc
βββ include
βΒ Β βββ regviewmethod.hpp
βΒ Β βββ viewsrc.h
βββ view
βββ home
βΒ Β βββ header.cpp
βββ login
βββ login.cpp
Probably like this
The login.html header.html files have been converted to cpp files
Two files in the include directory
Viewsrc.h is the definition of all view file functions
regviewmethod.hpp is a view registration function that the framework calls to register with the framework
#ifndef __HTTP_VIEWSRC_ALL_METHOD_H
#define __HTTP_VIEWSRC_ALL_METHOD_H
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include<string>
#include<map>
#include<functional>
#include "request.h"
#include "viewso_param.h"
namespace http {
namespace view {
namespace home{
std::string header(const struct view_param &vinfo,http::obj_val &obj);
}
namespace login{
std::string login(const struct view_param &vinfo,http::obj_val &obj);
}
}
}
#endif
You can see that the views are all placed in the namespace, so there is no conflict between the view files in one directory and those in other directories with the same name.
#ifndef __HTTP_REG_VIEW_METHOD_HPP
#define __HTTP_REG_VIEW_METHOD_HPP
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
#pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
#include<string>
#include<map>
#include<functional>
#include "request.h"
#include "viewso_param.h"
#include "viewmethold_reg.h"
#include "viewsrc.h"
namespace http
{
void _initview_method_regto(VIEW_REG &_viewmetholdreg)
{
//create time: Mon, 12 Dec 2022 05:01:53 GMT
_viewmetholdreg.emplace("home/header",http::view::home::header);
_viewmetholdreg.emplace("login/login",http::view::login::login);
}
}
#endif
Two functions are registered on the callback function, which means a view file is a function
Directory name and file name form a registration point
Login/login is a registration point
Controller peer->view("login/login"); calling method.
We have created the view, now let's test it
Follow the introductory chapter of Hello World
controller/srcDirectory create testview.cpp file
Probably like this
#include <chrono>
#include <thread>
#include "httppeer.h"
#include "testview.h"
namespace http
{
//@urlpath(null,testview)
std::string testloginview(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->getpeer();
client << " view test ";
// client << client.gethosturl();
// client<<"<p><a href=\""<<client.gethosturl()<<"/showcookie\">show</a></p>";
peer->view("login/login");
return "";
}
}
Map testview to testloginview function, the two names can be different.
http://localhost/testview You can access our testloginview function
Everything is ready, let's start compiling
Return to the project root directory and enter the build directory
cmake ..
make
Then go back to the root directory or open a new Terminal Command
cd ..
./bin/paozhu #In Linux use: sudo ./bin/paozhu
Open with a browser
http://localhost/testview
You can see the content of the view now
The framework has built-in structure and JSON conversion functions, reducing the workload of writing code
The framework provides two function operations json_encode json_decode that can be on structures or class objectsγ
std::string json_encode(obj)
std::string json_encode(std::vector<obj>)
Encoding objects as JSON supports arrays or objects
int json_decode(obj,json_string)
int json_decode(std::vector<obj>,json_string)
Decoding JSON strings to objects
common/json_reflect_headers.h It is the header file referenced by all reflection objects
This file was generated bypaozhu_cli
You need to create the corresponding type file in the libs directory, and then run paozhu_cli to generate it. The framework directly analyzes the type file to generate the corresponding code. Currently, it supports commonly used structures, which can be nested in multiple layers or reference structures that are also reflective.
Create test file libs/department/department_type.h
#ifndef LIBS_DEPARTMENT_TYPE_H
#define LIBS_DEPARTMENT_TYPE_H
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <sstream>
#include "unicode.h"
namespace psy
{
//@reflect json to_json from_json
struct department_outjson_t
{
unsigned int id = 0;
unsigned int key = 0;
unsigned int value = 0;
unsigned int parentid = 0;
unsigned int bianzhi_num = 0;
bool isopen = true;
bool _is_use = false;
std::string title;
std::string desc;
std::vector<department_outjson_t> children;
std::string to_json(std::string aa="",int offset=0);
unsigned int from_json()
{
//**aaaaa
/*
aaaa [][}]
*/
char b='}';
if(b=='{')
{
std::cout<<"assss}sss}{ssss}sss}"<<std::endl;
}
return 0;
}
};
//@reflect json to_json from_json
struct department_listoutjson_t
{
unsigned int code=0;
struct department_tables
{
std::vector<department_outjson_t> list;
unsigned int total=0;
} data;
std::vector<std::vector<std::string>> names;
};
} // namespace psy
#endif
//@reflect json It is a framework analysis source code extraction flag, which must be a struct or class starting with this, otherwise it will not work.
We create two structures, with one nested within the other, to facilitate business code operations. to_json from_json is a testing framework that analyzes whether the source code correctly skips these functions
Variables starting with an underscore will not be output to a JSON string
json_encode and json_decode
sudo ./bin/paozhu_cli
./bin/paozhu_cli model | view | viewtocpp | control
π Welcome to use cli to manage your MVC filesγ
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:
Input j
You can see the generated file now
1 /Users/hzq/paozhu/libs/department/department_type_jsonreflect.cpp
(m)model (v)view (f)viewtocpp or (c)control , (j)son ,x or q to exit[input m|v|f|c|j|]:
Reference controller/src/testjsonreflect.cpp
#include <chrono>
#include <thread>
#include "httppeer.h"
#include "testjsonreflect.h"
#include "json_reflect_headers.h"
#include "array_to_tree.h"
namespace http
{
//@urlpath(null,testjsonreflect)
std::string testjsonreflect(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->getpeer();
// client << " json reflect 𧨠Paozhu c++ web framework ";
psy::department_outjson_t deps_json_one;
std::vector<psy::department_outjson_t> depsjsonlist;
deps_json_one.id = 38;
deps_json_one.key = 38;
deps_json_one.value = 38;
deps_json_one.parentid = 0;
deps_json_one.isopen = true;
deps_json_one.title = "First Item";
deps_json_one.desc = "First Item";
deps_json_one.bianzhi_num = 12;
depsjsonlist.push_back(deps_json_one);
deps_json_one.id = 48;
deps_json_one.key = 48;
deps_json_one.value = 48;
deps_json_one.parentid = 0;
deps_json_one.isopen = true;
deps_json_one.title = "Second Item";
deps_json_one.desc = "Second memo";
deps_json_one.bianzhi_num = 16;
depsjsonlist.push_back(deps_json_one);
deps_json_one.id = 58;
deps_json_one.key = 58;
deps_json_one.value = 58;
deps_json_one.parentid = 48;
deps_json_one.isopen = true;
deps_json_one.title = "Three Item";
deps_json_one.desc = "Three memo";
deps_json_one.bianzhi_num = 22;
depsjsonlist.push_back(deps_json_one);
psy::department_listoutjson_t depout_data;
array_to_tree<psy::department_outjson_t>(depout_data.data.list, depsjsonlist);
depout_data.code = 0;
// depout_data.data.list=depsjsonlist; //has array_to_tree
depout_data.data.total = 0;
std::string jsondep_str = psy::json_encode(depout_data);
psy::department_listoutjson_t new_depout_data;
psy::json_decode(new_depout_data, jsondep_str);
std::cout << "data.list:" << depout_data.data.list.size() << std::endl;
std::cout << "data.list:" << new_depout_data.data.list.size() << std::endl;
std::string jsondep_str_temp = psy::json_encode(new_depout_data);
client << jsondep_str_temp;
client.out_jsontype();
return "";
}
} // namespace http
We first convert the array into a tree, and then encode the test object into a JSON string
std::string jsondep_str = psy::json_encode(depout_data);
Then decode the jsondep_str variable to the new object
psy::json_decode(new_depout_data, jsondep_str);
And then we'll do it again std::string jsondep_str_temp = psy::json_encode(new_depout_data);
Then output jsondep_str_temp, See if it meets our expectations
Example file controller/src/testcowaitclient.cpp
This is the synchronous usage method
//@urlpath(null,testcowaitclient5)
std::string testhttpclient_cowait_post(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "hello world! test testhttpclient_cowait_body";
std::shared_ptr<http::client> a = std::make_shared<http::client>();
a->get("http://www.xxxxxx.net/");
a->add_header("Connection", "keep-alive");
a->send();
if (a->get_status() == 200)
{
// client << a->getBody();
std::cout << "http://www.xxxxxx.net/login.php" << std::endl;
a->post("http://www.xxxxxx.net/login.php");
a->add_header("Connection", "keep-alive");
a->data["user_name"] = "admin";
a->data["user_pass"] = "123456";
a->data["action"] = "login";
a->send();
if (a->get_status() == 200)
{
//client << a->getBody();
std::cout << "http://www.xxxxxx.net/main.php" << std::endl;
a->get("http://www.xxxxxx.net/main.php");
a->requst_clear();
a->add_cookie("PHPSESSID", a->state.cookie["PHPSESSID"]);
a->add_header("Connection", "Close");
a->send();
if (a->get_status() == 200)
{
std::cout << a->get_body() << std::endl;
//client << a->getBody();
}
}
}
else
{
client << a->error_msg;
client << "+++++not content++++";
}
return "";
}
This is the Coroutine usage method
//@urlpath(null,testcowaitclient22)
asio::awaitable<std::string> testhttpclient22_cowait_body(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "hello world! co_await test localhost http://127.0.0.1addpost.html ";
std::shared_ptr<http::client> a = std::make_shared<http::client>();
a->get("http://127.0.0.1/addpost.html");
co_await a->async_send();
if (a->get_status() == 200)
{
client << a->get_body();
}
else
{
client << "+++++not content++++";
}
co_return "";
}
the Coroutine use async_ prefix.
The framework has enabled ASAN, and if it is detected by ASAN during debugging, it will indicate which line is problematic.
The g++ compilation flags have been enabled with -Wall -Werror -O3 -fsanitize=address, GCC-15 can detect common errors, such as returning temporary variable address errors.
In the production environment, the absence of output reasons makes it difficult to see the error message content.
You can refer to this CMakeLists.txt Use (-g) to retain debugging information in the product environment.
The compilation parameters for the production environment.
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release #OR cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_GD=ON
make -j8
Need to install gdb and coredumpctl
sudo apt install systemd-coredump gdb # Debian/Ubuntu
The framework will be checked every minute, at log directory have a restart_server file
If we need to retrieve a sample of the process stack, we can touch log/start_Server so that the process can restart
have access to pstree -aup view process status
If the sample has already been taken, I need to analyze the process stack situation
view coredump info
coredumpctl list
coredumpctl info
Looking directly online 1524 pid, That's what I saw beforepstree up
coredumpctl gdb 1524
bt can be view it
After entering, we can save it and view it in the currentthread_info.txtfile.
set logging file thread_info.txt
set logging enabled on
thread apply all bt
set logging enabled off
The core coredump file is usually located in
/var/lib/systemd/coredump
If compiled using -g, you will see something similar to the following, usually displaying what various threads are currently doing
Thread 17 (Thread 0x7fd9f16b9640 (LWP 10658)):
#0 http::hash_objkey (key="data:image/php;base64,PD9waHAgJE8wME9PMD11cmxkZWNvZGUoIiU3OCUzNCU2My"...) at /www/paozhu/vendor/httpserver/src/request.cpp:54
#1 0x000000000060032f in http::obj_val::operator[] (this=0x7fd9cc01e520, key="data:image/php;base64,PD9waHAgJE8wME9PMD11cmxkZWNvZGUoIiU3OCUzNCU2MyU2RiUyRiU3MCUzOSU3OSU3MSU2RSU2NCUyRCU2QyU3MiU2QiU2NCU2NyU1RiU"...) at /www/paozhu/vendor/httpserver/src/request.cpp:315
#2 0x00000000005a990c in http::httpparse::procssxformurlencoded (this=0x7fd9b400d370) at /www/paozhu/vendor/httpserver/src/http_parse.cpp:389
#3 0x00000000005b1bfa in http::httpparse::process (this=0x7fd9b400d370, buffer=0x2e223c0 "POST /public/static/lib/webuploader/0.1.5/server/preview.php HTTP/1.1\r\nHost: www. paozhu.org\r\nUser-Agent: Mozilla/5.0 AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0\r\nAccept-Encoding: gzip, deflate"..., buffersize=964) at /www/paozhu/vendor/httpserver/src/http_parse.cpp:2823
There is an error in log/error.log: Too many open files
The system limits the number of open sockets, which is set to 1024 by default. You can check this using ulimit -n. Alternatively, you can use sudo ls -l /proc/49121(pid)/fd or sudo ls -l /proc/49121(pid)/fd | wc -l to view the file descriptors (fd) already opened by process 49121
Modify the default number of file descriptors (fd) in the system,sudo vim /etc/security/limits.conf
# Format: User/Group Type Resource Value
* soft nofile 65535
* hard nofile 65535
Restart the system to see the changes.
Linux Out-Of-Memory killer issue
When there is a sudden increase in access volume, a large amount of memory is used, and the process exhausts all system memory. The system will then activate the cleanup program, so the system log "/var/log/messages" will display error messages like "Out of Memory: Kill process 1416222(paozhu)".
Confirm the specific time point when the Out-Of-Memory (OOM) error occurred. 'paozhu' is the process name.
sudo dmesg -T | grep -C 5 "paozhu"
OR
dmesg -T | grep -i "killed process" | grep paozhu
Information similar to the following
__vm_enough_memory: pid: 1416222, comm: paozhu, not enough memory for the allocation
Solution: Increase Swap space
#Check the existing Swap space
#sudo swapon --show
NAME TYPE SIZE USED PRIO
/swap.img file 4G 672.3M -2
#Increase the Swap space to 8G or 16G
# 1. Close the existing Swap file
sudo swapoff /swap.img
# 2. Delete the old Swap file
sudo rm /swap.img
# 3. Create a new Swap file (taking 8G as an example)
sudo fallocate -l 8G /swap.img
# 4. Set security permissions
sudo chmod 600 /swap.img
# 5. Format and enable
sudo mkswap /swap.img
sudo swapon /swap.img
#6. Verify the results
sudo swapon --show
Check the automatic mount configuration at startup. If Swap space was not used before, it may not be present and needs to be added.
cat /etc/fstab
echo '/swap.img none swap sw 0 0' | sudo tee -a /etc/fstab
After cloning the Paozhu framework from GitHub, you usually want to strip out the example business code and keep only the framework core plus the hello route as a minimal starting scaffold. Follow this recommended fourβstep cleanup flow.
common/ registration filesSix files need to be slimmed down: keep #include "httppeer.h", remove every other #include, and empty the bodies of registration functions such as _initauto_control_httpmethodregto() (keep the function signatures).
| File | Purpose |
|---|---|
common/autocontrolmethod.hpp | Controller method registration (auto-extracted by the build system from controller/src) |
common/autorestfulpaths.hpp | RESTful path registration |
common/reghttpmethod.hpp | HTTP method registration |
common/reghttpmethod_pre.hpp | HTTP pre-filter registration |
common/sockets_method_reg.hpp | Socket method registration |
common/websockets_method_reg.hpp | WebSocket method registration |
Each file should end up looking like:
#include "httppeer.h"
void _initauto_control_httpmethodregto(std::map<std::string, regmethold_t> &methodcallback)
{
// empty implementation
}
// other registration functions similarly cleared
rm -rf models/* schema/* orm/*
These three directories hold database models and generated ORM code β they are not needed for a clean scaffold. Regenerate later with bin/paozhu_cli orm <tag> when you enable a database.
Do not rm -rf the whole viewsrc/ directory β the registration headers under viewsrc/include/ must be kept as skeletons. Three sub-steps:
3.1 Remove the view source and template directories
rm -rf viewsrc/view/ view/
viewsrc/view/ holds the C++ view implementations; view/ holds the HTML templates. Both are example code and can be deleted directly.
3.2 Empty the namespace view block in viewsrc/include/viewsrc.h
Open viewsrc/include/viewsrc.h, delete every inner namespace (admin, cms, home, login, superadmin, techempower) and their function declarations inside namespace http { namespace view { ... } }. Keep the outer namespace wrappers and all #include lines. The file should end up like:
#ifndef __HTTP_VIEWSRC_ALL_METHOD_H
#define __HTTP_VIEWSRC_ALL_METHOD_H
// ... includes unchanged ...
namespace http {
namespace view {
}
}
#endif
3.3 Empty the function body in viewsrc/include/regviewmethod.hpp
Open viewsrc/include/regviewmethod.hpp, delete every _viewmetholdreg.emplace(...) line inside _initview_method_regto. Keep the function signature, namespace, and all #include lines. The file should end up like:
namespace http
{
void _initview_method_regto(VIEW_REG &_viewmetholdreg)
{
// empty body
}
}
This removes all example business code while preserving the full view-registration skeleton. When you add new views later, just declare functions inside namespace view and register them in regviewmethod.hpp.
testhello)rm -rf controller/include/*
find controller/src -type f ! -name 'testhello.cpp' -delete
The remaining controller/src/testhello.cpp carries the //@urlpath(null,hello) annotation and is the single entry point of the hello world route. Once you rebuild, http://127.0.0.1/hello is live again.
Rebuild the project after the four steps to get a clean scaffold:
cd build && cmake .. && make -j$(sysctl -n hw.ncpu)
Run ./bin/paozhu and open http://127.0.0.1/hello β you should see Hello world! 𧨠Paozhu c++ web framework, which confirms the framework core is intact.
Enable ENABLE_BENCHMARK because the project has rate limiting turned on by default:
cd build && cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_BENCHMARK=ON && make -j$(sysctl -n hw.ncpu)
Small tips from the author: when writing business code in C++, don't reach for raw new/delete β STL containers and smart pointers are enough. Old-school programmers love to wring every cycle out of the CPU, but modern compilers optimize aggressively anyway, so just write code that feels natural.
Web development is about shipping business logic, not showing off. Some C++ developers can't resist sprinkling template metaprogramming into business code β that's not a flex, it's a red flag. Save metaprogramming and template tricks for library and framework code, not where it'll be read three months from now by someone who just wants to fix a bug.
Web projects are fast-moving, and a lot of "clean" code turns into legacy. But here's the thing: even messy C++ code is fast. Business requirements change faster than code rots, so the "garbage" may never need optimizing β it'll get replaced first.
Many people think you can't build serious web backends in C++. Paozhu proves you can. If you're doing plain CRUD, other languages are fine too. But C++ opens doors no other web language can: FFmpeg for multimedia, WebRTC, industrial-grade OpenCV, face recognition, IoT β all first-class citizens.
Paozhu does not rely too much on third parties, reducing the risk of middleware attacks.