fcgi_responder是C ++ 17库,它实现了FastCGI协议的响应者角色。它处理从Web服务器接收到的原始数据,并返回需要通过客户端代码将其发送回服务器的序列化输出。该库不处理与Web服务器的连接的实现,因此客户端可以使用他们喜欢的任何套接字编程方法。这使得fcgi_responder可移植并不受外部依赖关系。
fcgi_responder目标是成为FastCGI协议的现代且可读性的实现。与流行的FastCGI实施libfcgi相比,我们使用asio库的基准显示出100%以上的性能增长,该实现是用C编写的。
展示柜
- 异步 – 基于
fcgi_responder和Asio网络框架
用法
Web服务器的处理请求
要使用fcgi_responder库,请从fcgi::Responder类中继承并实现其纯虚拟方法以提供必要的功能:
-
virtual void sendData(const std::string& data) = 0 -
virtual void processRequest(fcgi::Request&& request, fcgi::Response&& response) = 0 -
virtual void disconnect() = 0
然后,通过将传入数据传递到fcgi::Responder::receiveData方法来聆听Web服务器的连接并处理传入数据。
这是一个最小的示例,使用独立的ASIO库进行网络:
fcgi_responder/responder.h>
#include <iostream>
using unixdomain = asio::local::stream_protocol;
class Connection : public fcgi::Responder{
public:
explicit Connection(unixdomain::socket&& socket)
: socket_(std::move(socket))
{
}
void process()
{
while(isOpened_){
try {
auto receivedDataSize = socket_.read_some(asio::buffer(buffer_));
///
/// Passing read socket data with fcgi::Responder::receiveData method
///
receiveData(buffer_.data(), receivedDataSize);
}
catch(…){
isOpened_ = false;
return;
}
}
}
private:
///
/// Overriding fcgi::Responder::sendData to send response data to the web server
///
void sendData(const std::string& data) override
{
asio::write(socket_, asio::buffer(data, data.size()));
}
///
/// Overriding fcgi::Responder::disconnect to close connection with the web server
///
void disconnect() override
{
try{
socket_.shutdown(unixdomain::socket::shutdown_both);
socket_.close();
}
catch(const std::system_error& e){
std::cerr << \”socket close error:\” << e.code();
}
isOpened_ = false;
};
///
/// Overriding fcgi::Responder::processRequest to form response data
///
void processRequest(fcgi::Request&&, fcgi::Response&& response) override
{
response.setData(\”Status: 200 OK\\r\\n\”
\”Content-Type: text/html\\r\\n\”
\”\\r\\n\”
\”HELLO WORLD USING ASIO!\”);
response.send();
}
private:
unixdomain::socket socket_;
std::array<char, 65536> buffer_;
bool isOpened_ = true;
};
int main ()
{
auto socketPath = std::string{\”/tmp/fcgi.sock\”};
umask(0);
chmod(socketPath.c_str(), 0777);
unlink(socketPath.c_str());
auto io = asio::io_context{};
auto acceptor = unixdomain::acceptor{io, unixdomain::endpoint{socketPath}};
while (true) {
auto socket = acceptor.accept();
auto connection = Connection{std::move(socket)};
connection.process();
}
return 0;
}\”>
# include \" asio.hpp \" # include < fcgi_responder /responder.h > # include < iostream > using unixdomain = asio::local::stream_protocol; class Connection : public fcgi ::Responder{ public: explicit Connection (unixdomain::socket&& socket) : socket_(std::move(socket)) { } void process () { while (isOpened_){ try { auto receivedDataSize = socket_. read_some ( asio::buffer (buffer_)); // / // / Passing read socket data with fcgi::Responder::receiveData method // / receiveData (buffer_. data (), receivedDataSize); } catch (...){ isOpened_ = false ; return ; } } } private: // / // / Overriding fcgi::Responder::sendData to send response data to the web server // / void sendData ( const std::string& data) override { asio::write (socket_, asio::buffer (data, data. size ())); } // / // / Overriding fcgi::Responder::disconnect to close connection with the web server // / void disconnect () override { try { socket_. shutdown (unixdomain::socket::shutdown_both); socket_. close (); } catch ( const std::system_error& e){ std::cerr << \" socket close error: \" << e. code (); } isOpened_ = false ; }; // / // / Overriding fcgi::Responder::processRequest to form response data // / void processRequest (fcgi::Request&&, fcgi::Response&& response) override { response. setData ( \" Status: 200 OK \\r\\n \" \" Content-Type: text/html \\r\\n \" \" \\r\\n \" \" HELLO WORLD USING ASIO! \" ); response. send (); } private: unixdomain::socket socket_; std::array< char , 65536 > buffer_; bool isOpened_ = true ; }; int main () { auto socketPath = std::string{ \" /tmp/fcgi.sock \" }; umask ( 0 ); chmod (socketPath. c_str (), 0777 ); unlink (socketPath. c_str ()); auto io = asio::io_context{}; auto acceptor = unixdomain::acceptor{io, unixdomain::endpoint{socketPath}}; while ( true ) { auto socket = acceptor. accept (); auto connection = Connection{ std::move (socket)}; connection. process (); } return 0 ; }
检查使用QT框架的examples目录和其他示例。
向FastCGI应用程序发送请求
fcgi_responder库提供了一个可以用于将请求发送到FastCGI应用程序的fcgi::Requester类。
要使用它,从fcgi::Requester类中继承并实现其纯虚拟方法:
-
virtual void sendData(const std::string& data) = 0 -
virtual void disconnect() = 0
完成此操作后,您可以将套接字连接到侦听FastCGI应用程序,并通过调用fcgi::Requester::sendRequest方法来提出请求。请确保通过将传入数据传递到fcgi::Requester::receiveData方法来处理。
这是一个最小的示例,使用独立的ASIO库进行网络:
fcgi_responder/requester.h>
#include <iostream>
using unixdomain = asio::local::stream_protocol;
class Client : public fcgi::Requester{
public:
explicit Client(unixdomain::socket&& socket)
: socket_(std::move(socket))
{
}
void process()
{
while(isOpened_){
auto receivedDataSize = socket_.read_some(asio::buffer(buffer_));
///
/// Passing read socket data with fcgi::Requester::receiveData method
///
receiveData(buffer_.data(), receivedDataSize);
}
}
private:
///
/// Overriding fcgi::Requester::sendData to send request data to the FastCGI application
///
void sendData(const std::string& data) override
{
asio::write(socket_, asio::buffer(data, data.size()));
}
///
/// Overriding fcgi::Requester::disconnect to close connection with the FastCGI application
///
void disconnect() override
{
try{
socket_.shutdown(unixdomain::socket::shutdown_both);
socket_.close();
}
catch(const std::system_error& e){
std::cerr << \”socket close error:\” << e.code();
}
isOpened_ = false;
};
private:
unixdomain::socket socket_;
std::array<char, 65536> buffer_;
bool isOpened_ = true;
};
void onResponseReceived(const std::optional<fcgi::ResponseData>& response)
{
std::cout << \”Response:\” << std::endl;
if (response)
std::cout << response->data << std::endl;
else
std::cout << \”No response\” << std::endl;
}
int main ()
{
auto socketPath = std::string{\”/tmp/fcgi.sock\”};
auto io = asio::io_context{};
auto socket = unixdomain::socket{io};
try {
socket.connect(unixdomain::endpoint{socketPath});
}
catch(std::system_error& e){
std::cerr << \”Socket connection error:\” << e.code();
return 1;
}
auto client = Client{std::move(socket)};
client.setErrorInfoHandler([](const std::string& error){
std::cout << error << std::endl;
});
client.sendRequest({{\”REQUEST_METHOD\”,\”GET\”},
{\”REMOTE_ADDR\”,\”127.0.0.1\”},
{\”HTTP_HOST\”,\”localhost\”},
{\”REQUEST_URI\”,\”/\”}}, {}, onResponseReceived);
client.process();
return 0;
}\”>
# include \" asio.hpp \" # include < fcgi_responder /requester.h > # include < iostream > using unixdomain = asio::local::stream_protocol; class Client : public fcgi ::Requester{ public: explicit Client (unixdomain::socket&& socket) : socket_(std::move(socket)) { } void process () { while (isOpened_){ auto receivedDataSize = socket_. read_some ( asio::buffer (buffer_)); // / // / Passing read socket data with fcgi::Requester::receiveData method // / receiveData (buffer_. data (), receivedDataSize); } } private: // / // / Overriding fcgi::Requester::sendData to send request data to the FastCGI application // / void sendData ( const std::string& data) override { asio::write (socket_, asio::buffer (data, data. size ())); } // / // / Overriding fcgi::Requester::disconnect to close connection with the FastCGI application // / void disconnect () override { try { socket_. shutdown (unixdomain::socket::shutdown_both); socket_. close (); } catch ( const std::system_error& e){ std::cerr << \" socket close error: \" << e. code (); } isOpened_ = false ; }; private: unixdomain::socket socket_; std::array< char , 65536 > buffer_; bool isOpened_ = true ; }; void onResponseReceived ( const std::optional<fcgi::ResponseData>& response) { std::cout << \" Response: \" << std::endl; if (response) std::cout << response-> data << std::endl; else std::cout << \" No response \" << std::endl; } int main () { auto socketPath = std::string{ \" /tmp/fcgi.sock \" }; auto io = asio::io_context{}; auto socket = unixdomain::socket{io}; try { socket. connect (unixdomain::endpoint{socketPath}); } catch (std::system_error& e){ std::cerr << \" Socket connection error: \" << e. code (); return 1 ; } auto client = Client{ std::move (socket)}; client. setErrorInfoHandler ([]( const std::string& error){ std::cout << error << std::endl; }); client. sendRequest ({{ \" REQUEST_METHOD \" , \" GET \" }, { \" REMOTE_ADDR \" , \" 127.0.0.1 \" }, { \" HTTP_HOST \" , \" localhost \" }, { \" REQUEST_URI \" , \" / \" }}, {}, onResponseReceived); client. process (); return 0 ; }
安装
从项目的cmakelists.txt下载并链接库:
fcgi_responder
GIT_REPOSITORY \”https://gith**u*b.com/kamchatka-volcano/fcgi_responder.git\”
GIT_TAG \”origin/master\”
)
#uncomment if you need to install fcgi_responder with your target
#set(INSTALL_ fcgi_responder ON)
FetchContent_MakeAvailable( fcgi_responder )
add_executable(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE fcgi_responder :: fcgi_responder )\”>
cmake_minimum_required(VERSION 3.14)
include(FetchContent)
FetchContent_Declare( fcgi_responder
GIT_REPOSITORY \"https://gith**u*b.com/kamchatka-volcano/fcgi_responder.git\"
GIT_TAG \"origin/master\"
)
#uncomment if you need to install fcgi_responder with your target
#set(INSTALL_ fcgi_responder ON)
FetchContent_MakeAvailable( fcgi_responder )
add_executable(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PRIVATE fcgi_responder :: fcgi_responder )
要安装整个系统范围的库,请使用以下命令:
fcgi_responder
cmake -S . -B build
cmake –build build
cmake –install build\”>
git clone https://gith**u*b.com/kamchatka-volcano/fcgi_responder.git
cd fcgi_responder
cmake -S . -B build
cmake --build build
cmake --install build
安装后,您可以使用find_package()命令使您的项目内有可用的库:
fcgi_responder 1.0.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE fcgi_responder :: fcgi_responder )\”>
find_package( fcgi_responder 1.0.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE fcgi_responder :: fcgi_responder )
运行测试
fcgi_responder
cmake -S . -B build -DENABLE_TESTS=ON
cmake –build build
cd build/tests && ctest\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_TESTS=ON
cmake --build build
cd build/tests && ctest
运行模糊测试
使用AFL++模糊测试工具对fcgi_responder进行了测试。该存储库包含fuzz_test/input中的输入数据,一个模糊的线束fcgi_responder _fuzzer和FUZZing Input Data Enput Data Generator fuzz_input_generator 。
要构建fcgi_responder _fuzzer用于调试输入数据,请运行以下命令:
fcgi_responder
cmake -S . -B build -DENABLE_FUZZ_TESTS=ON
cmake –build build\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_FUZZ_TESTS=ON
cmake --build build
要构建fcgi_responder _fuzzer以运行使用afl-fuzz实用程序运行fuzzsing测试,请运行以下命令:
fcgi_responder
LLVM_CONFIG=\”llvm-config-11\” CXX=afl-clang-fast++ cmake -S . -B afl_build -DENABLE_FUZZ_TESTS=ON
cmake –build afl_build\”>
cd fcgi_responder
LLVM_CONFIG=\"llvm-config-11\" CXX=afl-clang-fast++ cmake -S . -B afl_build -DENABLE_FUZZ_TESTS=ON
cmake --build afl_build
调整LLVM_CONFIG变量,以指向您要使用的LLVM的版本。
使用afl-fuzz实用程序运行以下命令:
afl-fuzz -i ./fuzz_tests/input -o ./fuzz_tests/res -x ./fuzz_tests/afl_dict.txt -s 111 -- ./afl_build/fuzz_tests/ fcgi_responder _fuzzer @@
模糊测试的结果可以在fuzz_tests/res目录中找到。
要了解有关模糊测试的更多信息,请查看AFL ++文档和本教程。
fuzz_input_generator实用程序生成/fuzz_tests/input内部的输入数据。这是将输入数据写入文件的单元测试的修改版本。要构建它,请使用以下命令:
fcgi_responder
cmake -S . -B build -DENABLE_FUZZ_INPUT_GENERATOR=ON
cmake –build build\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_FUZZ_INPUT_GENERATOR=ON
cmake --build build
运行示例
设置您的Web服务器以使用Unix域套接字/tmp/fcgi.sock上的FastCGI协议。使用nginx,您可以使用此配置:
server {
listen 8088;
server_name localhost;
index /~;
location / {
try_files $uri $uri/ @fcgi;
}
location @fcgi {
fastcgi_pass unix:/tmp/fcgi.sock;
include fastcgi_params;
fastcgi_intercept_errors on;
fastcgi_keep_conn off;
}
}
构建并运行ASIO示例:
fcgi_responder
cmake -S . -B build -DENABLE_ASIO_EXAMPLE=ON -DENABLE_ASIO_REQUESTER_EXAMPLE=ON
cmake –build build
./build/examples/asio_example
./build/examples/asio_requester_example\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_ASIO_EXAMPLE=ON -DENABLE_ASIO_REQUESTER_EXAMPLE=ON
cmake --build build
./build/examples/asio_example
./build/examples/asio_requester_example
或构建并运行QT示例:
fcgi_responder
cmake -S . -B build -DENABLE_QT_EXAMPLE=ON -DENABLE_QT_REQUESTER_EXAMPLE=ON
cmake –build build
./build/examples/qt_example
./build/examples/qt_requester_example\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_QT_EXAMPLE=ON -DENABLE_QT_REQUESTER_EXAMPLE=ON
cmake --build build
./build/examples/qt_example
./build/examples/qt_requester_example
检查它在这里工作:http:// localhost:8088
运行基准
实用程序libfcgi_benchmark和fcgi_responder _benchmark用于测量本文档中图表的性能。它们可以通过以下命令构建:
fcgi_responder
cmake -S . -B build -DENABLE_LIBFCGI_BENCHMARK=ON -DENABLE_ fcgi_responder _BENCHMARK=ON
cmake –build build
./build/utils/ fcgi_responder _benchmark/ fcgi_responder _benchmark –response-size 27
./build/utils/libfcgi_benchmark/libfcgi_benchmark –response-size 27\”>
cd fcgi_responder
cmake -S . -B build -DENABLE_LIBFCGI_BENCHMARK=ON -DENABLE_ fcgi_responder _BENCHMARK=ON
cmake --build build
./build/utils/ fcgi_responder _benchmark/ fcgi_responder _benchmark --response-size 27
./build/utils/libfcgi_benchmark/libfcgi_benchmark --response-size 27
所需的Web服务器配置与上一节相同。
可以使用ab工具来测量这两个基准的吞吐量性能:
ab -n 20000 -c 10 http://l*ocalh**ost:8088/
执照
fcgi_responder由MS-PL许可证获得许可
