asyncgi是C ++ 17异步微框架,用于创建Web应用程序与任何支持FastCGI协议的HTTP服务器接口。它旨在提供一种现代化的CGI方式,并在C ++,多线程支持和干净而简单的API中使用自定义性能FastCGI实现:
asyncgi .h>
namespace http = asyncgi ::http;
int main()
{
auto io = asyncgi ::IO{};
auto router = asyncgi ::Router{io};
router.route(\”/\”, http::RequestMethod::Get).process(
[](const asyncgi ::Request&)
{
return http::Response{\”Hello world\”};
});
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
# include < asyncgi / asyncgi .h > namespace http = asyncgi ::http; int main () { auto io = asyncgi ::IO{}; auto router = asyncgi ::Router{io}; router. route ( \" / \" , http::RequestMethod::Get). process ( []( const asyncgi ::Request&) { return http::Response{ \" Hello world \" }; }); auto server = asyncgi ::Server{io, router}; server. listen ( \" /tmp/fcgi.sock \" ); io. run (); }
目录
- 用法
- 联系
- 请求处理器
- 路由器
- 路由参数
- 路线上下文
- 路线匹配者
- 完整的留言簿示例
- 计时器
- 客户
- 执行ASIO任务
- 展示柜
- 发展状况
- 安装
- 建筑物例子
- 运行功能测试
- 执照
用法
联系
使用asyncgi开发的Web应用程序需要使用Web服务器处理HTTP请求建立FASTCGI连接。大多数流行的服务器提供此功能,例如NGINX可以使用以下配置:
server {
listen 8088;
server_name localhost;
location / {
try_files $uri @fcgi;
}
location @fcgi {
fastcgi_pass unix:/tmp/fcgi.sock;
#or using a TCP socket
#fastcgi_pass localhost:9000;
include fastcgi_params;
}
}
asyncgi支持UNIX domain和TCP插座,以打开FastCGI连接。
请求处理器
为了处理请求,有必要提供满足RequestProcessor要求的函数或功能对象。这意味着该函数必须与以下签名之一无关:
-
http::Response (const asyncgi ::Request&) -
void (const asyncgi ::Request&, asyncgi ::Responder&)。
例子
asyncgi .h>
namespace http = asyncgi ::http;
http::Response guestBookPage(const asyncgi ::Request& request)
{
if (request.path() == \”/\”)
return {R\”(
<h1>Guest book</h1>
<p>No messages</p>
)\”};
return http::ResponseStatus::_404_Not_Found;
}
int main()
{
auto io = asyncgi ::IO{};
auto server = asyncgi ::Server{io, guestBookPage};
//Listen for FastCGI connections on UNIX domain socket
server.listen(\”/tmp/fcgi.sock\”);
//or over TCP
//server.listen(\”127.0.0.1\”, 9088);
io.run();
return 0;
}\”>
// /examples/example_request_processor.cpp // / # include < asyncgi / asyncgi .h > namespace http = asyncgi ::http; http::Response guestBookPage ( const asyncgi ::Request& request) { if (request. path () == \" / \" ) return { R\"( <h1>Guest book</h1> <p>No messages</p> )\" }; return http::ResponseStatus::_404_Not_Found; } int main () { auto io = asyncgi ::IO{}; auto server = asyncgi ::Server{io, guestBookPage}; // Listen for FastCGI connections on UNIX domain socket server. listen ( \" /tmp/fcgi.sock \" ); // or over TCP // server.listen(\"127.0.0.1\", 9088); io. run (); return 0 ; }
在这里, guestBookPage函数用作请求处理器。实施它的另一种方法是接受对asyncgi ::Responder对象的引用,该对象可用于手动发送响应:
asyncgi ::Responder& responder)
{
if (request.path() == \”/\”)
responder.send(R\”(
<h1>Guest book</h1>
<p>No messages</p>
)\”);
return responder.send(http::ResponseStatus::_404_Not_Found);
}\”>
void guestBookPage ( const asyncgi ::Request& request, asyncgi ::Responder& responder) { if (request. path () == \" / \" ) responder. send ( R\"( <h1>Guest book</h1> <p>No messages</p> )\" ); return responder. send (http::ResponseStatus::_404_Not_Found); }
这种方法倾向于更详细和容易出错,因此,只有在从请求处理器启动asyncgi操作需要启动异步操作的需要时,才能使用它。这些情况在本文档的后期部分涵盖。
路由器
可以在asyncgi ::Router对象中注册多个请求处理器,并将其与请求中指定的路径匹配。 asyncgi ::Router本身满足RequestProcessor要求。
如果请求处理需要多个线程,则可以将所需数量的工人传递给asyncgi ::IO对象的构造函数。在这种情况下,用户必须确保保护请求处理器中的任何共享数据都受到保护,避免并发读/写入访问。
例子
asyncgi .h>
#include <mutex>
namespace http = asyncgi ::http;
using namespace std::string_literals;
class GuestBookState {
public:
std::vector<std::string> messages()
{
auto lock = std::scoped_lock{mutex_};
return messages_;
}
void addMessage(const std::string& msg)
{
auto lock = std::scoped_lock{mutex_};
messages_.emplace_back(msg);
}
private:
std::vector<std::string> messages_;
std::mutex mutex_;
};
class GuestBookPage {
public:
GuestBookPage(GuestBookState& state)
: state_(&state)
{
}
http::Response operator()(const asyncgi ::Request&)
{
auto messages = state_->messages();
auto page = \”<h1>Guest book</h1>\”s;
if (messages.empty())
page += \”<p>No messages</p>\”;
else
for (const auto& msg : messages)
page += \”<p>\” + msg + \”</p>\”;
page += \”<hr>\”;
page += \”<form method=\\\”post\\\” enctype=\\\”multipart/form-data\\\”>\”
\”<label for=\\\”msg\\\”>Message:</label>\”
\”<input id=\\\”msg\\\” name=\\\”msg\\\” value=\\\”\\\”>\”
\”<input value=\\\”Submit\\\” data-popup=\\\”true\\\” type=\\\”submit\\\”>\”
\”</form>\”;
return page;
}
private:
GuestBookState* state_;
};
class GuestBookAddMessage {
public:
GuestBookAddMessage(GuestBookState& state)
: state_(&state)
{
}
http::Response operator()(const asyncgi ::Request& request)
{
state_->addMessage(std::string{request.formField(\”msg\”)});
return http::Redirect{\”/\”};
}
private:
GuestBookState* state_;
};
int main()
{
auto io = asyncgi ::IO{4}; //4 threads processing requests
auto state = GuestBookState{};
auto router = asyncgi ::Router{io};
router.route(\”/\”, http::RequestMethod::Get).process<GuestBookPage>(state);
router.route(\”/\”, http::RequestMethod::Post).process<GuestBookAddMessage>(state);
router.route().set(http::Response{http::ResponseStatus::_404_Not_Found, \”Page not found\”});
//Alternatively, it\’s possible to pass arguments for creation of http::Response object to the set() method.
//router.route().set(http::ResponseStatus::Code_404_Not_Found, \”Page not found\”);
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
// /examples/example_router.cpp // / # include < asyncgi / asyncgi .h > # include < mutex > namespace http = asyncgi ::http; using namespace std ::string_literals ; class GuestBookState { public: std::vector<std::string> messages () { auto lock = std::scoped_lock{mutex_}; return messages_; } void addMessage ( const std::string& msg) { auto lock = std::scoped_lock{mutex_}; messages_. emplace_back (msg); } private: std::vector<std::string> messages_; std::mutex mutex_; }; class GuestBookPage { public: GuestBookPage (GuestBookState& state) : state_(&state) { } http::Response operator ()( const asyncgi ::Request&) { auto messages = state_-> messages (); auto page = \" <h1>Guest book</h1> \" s; if (messages. empty ()) page += \" <p>No messages</p> \" ; else for ( const auto & msg : messages) page += \" <p> \" + msg + \" </p> \" ; page += \" <hr> \" ; page += \" <form method= \\\" post \\\" enctype= \\\" multipart/form-data \\\" > \" \" <label for= \\\" msg \\\" >Message:</label> \" \" <input id= \\\" msg \\\" name= \\\" msg \\\" value= \\\"\\\" > \" \" <input value= \\\" Submit \\\" data-popup= \\\" true \\\" type= \\\" submit \\\" > \" \" </form> \" ; return page; } private: GuestBookState* state_; }; class GuestBookAddMessage { public: GuestBookAddMessage (GuestBookState& state) : state_(&state) { } http::Response operator ()( const asyncgi ::Request& request) { state_-> addMessage (std::string{request. formField ( \" msg \" )}); return http::Redirect{ \" / \" }; } private: GuestBookState* state_; }; int main () { auto io = asyncgi ::IO{ 4 }; // 4 threads processing requests auto state = GuestBookState{}; auto router = asyncgi ::Router{io}; router. route ( \" / \" , http::RequestMethod::Get). process <GuestBookPage>(state); router. route ( \" / \" , http::RequestMethod::Post). process <GuestBookAddMessage>(state); router. route (). set (http::Response{http::ResponseStatus::_404_Not_Found, \" Page not found \" }); // Alternatively, it\'s possible to pass arguments for creation of http::Response object to the set() method. // router.route().set(http::ResponseStatus::Code_404_Not_Found, \"Page not found\"); auto server = asyncgi ::Server{io, router}; server. listen ( \" /tmp/fcgi.sock \" ); io. run (); }
路由参数
当使用带有正则表达式的asyncgi ::Router时,请求处理器必须满足ParametrizedRequestProcessor要求。这意味着一个函数对象必须与以下签名之一无关:
-
http::Response void(const TRouteParams&..., const asyncgi ::Request&) -
void (const TRouteParams&..., const asyncgi ::Request&, asyncgi ::Responder&)
TRouteParams表示从正则表达式的捕获组生成的零或更多参数。例如,可以使用http::Response (int age, string name, const asyncgi ::Request&)签名来处理由asyncgi ::rx{\"/person/(\\\\w+)/age/(\\\\d+)\"}匹配的请求。
ParametrizedRequestProcessor下面的示例GuestBookRemoveMessage
例子
asyncgi .h>
#include <mutex>
using namespace asyncgi ;
using namespace std::string_literals;
class GuestBookState {
public:
std::vector<std::string> messages()
{
auto lock = std::scoped_lock{mutex_};
return messages_;
}
void addMessage(const std::string& msg)
{
auto lock = std::scoped_lock{mutex_};
messages_.emplace_back(msg);
}
void removeMessage(int index)
{
auto lock = std::scoped_lock{mutex_};
if (index < 0 || index >= static_cast<int>(messages_.size()))
return;
messages_.erase(std::next(messages_.begin(), index));
}
private:
std::vector<std::string> messages_;
std::mutex mutex_;
};
std::string makeMessage(int index, const std::string& msg)
{
return msg + R\”(<form action=\”/delete/)\” + std::to_string(index) +
R\”(\” method=\”post\”> <input value=\”Delete\” type=\”submit\”> </form></div>)\”;
}
class GuestBookPage {
public:
explicit GuestBookPage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(const asyncgi ::Request&)
{
auto messages = state_->messages();
auto page = \”<h1>Guest book</h1>\”s;
if (messages.empty())
page += \”<p>No messages</p>\”;
else
for (auto i = 0; i < static_cast<int>(messages.size()); ++i)
page += \”<p>\” + makeMessage(i, messages.at(i)) + \”</p>\”;
page += \”<hr>\”;
page += \”<form method=\\\”post\\\” enctype=\\\”multipart/form-data\\\”>\”
\”<label for=\\\”msg\\\”>Message:</label>\”
\”<input id=\\\”msg\\\” name=\\\”msg\\\” value=\\\”\\\”>\”
\”<input value=\\\”Submit\\\” data-popup=\\\”true\\\” type=\\\”submit\\\”>\”
\”</form>\”;
return page;
}
private:
GuestBookState* state_;
};
class GuestBookAddMessage {
public:
explicit GuestBookAddMessage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(const asyncgi ::Request& request)
{
state_->addMessage(std::string{request.formField(\”msg\”)});
return http::Redirect{\”/\”};
}
private:
GuestBookState* state_;
};
class GuestBookRemoveMessage {
public:
explicit GuestBookRemoveMessage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(int index, const asyncgi ::Request&)
{
state_->removeMessage(index);
return http::Redirect{\”/\”};
}
private:
GuestBookState* state_;
};
int main()
{
auto io = asyncgi ::IO{4};
auto state = GuestBookState{};
auto router = asyncgi ::Router{io};
router.route(\”/\”, http::RequestMethod::Get).process<GuestBookPage>(state);
router.route(\”/\”, http::RequestMethod::Post).process<GuestBookAddMessage>(state);
router.route( asyncgi ::rx{\”/delete/(.+)\”}, http::RequestMethod::Post).process<GuestBookRemoveMessage>(state);
router.route().set(http::ResponseStatus::_404_Not_Found, \”Page not found\”);
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
// /examples/example_route_params.cpp // / # include < asyncgi / asyncgi .h > # include < mutex > using namespace asyncgi ; using namespace std ::string_literals ; class GuestBookState { public: std::vector<std::string> messages () { auto lock = std::scoped_lock{mutex_}; return messages_; } void addMessage ( const std::string& msg) { auto lock = std::scoped_lock{mutex_}; messages_. emplace_back (msg); } void removeMessage ( int index) { auto lock = std::scoped_lock{mutex_}; if (index < 0 || index >= static_cast < int >(messages_. size ())) return ; messages_. erase ( std::next (messages_. begin (), index)); } private: std::vector<std::string> messages_; std::mutex mutex_; }; std::string makeMessage ( int index, const std::string& msg) { return msg + R\"( <form action=\"/delete/ )\" + std::to_string (index) + R\"( \" method=\"post\"> <input value=\"Delete\" type=\"submit\"> </form></div> )\" ; } class GuestBookPage { public: explicit GuestBookPage (GuestBookState& state) : state_{&state} { } http::Response operator ()( const asyncgi ::Request&) { auto messages = state_-> messages (); auto page = \" <h1>Guest book</h1> \" s; if (messages. empty ()) page += \" <p>No messages</p> \" ; else for ( auto i = 0 ; i < static_cast < int >(messages. size ()); ++i) page += \" <p> \" + makeMessage (i, messages. at (i)) + \" </p> \" ; page += \" <hr> \" ; page += \" <form method= \\\" post \\\" enctype= \\\" multipart/form-data \\\" > \" \" <label for= \\\" msg \\\" >Message:</label> \" \" <input id= \\\" msg \\\" name= \\\" msg \\\" value= \\\"\\\" > \" \" <input value= \\\" Submit \\\" data-popup= \\\" true \\\" type= \\\" submit \\\" > \" \" </form> \" ; return page; } private: GuestBookState* state_; }; class GuestBookAddMessage { public: explicit GuestBookAddMessage (GuestBookState& state) : state_{&state} { } http::Response operator ()( const asyncgi ::Request& request) { state_-> addMessage (std::string{request. formField ( \" msg \" )}); return http::Redirect{ \" / \" }; } private: GuestBookState* state_; }; class GuestBookRemoveMessage { public: explicit GuestBookRemoveMessage (GuestBookState& state) : state_{&state} { } http::Response operator ()( int index, const asyncgi ::Request&) { state_-> removeMessage (index); return http::Redirect{ \" / \" }; } private: GuestBookState* state_; }; int main () { auto io = asyncgi ::IO{ 4 }; auto state = GuestBookState{}; auto router = asyncgi ::Router{io}; router. route ( \" / \" , http::RequestMethod::Get). process <GuestBookPage>(state); router. route ( \" / \" , http::RequestMethod::Post). process <GuestBookAddMessage>(state); router. route ( asyncgi ::rx{ \" /delete/(.+) \" }, http::RequestMethod::Post). process <GuestBookRemoveMessage>(state); router. route (). set (http::ResponseStatus::_404_Not_Found, \" Page not found \" ); auto server = asyncgi ::Server{io, router}; server. listen ( \" /tmp/fcgi.sock \" ); io. run (); }
使用std::stringstream将正则表达捕获组转化为请求处理器参数。为了支持使用用户定义的参数类型的请求处理器,有必要提供asyncgi ::config::StringConverter类模板的专业化。已修改了上一个示例以重新格式化GuestBookRemoveMessage请求处理器,以将MessageNumber结构用作请求处理器参数:
例子
asyncgi .h>
#include <mutex>
using namespace asyncgi ;
using namespace std::string_literals;
struct MessageNumber {
int value;
};
template<>
struct asyncgi ::config::StringConverter<MessageNumber> {
static std::optional<MessageNumber> fromString(const std::string& data)
{
return MessageNumber{std::stoi(data)};
}
};
class GuestBookState {
public:
std::vector<std::string> messages()
{
auto lock = std::scoped_lock{mutex_};
return messages_;
}
void addMessage(const std::string& msg)
{
auto lock = std::scoped_lock{mutex_};
messages_.emplace_back(msg);
}
void removeMessage(int index)
{
auto lock = std::scoped_lock{mutex_};
if (index < 0 || index >= static_cast<int>(messages_.size()))
return;
messages_.erase(std::next(messages_.begin(), index));
}
private:
std::vector<std::string> messages_;
std::mutex mutex_;
};
std::string makeMessage(int index, const std::string& msg)
{
return msg + R\”(<form action=\”/delete/)\” + std::to_string(index) +
R\”(\” method=\”post\”> <input value=\”Delete\” type=\”submit\”> </form></div>)\”;
}
class GuestBookPage {
public:
explicit GuestBookPage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(const asyncgi ::Request&)
{
auto messages = state_->messages();
auto page = \”<h1>Guest book</h1>\”s;
if (messages.empty())
page += \”<p>No messages</p>\”;
else
for (auto i = 0; i < static_cast<int>(messages.size()); ++i)
page += \”<p>\” + makeMessage(i, messages.at(i)) + \”</p>\”;
page += \”<hr>\”;
page += \”<form method=\\\”post\\\” enctype=\\\”multipart/form-data\\\”>\”
\”<label for=\\\”msg\\\”>Message:</label>\”
\”<input id=\\\”msg\\\” name=\\\”msg\\\” value=\\\”\\\”>\”
\”<input value=\\\”Submit\\\” data-popup=\\\”true\\\” type=\\\”submit\\\”>\”
\”</form>\”;
return page;
}
private:
GuestBookState* state_;
};
class GuestBookAddMessage {
public:
explicit GuestBookAddMessage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(const asyncgi ::Request& request)
{
state_->addMessage(std::string{request.formField(\”msg\”)});
return http::Redirect{\”/\”};
}
private:
GuestBookState* state_;
};
class GuestBookRemoveMessage {
public:
explicit GuestBookRemoveMessage(GuestBookState& state)
: state_{&state}
{
}
http::Response operator()(MessageNumber msgNumber, const asyncgi ::Request&)
{
state_->removeMessage(msgNumber.value);
return http::Redirect{\”/\”};
}
private:
GuestBookState* state_;
};
int main()
{
auto io = asyncgi ::IO{4};
auto state = GuestBookState{};
auto router = asyncgi ::Router{io};
router.route(\”/\”, http::RequestMethod::Get).process<GuestBookPage>(state);
router.route(\”/\”, http::RequestMethod::Post).process<GuestBookAddMessage>(state);
router.route( asyncgi ::rx{\”/delete/(.+)\”}, http::RequestMethod::Post).process<GuestBookRemoveMessage>(state);
router.route().set(http::ResponseStatus::_404_Not_Found, \”Page not found\”);
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
// /examples/example_route_params_user_defined_types.cpp // / # include < asyncgi / asyncgi .h > # include < mutex > using namespace asyncgi ; using namespace std ::string_literals ; struct MessageNumber { int value; }; template <> struct asyncgi ::config::StringConverter<MessageNumber> { static std::optional<MessageNumber> fromString ( const std::string& data) { return MessageNumber{ std::stoi (data)}; } }; class GuestBookState { public: std::vector<std::string> messages () { auto lock = std::scoped_lock{mutex_}; return messages_; } void addMessage ( const std::string& msg) { auto lock = std::scoped_lock{mutex_}; messages_. emplace_back (msg); } void removeMessage ( int index) { auto lock = std::scoped_lock{mutex_}; if (index < 0 || index >= static_cast < int >(messages_. size ())) return ; messages_. erase ( std::next (messages_. begin (), index)); } private: std::vector<std::string> messages_; std::mutex mutex_; }; std::string makeMessage ( int index, const std::string& msg) { return msg + R\"( <form action=\"/delete/ )\" + std::to_string (index) + R\"( \" method=\"post\"> <input value=\"Delete\" type=\"submit\"> </form></div> )\" ; } class GuestBookPage { public: explicit GuestBookPage (GuestBookState& state) : state_{&state} { } http::Response operator ()( const asyncgi ::Request&) { auto messages = state_-> messages (); auto page = \" <h1>Guest book</h1> \" s; if (messages. empty ()) page += \" <p>No messages</p> \" ; else for ( auto i = 0 ; i < static_cast < int >(messages. size ()); ++i) page += \" <p> \" + makeMessage (i, messages. at (i)) + \" </p> \" ; page += \" <hr> \" ; page += \" <form method= \\\" post \\\" enctype= \\\" multipart/form-data \\\" > \" \" <label for= \\\" msg \\\" >Message:</label> \" \" <input id= \\\" msg \\\" name= \\\" msg \\\" value= \\\"\\\" > \" \" <input value= \\\" Submit \\\" data-popup= \\\" true \\\" type= \\\" submit \\\" > \" \" </form> \" ; return page; } private: GuestBookState* state_; }; class GuestBookAddMessage { public: explicit GuestBookAddMessage (GuestBookState& state) : state_{&state} { } http::Response operator ()( const asyncgi ::Request& request) { state_-> addMessage (std::string{request. formField ( \" msg \" )}); return http::Redirect{ \" / \" }; } private: GuestBookState* state_; }; class GuestBookRemoveMessage { public: explicit GuestBookRemoveMessage (GuestBookState& state) : state_{&state} { } http::Response operator ()(MessageNumber msgNumber, const asyncgi ::Request&) { state_-> removeMessage (msgNumber. value ); return http::Redirect{ \" / \" }; } private: GuestBookState* state_; }; int main () { auto io = asyncgi ::IO{ 4 }; auto state = GuestBookState{}; auto router = asyncgi ::Router{io}; router. route ( \" / \" , http::RequestMethod::Get). process <GuestBookPage>(state); router. route ( \" / \" , http::RequestMethod::Post). process <GuestBookAddMessage>(state); router. route ( asyncgi ::rx{ \" /delete/(.+) \" }, http::RequestMethod::Post). process <GuestBookRemoveMessage>(state); router. route (). set (http::ResponseStatus::_404_Not_Found, \" Page not found \" ); auto server = asyncgi ::Server{io, router}; server. listen ( \" /tmp/fcgi.sock \" ); io. run (); }
路线上下文
使用asyncgi ::Router时,可以为上下文结构类型指定模板参数。然后,该结构将传递给ContextualRequestProcessor函数,可以在多个路由的整个请求处理中访问和修改。 ContextualRequestProcessor是一个RequestProcessor ,它采用一个参考上下文对象的其他参数。
只要所有先前的请求处理器都不发送任何响应,就可以匹配多个路由。为了避免发送响应,请求处理器可以使用std::optional<http::Response>签名并返回空值。这允许使用asyncgi ::Router注册类似中间件的处理器,该处理器主要修改后续处理器的路由上下文。
下一个示例演示了如何将路由上下文用于存储授权信息:
例子
asyncgi .h>
#include <mutex>
#include <optional>
namespace http = asyncgi ::http;
using namespace std::string_literals;
enum class AccessRole {
Admin,
Guest
};
struct RouteContext {
AccessRole role = AccessRole::Guest;
};
struct AdminAuthorizer {
std::optional<http::Response> operator()(const asyncgi ::Request& request, RouteContext& context)
{
if (request.cookie(\”admin_id\”) == \”ADMIN_SECRET\”)
context.role = AccessRole::Admin;
return std::nullopt;
}
};
struct LoginPage {
http::Response operator()(const asyncgi ::Request&, RouteContext& context)
{
if (context.role == AccessRole::Guest)
return {R\”(
<html>
<form method=\”post\” enctype=\”multipart/form-data\”>
<label for=\”msg\”>Login:</label>
<input id=\”login\” name=\”login\” value=\”\”>
<label for=\”msg\”>Password:</label>
<input id=\”passwd\” name=\”passwd\” value=\”\”>
<input value=\”Submit\” data-popup=\”true\” type=\”submit\”>
</form></html>)\”};
else //We are already logged in as the administrator
return http::Redirect{\”/\”};
}
};
struct LoginPageAuthorize {
http::Response operator()(const asyncgi ::Request& request, RouteContext& context)
{
if (context.role == AccessRole::Guest) {
if (request.formField(\”login\”) == \”admin\” && request.formField(\”passwd\”) == \”12345\”)
return {http::Redirect{\”/\”}, { asyncgi ::http::Cookie(\”admin_id\”, \”ADMIN_SECRET\”)}};
else
return http::Redirect{\”/login\”};
}
else //We are already logged in as the administrator
return http::Redirect{\”/\”};
}
};
int main()
{
auto io = asyncgi ::IO{4}; //4 threads processing requests
auto router = asyncgi ::Router<RouteContext>{io};
router.route( asyncgi ::rx{\”.*\”}).process<AdminAuthorizer>();
router.route(\”/\”).process(
[](const asyncgi ::Request&, asyncgi ::Responder& response, RouteContext& context)
{
if (context.role == AccessRole::Admin)
response.send(\”<p>Hello admin</p>\”);
else
response.send(R\”(<p>Hello guest</p><p><a href=\”/login\”>login</a>)\”);
});
router.route(\”/login\”, http::RequestMethod::Get).process<LoginPage>();
router.route(\”/login\”, http::RequestMethod::Post).process<LoginPageAuthorize>();
router.route().set(http::ResponseStatus::_404_Not_Found, \”Page not found\”);
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
// /examples/example_route_context.cpp // / # include < asyncgi / asyncgi .h > # include < mutex > # include < optional > namespace http = asyncgi ::http; using namespace std ::string_literals ; enum class AccessRole { Admin, Guest }; struct RouteContext { AccessRole role = AccessRole::Guest; }; struct AdminAuthorizer { std::optional<http::Response> operator ()( const asyncgi ::Request& request, RouteContext& context) { if (request. cookie ( \" admin_id \" ) == \" ADMIN_SECRET \" ) context. role = AccessRole::Admin; return std::nullopt; } }; struct LoginPage { http::Response operator ()( const asyncgi ::Request&, RouteContext& context) { if (context. role == AccessRole::Guest) return { R\"( <html> <form method=\"post\" enctype=\"multipart/form-data\"> <label for=\"msg\">Login:</label> <input id=\"login\" name=\"login\" value=\"\"> <label for=\"msg\">Password:</label> <input id=\"passwd\" name=\"passwd\" value=\"\"> <input value=\"Submit\" data-popup=\"true\" type=\"submit\"> </form></html> )\" }; else // We are already logged in as the administrator return http::Redirect{ \" / \" }; } }; struct LoginPageAuthorize { http::Response operator ()( const asyncgi ::Request& request, RouteContext& context) { if (context. role == AccessRole::Guest) { if (request. formField ( \" login \" ) == \" admin \" && request. formField ( \" passwd \" ) == \" 12345 \" ) return {http::Redirect{ \" / \" }, { asyncgi ::http::Cookie ( \" admin_id \" , \" ADMIN_SECRET \" )}}; else return http::Redirect{ \" /login \" }; } else // We are already logged in as the administrator return http::Redirect{ \" / \" }; } }; int main () { auto io = asyncgi ::IO{ 4 }; // 4 threads processing requests auto router = asyncgi ::Router<RouteContext>{io}; router. route ( asyncgi ::rx{ \" .* \" }). process <AdminAuthorizer>(); router. route ( \" / \" ). process ( []( const asyncgi ::Request&, asyncgi ::Responder& response, RouteContext& context) { if (context. role == AccessRole::Admin) response. send ( \" <p>Hello admin</p> \" ); else response. send ( R\"( <p>Hello guest</p><p><a href=\"/login\">login</a> )\" ); }); router. route ( \" /login \" , http::RequestMethod::Get). process <LoginPage>(); router. route ( \" /login \" , http::RequestMethod::Post). process <LoginPageAuthorize>(); router. route (). set (http::ResponseStatus::_404_Not_Found, \" Page not found \" ); auto server = asyncgi ::Server{io, router}; server. listen ( \" /tmp/fcgi.sock \" ); io. run (); }
路线匹配者
请求或上下文对象的任何参数都可以在asyncgi ::Router::route()方法中注册以进行路由匹配。为了实现这一目标,需要提供asyncgi ::config::RouteMatcher类模板的专业化,并在其中实现比较器Bool Operator()。让我们看看如何从上一个示例中注册枚举类Access作为路由匹配器:
例子
asyncgi .h>
#include <mutex>
#include <optional>
namespace http = asyncgi ::http;
using namespace std::string_literals;
enum class AccessRole {
Admin,
Guest
};
struct RouteContext {
AccessRole role = AccessRole::Guest;
};
struct AdminAuthorizer {
std::optional<http::Response> operator()(const asyncgi ::Request& request, RouteContext& context)
{
if (request.cookie(\”admin_id\”) == \”ADMIN_SECRET\”)
context.role = AccessRole::Admin;
return std::nullopt;
}
};
struct LoginPage {
http::Response operator()(const asyncgi ::Request&)
{
return {R\”(
<html>
<form method=\”post\” enctype=\”multipart/form-data\”>
<label for=\”msg\”>Login:</label>
<input id=\”login\” name=\”login\” value=\”\”>
<label for=\”msg\”>Password:</label>
<input id=\”passwd\” name=\”passwd\” value=\”\”>
<input value=\”Submit\” data-popup=\”true\” type=\”submit\”>
</form></html>)\”};
}
};
struct LoginPageAuthorize {
http::Response operator()(const asyncgi ::Request& request)
{
if (request.formField(\”login\”) == \”admin\” && request.formField(\”passwd\”) == \”12345\”)
return {http::Redirect{\”/\”}, { asyncgi ::http::Cookie(\”admin_id\”, \”ADMIN_SECRET\”)}};
return http::Redirect{\”/login\”};
}
};
template<>
struct asyncgi ::config::RouteMatcher<AccessRole, RouteContext> {
bool operator()(AccessRole value, const asyncgi ::Request&, const RouteContext& context) const
{
return value == context.role;
}
};
int main()
{
auto io = asyncgi ::IO{4};
auto router = asyncgi ::Router<RouteContext>{io};
router.route( asyncgi ::rx{\”.*\”}).process<AdminAuthorizer>();
router.route(\”/\”).process(
[](const asyncgi ::Request&, RouteContext& context) -> http::Response
{
if (context.role == AccessRole::Admin)
return {\”<p>Hello admin</p>\”};
else
return {R\”(<p>Hello guest</p><p><a href=\”/login\”>login</a>)\”};
});
router.route(\”/login\”, http::RequestMethod::Get, AccessRole::Guest).process<LoginPage>();
router.route(\”/login\”, http::RequestMethod::Post, AccessRole::Guest).process<LoginPageAuthorize>();
router.route(\”/login\”, http::RequestMethod::Get, AccessRole::Admin).set(\”/\”, http::RedirectType::Found);
router.route(\”/login\”, http::RequestMethod::Post, AccessRole::Admin).set(\”/\”, http::RedirectType::Found);
router.route().set(http::ResponseStatus::_404_Not_Found, \”Page not found\”);
auto server = asyncgi ::Server{io, router};
server.listen(\”/tmp/fcgi.sock\”);
io.run();
}\”>
// /examples/example_route_matcher.cpp // / # include < asyncgi / asyncgi .h > # include < mutex > # include < optional > namespace http = asyncgi ::http; using namespace std ::string_literals ; enum class AccessRole { Admin, Guest }; struct RouteContext { AccessRole role = AccessRole::Guest; }; struct AdminAuthorizer { std::optional<http::Response> operator ()( const asyncgi ::Request& request, RouteContext& context) { if (request. cookie ( \" admin_id \" ) == \" ADMIN_SECRET \" ) context. role = AccessRole::Admin; return std::nullopt; } }; struct LoginPage { http::Response operator ()( const asyncgi ::Request&) { return { R\"( <html> <form method=\"post\" enctype=\"multipart/form-data\"> <label for=\"msg\">Login:</label> <input id=\"login\" name=\"login\" value=\"\"> <label for=\"msg\">Password:</label> <input id=\"passwd\" name=\"passwd\" value=\"\"> <input value=\"Submit\" data-popup=\"true\" type=\"submit\"> </form></html> )\" }; } }; struct LoginPageAuthorize { http::Response operator ()( const asyncgi ::Request& request) { if (request. formField ( \" login \"
