diff --git a/Makefile b/Makefile index 3f2e5c9..d1b4bfd 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -mirall: Makefile mirall.o fastcgi.o fastcgi_conn.o buffer.o - g++ -std=gnu++2a -o mirall mirall.o fastcgi.o fastcgi_conn.o buffer.o -lgflags -lglog -lpthread +example_simple: example_simple.o fastcgi.o fastcgi_conn.o fastcgi_request.o buffer.o + g++ -std=gnu++2a -o example_simple example_simple.o fastcgi.o fastcgi_conn.o fastcgi_request.o buffer.o -lgflags -lglog -lpthread clean: - rm --force mirall *.o + rm --force exmaple_simple *.o -.cpp.o: Makefile *.h +.cpp.o: g++ -std=gnu++2a -o $@ -c $< diff --git a/buffer.cpp b/buffer.cpp index 05a1547..2b5cddc 100644 --- a/buffer.cpp +++ b/buffer.cpp @@ -19,7 +19,7 @@ bool ConstBuffer::Discard(size_t len) { if (len > ReadMaxLen()) { return false; } - Read(len); + static_cast(Read(len)); return true; } @@ -40,7 +40,9 @@ Buffer::Buffer(char *buf, size_t size, size_t len) } Buffer::Buffer(size_t size) - : Buffer((own_buf_.reset(new char[size]), own_buf_.get()), size, 0) {} + : Buffer(new char[size], size, 0) { + own_buf_.reset(buf_); +} char *Buffer::WritePtr() { return &buf_[len_]; diff --git a/buffer.h b/buffer.h index 334c8e3..627054b 100644 --- a/buffer.h +++ b/buffer.h @@ -7,9 +7,9 @@ class ConstBuffer { public: ConstBuffer(const char *buf, size_t len); - size_t ReadMaxLen() const; - const char *Read(size_t len); - template const T *ReadObj(); + [[nodiscard]] size_t ReadMaxLen() const; + [[nodiscard]] const char *Read(size_t len); + template [[nodiscard]] const T *ReadObj(); bool Discard(size_t len); // like Read() but don't use the result void ResetRead(); // next read from last commit @@ -28,8 +28,8 @@ class Buffer : public ConstBuffer { Buffer(char *buf, size_t size, size_t len); Buffer(size_t size); - char *WritePtr(); - size_t WriteMaxLen() const; + [[nodiscard]] char *WritePtr(); + [[nodiscard]] size_t WriteMaxLen() const; void Wrote(size_t len); void Consume(); // discard up to last commit diff --git a/example_simple b/example_simple new file mode 100755 index 0000000..eb82546 Binary files /dev/null and b/example_simple differ diff --git a/mirall.cpp b/example_simple.cpp similarity index 52% rename from mirall.cpp rename to example_simple.cpp index d019c2c..fff7ba5 100644 --- a/mirall.cpp +++ b/example_simple.cpp @@ -9,6 +9,10 @@ int main(int argc, char *argv[]) { google::InitGoogleLogging(argv[0]); gflags::ParseCommandLineFlags(&argc, &argv, true); - FastCGIServer server(FLAGS_port); + FastCGIServer server(FLAGS_port, [](std::unique_ptr request) { + LOG(INFO) << "request from " << request->GetParam("REMOTE_ADDR"); + request->Write({{"Content-Type", "text/plain"}}, {"Hello world"}); + request->WriteEnd(); + }); server.Serve(); } diff --git a/fastcgi.cpp b/fastcgi.cpp index d43258f..c8392bc 100644 --- a/fastcgi.cpp +++ b/fastcgi.cpp @@ -6,7 +6,8 @@ #include "fastcgi.h" #include "fastcgi_conn.h" -FastCGIServer::FastCGIServer(int port) { +FastCGIServer::FastCGIServer(int port, const std::function)>& callback) + : callback_(callback) { LOG(INFO) << "listening on [::1]:" << port; listen_sock_ = socket(AF_INET6, SOCK_STREAM, 0); @@ -38,7 +39,7 @@ void FastCGIServer::Serve() { PCHECK(client_sock >= 0) << "accept()"; CHECK_EQ(client_addr.sin6_family, AF_INET6); - auto *conn = new FastCGIConn(client_sock, client_addr); + auto *conn = new FastCGIConn(client_sock, client_addr, callback_); std::thread thread([conn]() { conn->Serve(); }); thread.detach(); } diff --git a/fastcgi.h b/fastcgi.h index 8757110..1097bd8 100644 --- a/fastcgi.h +++ b/fastcgi.h @@ -1,10 +1,16 @@ #pragma once +#include +#include + +#include "fastcgi_request.h" + class FastCGIServer { public: - FastCGIServer(int port); + FastCGIServer(int port, const std::function)>& callback); void Serve(); private: int listen_sock_; + std::function)> callback_; }; diff --git a/fastcgi_conn.cpp b/fastcgi_conn.cpp index 7cb5aac..6b375f2 100644 --- a/fastcgi_conn.cpp +++ b/fastcgi_conn.cpp @@ -1,8 +1,11 @@ #include #include +#include #include "fastcgi_conn.h" +#include "fastcgi_request.h" + namespace { struct fcgi_header { @@ -12,16 +15,39 @@ struct fcgi_header { uint16_t request_id_; // network byte order uint16_t content_length_; // network byte order public: - uint8_t padding_length; - uint8_t reserved; + uint8_t padding_length = 0; + uint8_t reserved = 0; - uint16_t ContentLength() const { return htons(content_length_); } + uint16_t RequestId() const { return ntohs(request_id_); } + uint16_t ContentLength() const { return ntohs(content_length_); } + + void SetRequestId(uint16_t request_id) { request_id_ = htons(request_id); } + void SetContentLength(uint16_t content_length) { content_length_ = htons(content_length); } }; struct fcgi_begin_request { - uint16_t role; // network byte order + private: + uint16_t role_; // network byte order + public: uint8_t flags; uint8_t reserved[5]; + + uint16_t Role() const { return ntohs(role_); } +}; + +struct fcgi_end_request { + private: + uint32_t app_status_; + public: + uint8_t protocol_status; + uint8_t reserved[3] = {}; + + void SetAppStatus(uint32_t app_status) { app_status_ = htonl(app_status); } +}; + +struct fcgi_param_header { + uint8_t key_length; + uint8_t value_length; }; constexpr auto fcgi_max_content_len = 65535; @@ -30,8 +56,9 @@ constexpr auto fcgi_max_record_len = sizeof(fcgi_header) + fcgi_max_content_len } // namespace -FastCGIConn::FastCGIConn(int sock, const sockaddr_in6& client_addr) +FastCGIConn::FastCGIConn(int sock, const sockaddr_in6& client_addr, const std::function)>& callback) : sock_(sock), + callback_(callback), buf_(fcgi_max_record_len) { char client_addr_str[INET6_ADDRSTRLEN]; PCHECK(inet_ntop(AF_INET6, &client_addr.sin6_addr, client_addr_str, sizeof(client_addr_str))); @@ -50,6 +77,8 @@ void FastCGIConn::Serve() { PCHECK(read_len >= 0); if (read_len == 0) { LOG(INFO) << "peer closed connection"; + delete this; + return; } buf_.Wrote(read_len); @@ -58,6 +87,47 @@ void FastCGIConn::Serve() { } } +void FastCGIConn::WriteBlock(uint8_t type, uint16_t request_id, const std::vector& vecs) { + std::vector out_vecs; + out_vecs.reserve(vecs.size() + 1); + + fcgi_header header; + header.version = 1; + header.type = type; + header.SetRequestId(request_id); + uint16_t content_length = 0; + for (auto& vec : vecs) { + content_length += vec.iov_len; + } + header.SetContentLength(content_length); + out_vecs.push_back(std::move(iovec{ + .iov_base = &header, + .iov_len = sizeof(header), + })); + + for (auto& vec : vecs) { + out_vecs.push_back(vec); + } + + CHECK_EQ(writev(sock_, out_vecs.data(), out_vecs.size()), content_length + sizeof(header)); +} + +void FastCGIConn::WriteOutput(uint16_t request_id, const std::vector& vecs) { + WriteBlock(6, request_id, vecs); +} + +void FastCGIConn::WriteEnd(uint16_t request_id, uint8_t status) { + fcgi_end_request end; + end.SetAppStatus(status); + + std::vector vecs; + vecs.push_back(std::move(iovec{ + .iov_base = &end, + .iov_len = sizeof(end), + })); + WriteBlock(3, request_id, vecs); +} + void FastCGIConn::ParseBuf() { buf_.ResetRead(); @@ -72,22 +142,39 @@ void FastCGIConn::ParseBuf() { return; } - LOG(INFO) << "type: " << (int)header->type; - switch (header->type) { case 1: { CHECK_EQ(header->ContentLength(), sizeof(fcgi_begin_request)); const auto *begin_request = CHECK_NOTNULL(buf_.ReadObj()); - CHECK_EQ(ntohs(begin_request->role), 1); + CHECK_EQ(begin_request->Role(), 1); + + request_.reset(new FastCGIRequest(header->RequestId(), this)); } break; case 4: { - const auto *params = buf_.Read(header->ContentLength()); + ConstBuffer param_buf(buf_.Read(header->ContentLength()), header->ContentLength()); + while (param_buf.ReadMaxLen() > 0) { + const auto *param_header = param_buf.ReadObj(); + std::string_view key(param_buf.Read(param_header->key_length), param_header->key_length); + std::string_view value(param_buf.Read(param_header->value_length), param_header->value_length); + request_->AddParam(key, value); + } } break; + + case 5: + { + if (header->ContentLength() == 0) { + // Magic signal for completed request (mirrors the HTTP/1.1 protocol) + callback_(std::move(request_)); + } else { + std::string_view in(buf_.Read(header->ContentLength()), header->ContentLength()); + request_->AddIn(in); + } + } } CHECK(buf_.Discard(header->padding_length)); diff --git a/fastcgi_conn.h b/fastcgi_conn.h index d321132..848ca04 100644 --- a/fastcgi_conn.h +++ b/fastcgi_conn.h @@ -1,19 +1,31 @@ #pragma once +#include +#include + #include "buffer.h" struct sockaddr_in6; +class FastCGIRequest; class FastCGIConn { public: - FastCGIConn(int sock, const sockaddr_in6& client_addr); + FastCGIConn(int sock, const sockaddr_in6& client_addr, const std::function)>& callback); ~FastCGIConn(); void Serve(); + void WriteBlock(uint8_t type, uint16_t request_id, const std::vector& vecs); + void WriteOutput(uint16_t request_id, const std::vector& vecs); + void WriteEnd(uint16_t request_id, uint8_t status); + private: void ParseBuf(); const int sock_; + std::function)> callback_; + Buffer buf_; + + std::unique_ptr request_; }; diff --git a/fastcgi_request.cpp b/fastcgi_request.cpp new file mode 100644 index 0000000..a048cfa --- /dev/null +++ b/fastcgi_request.cpp @@ -0,0 +1,68 @@ +#include + +#include "fastcgi_conn.h" + +#include "fastcgi_request.h" + +FastCGIRequest::FastCGIRequest(uint16_t request_id, FastCGIConn* conn) + : request_id_(request_id), + conn_(conn) {} + +void FastCGIRequest::AddParam(const std::string_view& key, const std::string_view& value) { + params_.try_emplace(std::move(std::string(key)), std::move(std::string(value))); +} + +void FastCGIRequest::AddIn(const std::string_view& in) { + in_.append(in); +} + +const std::string& FastCGIRequest::GetParam(const std::string& key) { + return params_.at(key); +} + +void FastCGIRequest::Write(const std::vector>& headers, const std::vector& body) { + std::vector vecs; + vecs.reserve((headers.size() * 4) + 1 + body.size()); + + CHECK(headers.empty() || !body_sent_); + + for (const auto& header : headers) { + vecs.push_back(std::move(iovec{ + .iov_base = const_cast(header.first.data()), + .iov_len = header.first.size(), + })); + vecs.push_back(std::move(iovec{ + .iov_base = const_cast(": "), + .iov_len = 2, + })); + vecs.push_back(std::move(iovec{ + .iov_base = const_cast(header.second.data()), + .iov_len = header.second.size(), + })); + vecs.push_back(std::move(iovec{ + .iov_base = const_cast("\n"), + .iov_len = 1, + })); + } + + if (!body.empty() && !body_sent_) { + body_sent_ = true; + vecs.push_back(std::move(iovec{ + .iov_base = const_cast("\n"), + .iov_len = 1, + })); + } + + for (const auto& chunk : body) { + vecs.push_back(std::move(iovec{ + .iov_base = const_cast(chunk.data()), + .iov_len = chunk.size(), + })); + } + + conn_->WriteOutput(request_id_, vecs); +} + +void FastCGIRequest::WriteEnd() { + conn_->WriteEnd(request_id_, 0); +} diff --git a/fastcgi_request.h b/fastcgi_request.h new file mode 100644 index 0000000..e1547d4 --- /dev/null +++ b/fastcgi_request.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +class FastCGIConn; + +class FastCGIRequest { + public: + FastCGIRequest(uint16_t request_id, FastCGIConn *conn); + + void AddParam(const std::string_view& key, const std::string_view& value); + void AddIn(const std::string_view& in); + + const std::string& GetParam(const std::string& key); + + void Write(const std::vector>& headers, const std::vector& body); + void WriteEnd(); + + private: + const uint16_t request_id_; + FastCGIConn *conn_; + + std::unordered_map params_; + std::string in_; + bool body_sent_ = false; +};