Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve reverseproxy #520

Merged
merged 20 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ endif()

if (ENABLE_GZIP)
target_link_libraries(${project_name} ${ZLIB_LIBRARIES})
target_link_libraries(benchmark PRIVATE ${ZLIB_LIBRARIES})
endif()

if (ENABLE_SIMD STREQUAL "AARCH64")
Expand Down
1 change: 1 addition & 0 deletions include/cinatra.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define CINATRA_CINATRA_HPP

#include "cinatra/coro_http_client.hpp"
#include "cinatra/coro_http_reverse_proxy.hpp"
#include "cinatra/coro_http_server.hpp"
#include "cinatra/smtp_client.hpp"

Expand Down
18 changes: 9 additions & 9 deletions include/cinatra/cinatra_log_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,45 +31,45 @@ constexpr inline cinatra::null_logger_t NULL_LOGGER;
#ifdef CINATRA_LOG_ERROR
#else
#define CINATRA_LOG_ERROR \
cerr_logger_t {}
cinatra::cerr_logger_t {}
#endif

#ifdef CINATRA_LOG_WARNING
#else
#ifndef NDEBUG
#define CINATRA_LOG_WARNING \
cerr_logger_t {}
cinatra::cerr_logger_t {}
#else
#define CINATRA_LOG_WARNING NULL_LOGGER
#define CINATRA_LOG_WARNING cinatra::NULL_LOGGER
#endif
#endif

#ifdef CINATRA_LOG_INFO
#else
#ifndef NDEBUG
#define CINATRA_LOG_INFO \
cout_logger_t {}
cinatra::cout_logger_t {}
#else
#define CINATRA_LOG_INFO NULL_LOGGER
#define CINATRA_LOG_INFO cinatra::NULL_LOGGER
#endif
#endif

#ifdef CINATRA_LOG_DEBUG
#else
#ifndef NDEBUG
#define CINATRA_LOG_DEBUG \
cout_logger_t {}
cinatra::cout_logger_t {}
#else
#define CINATRA_LOG_DEBUG NULL_LOGGER
#define CINATRA_LOG_DEBUG cinatra::NULL_LOGGER
#endif
#endif

#ifdef CINATRA_LOG_TRACE
#else
#ifndef NDEBUG
#define CINATRA_LOG_TRACE \
cout_logger_t {}
cinatra::cout_logger_t {}
#else
#define CINATRA_LOG_TRACE NULL_LOGGER
#define CINATRA_LOG_TRACE cinatra::NULL_LOGGER
#endif
#endif
1 change: 0 additions & 1 deletion include/cinatra/coro_http_request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ class coro_http_request {
}

bool has_session() { return !cached_session_id_.empty(); }

void clear() { body_ = {}; }

std::unordered_map<std::string, std::string> params_;
Expand Down
66 changes: 49 additions & 17 deletions include/cinatra/coro_http_response.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ class coro_http_response {
void set_status_and_content(
status_type status, std::string content = "",
content_encoding encoding = content_encoding::none) {
set_status_and_content_view(status, content, encoding, false);
}

void set_status_and_content_view(
status_type status, std::string_view content = "",
content_encoding encoding = content_encoding::none, bool is_view = true) {
status_ = status;
#ifdef CINATRA_ENABLE_GZIP
if (encoding == content_encoding::gzip) {
std::string encode_str;
bool r = gzip_codec::compress(
std::string_view(content.data(), content.length()), encode_str, true);
bool r = gzip_codec::compress(content, encode_str, true);
if (!r) {
set_status_and_content(status_type::internal_server_error,
"gzip compress error");
Expand All @@ -70,7 +75,12 @@ class coro_http_response {
else
#endif
{
content_ = std::move(content);
if (is_view) {
content_view_ = content;
}
else {
content_ = std::move(content);
}
}
has_set_content_ = true;
}
Expand Down Expand Up @@ -102,12 +112,10 @@ class coro_http_response {
buffers.push_back(asio::buffer(to_http_status_string(status_)));
build_resp_head(buffers);
if (!content_.empty()) {
if (fmt_type_ == format_type::chunked) {
to_chunked_buffers(buffers, content_, true);
}
else {
buffers.push_back(asio::buffer(content_));
}
handle_content(buffers, content_);
}
else if (!content_view_.empty()) {
handle_content(buffers, content_view_);
}
}

Expand All @@ -116,7 +124,7 @@ class coro_http_response {
bool has_len = false;
bool has_host = false;
for (auto &[k, v] : resp_headers_) {
if (k == "Host") {
if (k == "Server") {
has_host = true;
}
if (k == "Content-Length") {
Expand Down Expand Up @@ -180,12 +188,15 @@ class coro_http_response {
bool has_len = false;
bool has_host = false;
for (auto &[k, v] : resp_headers_) {
if (k == "Host") {
if (k == "Server") {
has_host = true;
}
if (k == "Content-Length") {
else if (k == "Content-Length") {
has_len = true;
}
else if (k == "Date") {
need_date_ = false;
}
}

if (!has_host) {
Expand All @@ -209,11 +220,12 @@ class coro_http_response {
}

if (!content_.empty()) {
auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_.size());
buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV));
buffers.emplace_back(
asio::buffer(std::string_view(buf_, std::distance(buf_, ptr))));
buffers.emplace_back(asio::buffer(CRCF));
if (!has_len)
handle_content_len(buffers, content_);
}
else if (!content_view_.empty()) {
if (!has_len)
handle_content_len(buffers, content_view_);
}
else {
if (!has_len && boundary_.empty())
Expand Down Expand Up @@ -279,6 +291,25 @@ class coro_http_response {
}

private:
void handle_content(std::vector<asio::const_buffer> &buffers,
std::string_view content) {
if (fmt_type_ == format_type::chunked) {
to_chunked_buffers(buffers, content, true);
}
else {
buffers.push_back(asio::buffer(content));
}
}

void handle_content_len(std::vector<asio::const_buffer> &buffers,
std::string_view content) {
auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content.size());
buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV));
buffers.emplace_back(
asio::buffer(std::string_view(buf_, std::distance(buf_, ptr))));
buffers.emplace_back(asio::buffer(CRCF));
}

status_type status_;
format_type fmt_type_;
std::string content_;
Expand All @@ -293,5 +324,6 @@ class coro_http_response {
bool need_date_ = true;
std::unordered_map<std::string, cookie> cookies_;
std::string_view content_type_;
std::string_view content_view_;
};
} // namespace cinatra
108 changes: 108 additions & 0 deletions include/cinatra/coro_http_reverse_proxy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <mutex>
#include <stdexcept>
#include <string>
#include <unordered_map>

#include "cinatra/coro_http_client.hpp"
#include "cinatra/coro_http_server.hpp"
#include "ylt/coro_io/channel.hpp"

namespace cinatra {

class reverse_proxy {
public:
reverse_proxy(size_t thread_num, unsigned short port)
: server_(thread_num, port) {}

void add_dest_host(std::string url, int weight = 0) {
dest_hosts_.push_back(std::move(url));
weights_.push_back(weight);
}

template <http_method... method>
void start_reverse_proxy(
std::string url_path = "/", bool sync = true,
coro_io::load_blance_algorithm type =
coro_io::load_blance_algorithm::random,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
if (dest_hosts_.empty()) {
throw std::invalid_argument("not config hosts yet!");
}

std::vector<std::string_view> hosts{dest_hosts_.begin(), dest_hosts_.end()};

channel_ = std::make_shared<coro_io::channel<coro_http_client>>(
coro_io::channel<coro_http_client>::create(hosts, {.lba = type},
weights_));

server_.set_http_handler<method...>(
url_path,
[this, type, url_path](
coro_http_request &req,
coro_http_response &response) -> async_simple::coro::Lazy<void> {
co_await channel_->send_request(
[this, &req, &response](
coro_http_client &client,
std::string_view host) -> async_simple::coro::Lazy<void> {
uri_t uri;
uri.parse_from(host.data());
co_await reply(client, uri.get_path(), req, response);
});
},
std::move(aspects));

start(sync);
}

private:
async_simple::coro::Lazy<void> reply(coro_http_client &client,
std::string url_path,
coro_http_request &req,
coro_http_response &response) {
auto req_headers = copy_request_headers(req.get_headers());
auto ctx = req_context<std::string_view>{.content = req.get_body()};
auto result = co_await client.async_request(
std::move(url_path), method_type(req.get_method()), std::move(ctx),
std::move(req_headers));

for (auto &[k, v] : result.resp_headers) {
response.add_header(std::string(k), std::string(v));
}

response.set_status_and_content_view(
static_cast<status_type>(result.status), result.resp_body);
co_await response.get_conn()->reply();
response.set_delay(true);
}

void start(bool sync) {
if (sync) {
server_.sync_start();
}
else {
server_.async_start();
}
}

std::unordered_map<std::string, std::string> copy_request_headers(
std::span<http_header> req_headers) {
std::unordered_map<std::string, std::string> request_headers;
for (auto &[k, v] : req_headers) {
request_headers.emplace(k, v);
}

return request_headers;
}

coro_http_server server_;
std::shared_ptr<coro_io::channel<coro_http_client>> channel_;

// real dest hosts
std::vector<std::string> dest_hosts_;
std::vector<int> weights_;
};

} // namespace cinatra
16 changes: 12 additions & 4 deletions include/cinatra/define.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ using namespace std::string_view_literals;

namespace cinatra {
enum class http_method {
UNKNOW,
DEL,
NIL = 0,
GET,
HEAD,
POST,
PUT,
TRACE,
PATCH,
CONNECT,
OPTIONS,
TRACE
DEL,
};
constexpr inline auto GET = http_method::GET;
constexpr inline auto POST = http_method::POST;
Expand Down Expand Up @@ -52,10 +52,18 @@ inline constexpr std::string_view method_name(http_method mthd) {
case cinatra::http_method::TRACE:
return "TRACE"sv;
default:
return "UNKONWN"sv;
return "NIL"sv;
}
}

inline constexpr std::array<int, 20> method_table = {
3, 1, 9, 0, 0, 0, 4, 5, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 6, 7};

inline constexpr http_method method_type(std::string_view mthd) {
int index = ((mthd[0] & ~0x20) ^ ((mthd[1] + 1) & ~0x20)) % 20;
return (http_method)method_table[index];
}

enum class transfer_type { CHUNKED, ACCEPT_RANGES };

enum class content_type {
Expand Down
2 changes: 1 addition & 1 deletion include/cinatra/uri.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ struct context {
std::string path;
std::string query;
std::string body;
http_method method = http_method::UNKNOW;
http_method method = http_method::NIL;

context() = default;
context(const uri_t &u, http_method mthd)
Expand Down
Loading
Loading