fcgi_responder

其他类别 2025-08-17

fcgi_responder是C ++ 17库,它实现了FastCGI协议的响应者角色。它处理从Web服务器接收到的原始数据,并返回需要通过客户端代码将其发送回服务器的序列化输出。该库不处理与Web服务器的连接的实现,因此客户端可以使用他们喜欢的任何套接字编程方法。这使得fcgi_responder可移植并不受外部依赖关系。

fcgi_responder目标是成为FastCGI协议的现代且可读性的实现。与流行的FastCGI实施libfcgi相比,我们使用asio库的基准显示出100%以上的性能增长,该实现是用C编写的。

展示柜

  • 异步 - 基于fcgi_responderAsio网络框架

用法

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 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 OKrn" "Content-Type: text/htmlrn" "rn" "HELLO WORLD USING ASIO!"); response.send(); } private: unixdomain::socket socket_; std::array 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 rn "
                         " Content-Type: text/html rn "
                         " rn "
                         " 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 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 buffer_; bool isOpened_ = true; }; void onResponseReceived(const std::optional& 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& 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_benchmarkfcgi_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许可证获得许可

下载源码

通过命令行克隆项目:

git clone https://github.com/kamchatka-volcano/fcgi_responder.git