Skip to content

Commit

Permalink
optimize header parse by zero-copy (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
poor-circle authored Aug 25, 2023
1 parent 2556a52 commit b9eccd5
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 521 deletions.
1 change: 1 addition & 0 deletions include/cinatra/cinatra_log_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#pragma once
#include <iostream>
namespace cinatra {
struct null_logger_t {
Expand Down
41 changes: 15 additions & 26 deletions include/cinatra/coro_http_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "async_simple/coro/Lazy.h"
#include "cinatra_log_wrapper.hpp"
#include "http_parser.hpp"
#include "picohttpparser.h"
#include "response_cv.hpp"
#include "uri.hpp"
#include "websocket.hpp"
Expand Down Expand Up @@ -50,11 +51,13 @@ inline ClientInjectAction inject_write_failed = ClientInjectAction::none;
inline ClientInjectAction inject_read_failed = ClientInjectAction::none;
#endif

struct http_header;

struct resp_data {
std::error_code net_err;
int status;
std::string_view resp_body;
std::vector<std::pair<std::string, std::string>> resp_headers;
std::span<http_header> resp_headers;
bool eof;
#ifdef BENCHMARK_TEST
uint64_t total;
Expand Down Expand Up @@ -1216,7 +1219,7 @@ class coro_http_client {
return std::make_error_code(std::errc::protocol_error);
}
read_buf_.consume(header_size); // header size
data.resp_headers = get_headers(parser);
data.resp_headers = parser.get_headers();
data.status = parser.status();
return {};
}
Expand All @@ -1234,8 +1237,7 @@ class coro_http_client {
break;
}

http_parser parser;
ec = handle_header(data, parser, size);
ec = handle_header(data, parser_, size);
#ifdef INJECT_FOR_HTTP_CLIENT_TEST
if (inject_header_valid == ClientInjectAction::header_error) {
ec = std::make_error_code(std::errc::protocol_error);
Expand All @@ -1248,32 +1250,32 @@ class coro_http_client {
break;
}

is_keep_alive = parser.keep_alive();
is_keep_alive = parser_.keep_alive();
if (method == http_method::HEAD) {
co_return data;
}

bool is_ranges = parser.is_ranges();
bool is_ranges = parser_.is_ranges();
if (is_ranges) {
is_keep_alive = true;
}
if (parser.is_chunked()) {
if (parser_.is_chunked()) {
is_keep_alive = true;
ec = co_await handle_chunked(data, std::move(ctx));
break;
}

redirect_uri_.clear();
bool is_redirect = parser.is_location();
bool is_redirect = parser_.is_location();
if (is_redirect)
redirect_uri_ = parser.get_header_value("Location");
redirect_uri_ = parser_.get_header_value("Location");

size_t content_len = (size_t)parser.body_len();
size_t content_len = (size_t)parser_.body_len();
#ifdef BENCHMARK_TEST
total_len_ = parser.total_len();
total_len_ = parser_.total_len();
#endif

if ((size_t)parser.body_len() <= read_buf_.size()) {
if ((size_t)parser_.body_len() <= read_buf_.size()) {
// Now get entire content, additional data will discard.
// copy body.
if (content_len > 0) {
Expand Down Expand Up @@ -1548,20 +1550,6 @@ class coro_http_client {
co_return resp_data{{}, 200};
}

std::vector<std::pair<std::string, std::string>> get_headers(
http_parser &parser) {
std::vector<std::pair<std::string, std::string>> resp_headers;

auto [headers, num_headers] = parser.get_headers();
for (size_t i = 0; i < num_headers; i++) {
resp_headers.emplace_back(
std::string(headers[i].name, headers[i].name_len),
std::string(headers[i].value, headers[i].value_len));
}

return resp_headers;
}

// this function must be called before async_ws_connect.
async_simple::coro::Lazy<void> async_read_ws() {
resp_data data{};
Expand Down Expand Up @@ -1722,6 +1710,7 @@ class coro_http_client {
return has_http_scheme;
}

http_parser parser_;
coro_io::ExecutorWrapper<> executor_wrapper_;
coro_io::period_timer timer_;
std::shared_ptr<socket_t> socket_;
Expand Down
88 changes: 38 additions & 50 deletions include/cinatra/http_parser.hpp
Original file line number Diff line number Diff line change
@@ -1,48 +1,62 @@
#pragma once
#include <algorithm>
#include <array>
#include <cctype>
#include <span>
#include <string>
#include <string_view>
#include <vector>

#include "cinatra_log_wrapper.hpp"
#include "picohttpparser.h"

using namespace std::string_view_literals;

#ifndef CINATRA_MAX_HTTP_HEADER_FIELD_SIZE
#define CINATRA_MAX_HTTP_HEADER_FIELD_SIZE 100
#endif

namespace cinatra {
class http_parser {
public:
int parse_response(const char *data, size_t size, int last_len) {
int minor_version;

num_headers_ = sizeof(headers_) / sizeof(headers_[0]);
num_headers_ = CINATRA_MAX_HTTP_HEADER_FIELD_SIZE;
const char *msg;
size_t msg_len;
header_len_ =
phr_parse_response(data, size, &minor_version, &status_, &msg, &msg_len,
headers_, &num_headers_, last_len);
header_len_ = cinatra::detail::phr_parse_response(
data, size, &minor_version, &status_, &msg, &msg_len, headers_.data(),
&num_headers_, last_len);
msg_ = {msg, msg_len};
auto header_value = this->get_header_value("content-length");
auto header_value = this->get_header_value("content-length"sv);
if (header_value.empty()) {
body_len_ = 0;
}
else {
body_len_ = atoi(header_value.data());
}

if (header_len_ < 0) [[unlikely]] {
CINATRA_LOG_WARNING << "parse http head failed";
if (size == CINATRA_MAX_HTTP_HEADER_FIELD_SIZE) {
CINATRA_LOG_ERROR << "the field of http head is out of max limit "
<< CINATRA_MAX_HTTP_HEADER_FIELD_SIZE
<< ", you can define macro "
"CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it.";
}
}
return header_len_;
}

std::string_view get_header_value(std::string_view key) const {
for (size_t i = 0; i < num_headers_; i++) {
if (iequal(headers_[i].name, headers_[i].name_len, key.data()))
return std::string_view(headers_[i].value, headers_[i].value_len);
if (iequal(headers_[i].name, key))
return headers_[i].value;
}

return {};
}

bool is_chunked() const {
auto transfer_encoding = this->get_header_value("transfer-encoding");
auto transfer_encoding = this->get_header_value("transfer-encoding"sv);
if (transfer_encoding == "chunked"sv) {
return true;
}
Expand All @@ -51,21 +65,21 @@ class http_parser {
}

bool is_ranges() const {
auto transfer_encoding = this->get_header_value("Accept-Ranges");
auto transfer_encoding = this->get_header_value("Accept-Ranges"sv);
return !transfer_encoding.empty();
}

bool is_websocket() const {
auto upgrade = this->get_header_value("Upgrade");
auto upgrade = this->get_header_value("Upgrade"sv);
return upgrade == "WebSocket"sv || upgrade == "websocket"sv;
}

bool keep_alive() const {
if (is_websocket()) {
return true;
}
auto val = this->get_header_value("connection");
if (val.empty() || iequal(val.data(), val.length(), "keep-alive")) {
auto val = this->get_header_value("connection"sv);
if (val.empty() || iequal(val, "keep-alive"sv)) {
return true;
}

Expand All @@ -81,55 +95,29 @@ class http_parser {
int total_len() const { return header_len_ + body_len_; }

bool is_location() {
auto location = this->get_header_value("Location");
auto location = this->get_header_value("Location"sv);
return !location.empty();
}

std::string_view msg() const { return msg_; }

std::pair<phr_header *, size_t> get_headers() {
return {headers_, num_headers_};
}

void set_headers(
const std::vector<std::pair<std::string, std::string>> &headers) {
num_headers_ = headers.size();
for (size_t i = 0; i < num_headers_; i++) {
headers_[i].name = headers[i].first.data();
headers_[i].name_len = headers[i].first.size();
headers_[i].value = headers[i].second.data();
headers_[i].value_len = headers[i].second.size();
}
std::span<http_header> get_headers() {
return {headers_.data(), num_headers_};
}

private:
std::string_view get_header_value(phr_header *headers, size_t num_headers,
std::string_view key) {
for (size_t i = 0; i < num_headers; i++) {
if (iequal(headers[i].name, headers[i].name_len, key.data()))
return std::string_view(headers[i].value, headers[i].value_len);
}

return {};
}

bool iequal(const char *s, size_t l, const char *t) const {
if (strlen(t) != l)
return false;

for (size_t i = 0; i < l; i++) {
if (std::tolower(s[i]) != std::tolower(t[i]))
return false;
}

return true;
bool iequal(std::string_view a, std::string_view b) const {
return std::equal(a.begin(), a.end(), b.begin(), b.end(),
[](char a, char b) {
return tolower(a) == tolower(b);
});
}

int status_ = 0;
std::string_view msg_;
size_t num_headers_ = 0;
int header_len_ = 0;
int body_len_ = 0;
struct phr_header headers_[100];
std::array<http_header, CINATRA_MAX_HTTP_HEADER_FIELD_SIZE> headers_;
};
} // namespace cinatra
Loading

0 comments on commit b9eccd5

Please sign in to comment.