From 0cc3f17aab2b837c1ebb00b4acbcaef2bd99d89e Mon Sep 17 00:00:00 2001 From: helintongh Date: Fri, 22 Nov 2024 13:41:46 +0800 Subject: [PATCH 1/3] [struct_pb] .proto file to struc_pack tool (#808) --- src/struct_pb/tools/CMakeLists.txt | 14 + src/struct_pb/tools/README.md | 402 ++++++++++++++++++ src/struct_pb/tools/proto_to_struct.cpp | 135 ++++++ src/struct_pb/tools/struct_code_generator.hpp | 266 ++++++++++++ src/struct_pb/tools/struct_token.hpp | 125 ++++++ 5 files changed, 942 insertions(+) create mode 100644 src/struct_pb/tools/CMakeLists.txt create mode 100644 src/struct_pb/tools/README.md create mode 100644 src/struct_pb/tools/proto_to_struct.cpp create mode 100644 src/struct_pb/tools/struct_code_generator.hpp create mode 100644 src/struct_pb/tools/struct_token.hpp diff --git a/src/struct_pb/tools/CMakeLists.txt b/src/struct_pb/tools/CMakeLists.txt new file mode 100644 index 000000000..f8eec1720 --- /dev/null +++ b/src/struct_pb/tools/CMakeLists.txt @@ -0,0 +1,14 @@ +project(proto_to_struct) + +cmake_minimum_required(VERSION 3.10) + +set(CMAKE_CXX_STANDARD 20) + +find_package(Protobuf REQUIRED) +include_directories(${PROTOBUF_INCLUDE_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +set(SOURCE_FILE proto_to_struct.cpp) + +add_executable(proto_to_struct ${SOURCE_FILE}) +target_link_libraries(proto_to_struct protobuf::libprotobuf protobuf::libprotoc pthread) diff --git a/src/struct_pb/tools/README.md b/src/struct_pb/tools/README.md new file mode 100644 index 000000000..b14f72fe3 --- /dev/null +++ b/src/struct_pb/tools/README.md @@ -0,0 +1,402 @@ +# Instructions for using the .proto file to struc_pack header tool + +## compile + +libprotobuf and libprotoc version is 3.21.0. + +```shell +mkdir build +cd build +cmake .. && make +``` + +## usage + +Usage: + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=:./protos +``` + +data.proto is the original file that is intended to be the structure pack file. + +`--custom_out=` is followed by the path to the generated file. + +data.proto: + +```proto +syntax = "proto3"; + +package mygame; + +option optimize_for = SPEED; +option cc_enable_arenas = true; + +message Vec3 { + float x = 1; + float y = 2; + float z = 3; +} + +message Weapon { + string name = 1; + int32 damage = 2; +} + +message Monster { + Vec3 pos = 1; + int32 mana = 2; + int32 hp = 3; + string name = 4; + bytes inventory = 5; + enum Color { + Red = 0; + Green = 1; + Blue = 2; + } + Color color = 6; + repeated Weapon weapons = 7; + Weapon equipped = 8; + repeated Vec3 path = 9; +} + +message Monsters { + repeated Monster monsters = 1; +} + +message person { + int32 id = 1; + string name = 2; + int32 age = 3; + double salary = 4; +} + +message persons { + repeated person person_list = 1; +} + +message bench_int32 { + int32 a = 1; + int32 b = 2; + int32 c = 3; + int32 d = 4; +} +``` + +generate struct pack file: + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 { + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon { + std::string name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster { + Vec3 pos; + int32_t mana; + int32_t hp; + std::string name; + std::string inventory; + Color color; + std::vectorweapons; + Weapon equipped; + std::vectorpath; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters { + std::vectormonsters; +}; +YLT_REFL(Monsters, monsters); + +struct person { + int32_t id; + std::string name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons { + std::vectorperson_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 { + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +There are two parameters: + +## add_optional + +Generate C++ files in optional struct pack format. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=add_optional:./protos +``` + +In the generated file, `std::string` will be converted to `std::optional`, and the 'class' type(For example `class Foo`) will be converted to `std::optional`. + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 { + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon { + std::optional name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster { + std::optional pos; + int32_t mana; + int32_t hp; + std::optional name; + std::optional inventory; + Color color; + std::optional> weapons; + std::optional equipped; + std::optional> path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters { + std::optional> monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person { + int32_t id; + std::optional name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons { + std::optional> person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 { + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +## enable_inherit + +Generate C++ files in non std::optional format and the file conforms to the `struct pb` standard. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=enable_inherit:./protos +``` + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 : public iguana::base_impl { + Vec3() = default; + Vec3(float a, float b, float c) : x(a), y(b), z(c) {} + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon : public iguana::base_impl { + Weapon() = default; + Weapon(std::string a, int32_t b) : name(std::move(a)), damage(b) {} + std::string name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster : public iguana::base_impl { + Monster() = default; + Monster(Vec3 a, int32_t b, int32_t c, std::string d, std::string e, Color f, std::vector g, Weapon h, std::vector i) : pos(a), mana(b), hp(c), name(std::move(d)), inventory(std::move(e)), color(f), weapons(std::move(g)), equipped(h), path(std::move(i)) {} + Vec3 pos; + int32_t mana; + int32_t hp; + std::string name; + std::string inventory; + Color color; + std::vector weapons; + Weapon equipped; + std::vector path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters : public iguana::base_impl { + Monsters() = default; + Monsters(std::vector a) : monsters(std::move(a)) {} + std::vector monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person : public iguana::base_impl { + person() = default; + person(int32_t a, std::string b, int32_t c, double d) : id(a), name(std::move(b)), age(c), salary(d) {} + int32_t id; + std::string name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons : public iguana::base_impl { + persons() = default; + persons(std::vector a) : person_list(std::move(a)) {} + std::vector person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 : public iguana::base_impl { + bench_int32() = default; + bench_int32(int32_t a, int32_t b, int32_t c, int32_t d) : a(a), b(b), c(c), d(d) {} + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` + +## add_optional and enable_inherit + +The presence of these two parameters indicates that these two functions take effect on the generated file at the same time. + +```shell +protoc --plugin=protoc-gen-custom=./build/proto_to_struct data.proto --custom_out=add_optional+enable_inherit:./protos +``` + +```cpp +#pragma once +#include + +enum class Color { + Red = 0, + Green = 1, + Blue = 2, +}; + +struct Vec3 : public iguana::base_impl { + Vec3() = default; + Vec3(float a, float b, float c) : x(a), y(b), z(c) {} + float x; + float y; + float z; +}; +YLT_REFL(Vec3, x, y, z); + +struct Weapon : public iguana::base_impl { + Weapon() = default; + Weapon(std::optional a, int32_t b) : name(std::move(a)), damage(b) {} + std::optional name; + int32_t damage; +}; +YLT_REFL(Weapon, name, damage); + +struct Monster : public iguana::base_impl { + Monster() = default; + Monster(std::optional a, int32_t b, int32_t c, std::optional d, std::optional e, Color f, std::optional> g, std::optional h, std::optional> i) : pos(a), mana(b), hp(c), name(std::move(d)), inventory(std::move(e)), color(f), weapons(std::move(g)), equipped(h), path(std::move(i)) {} + std::optional pos; + int32_t mana; + int32_t hp; + std::optional name; + std::optional inventory; + Color color; + std::optional> weapons; + std::optional equipped; + std::optional> path; +}; +YLT_REFL(Monster, pos, mana, hp, name, inventory, color, weapons, equipped, path); + +struct Monsters : public iguana::base_impl { + Monsters() = default; + Monsters(std::optional> a) : monsters(std::move(a)) {} + std::optional> monsters; +}; +YLT_REFL(Monsters, monsters); + +struct person : public iguana::base_impl { + person() = default; + person(int32_t a, std::optional b, int32_t c, double d) : id(a), name(std::move(b)), age(c), salary(d) {} + int32_t id; + std::optional name; + int32_t age; + double salary; +}; +YLT_REFL(person, id, name, age, salary); + +struct persons : public iguana::base_impl { + persons() = default; + persons(std::optional> a) : person_list(std::move(a)) {} + std::optional> person_list; +}; +YLT_REFL(persons, person_list); + +struct bench_int32 : public iguana::base_impl { + bench_int32() = default; + bench_int32(int32_t a, int32_t b, int32_t c, int32_t d) : a(a), b(b), c(c), d(d) {} + int32_t a; + int32_t b; + int32_t c; + int32_t d; +}; +YLT_REFL(bench_int32, a, b, c, d); + + +``` \ No newline at end of file diff --git a/src/struct_pb/tools/proto_to_struct.cpp b/src/struct_pb/tools/proto_to_struct.cpp new file mode 100644 index 000000000..d4028d2ca --- /dev/null +++ b/src/struct_pb/tools/proto_to_struct.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "struct_code_generator.hpp" +#include "struct_token.hpp" + +bool write_to_output(google::protobuf::io::ZeroCopyOutputStream* output, + const void* data, int size) { + const uint8_t* in = reinterpret_cast(data); + int in_size = size; + + void* out; + int out_size; + + while (true) { + if (!output->Next(&out, &out_size)) { + return false; + } + + if (in_size <= out_size) { + memcpy(out, in, in_size); + output->BackUp(out_size - in_size); + return true; + } + + memcpy(out, in, out_size); + in += out_size; + in_size -= out_size; + } +} + +class struct_code_generator : public google::protobuf::compiler::CodeGenerator { + public: + virtual ~struct_code_generator() {} + + virtual bool Generate(const google::protobuf::FileDescriptor* file, + const std::string& parameter, + google::protobuf::compiler::GeneratorContext* context, + std::string* error) const override { + std::string filename = file->name() + ".h"; + + auto output = context->Open(filename); + + bool enable_optional = false; + bool enable_inherit = false; + + if (parameter.find("add_optional") != std::string::npos) { + enable_optional = true; + } + + if (parameter.find("enable_inherit") != std::string::npos) { + enable_inherit = true; + } + + // Use ZeroCopyOutputStream + google::protobuf::io::ZeroCopyOutputStream* zero_copy_output = output; + + std::vector proto_module_info; + std::vector proto_enum_info; + for (int i = 0; i < file->message_type_count(); ++i) { + // struct name + const google::protobuf::Descriptor* descriptor = file->message_type(i); + + struct_enum enum_token; + enum_token.clear(); + enum_token.get_enum_fields(descriptor); + proto_enum_info.emplace_back(enum_token); + + struct_tokenizer tokenizer; + tokenizer.clear(); + tokenizer.tokenizer(descriptor); + proto_module_info.emplace_back(tokenizer); + } + + std::string struct_header = code_generate_header(); + write_to_output(zero_copy_output, (const void*)struct_header.c_str(), + struct_header.size()); + + // codegen struct enum + for (auto enum_inst : proto_enum_info) { + std::string enum_str = ""; + enum_str = code_generate_enum(enum_inst); + write_to_output(zero_copy_output, (const void*)enum_str.c_str(), + enum_str.size()); + } + + // codegen struct + std::vector struct_module_contents; + + for (auto single_struct : proto_module_info) { + std::string struct_default_str = ""; + std::string struct_constructor_str = ""; + std::string struct_body_str = ""; + std::string struct_macro_str = ""; + + struct_default_str = code_generate_struct_default( + single_struct.get_struct_name(), enable_inherit); + struct_constructor_str = code_generate_struct_constructor( + single_struct.get_struct_name(), single_struct.get_tokens(), + enable_optional); + struct_body_str = + code_generate_body(single_struct.get_tokens(), enable_optional); + struct_macro_str = code_generate_ylt_macro( + single_struct.get_struct_name(), single_struct.get_tokens()); + + write_to_output(zero_copy_output, (const void*)struct_default_str.c_str(), + struct_default_str.size()); + if (enable_inherit) + write_to_output(zero_copy_output, + (const void*)struct_constructor_str.c_str(), + struct_constructor_str.size()); + write_to_output(zero_copy_output, (const void*)struct_body_str.c_str(), + struct_body_str.size()); + write_to_output(zero_copy_output, (const void*)struct_macro_str.c_str(), + struct_macro_str.size()); + } + + delete zero_copy_output; + return true; + } +}; + +int main(int argc, char* argv[]) { + google::protobuf::compiler::PluginMain(argc, argv, + new struct_code_generator()); + return 0; +} \ No newline at end of file diff --git a/src/struct_pb/tools/struct_code_generator.hpp b/src/struct_pb/tools/struct_code_generator.hpp new file mode 100644 index 000000000..7648850b5 --- /dev/null +++ b/src/struct_pb/tools/struct_code_generator.hpp @@ -0,0 +1,266 @@ +#pragma once +#include + +#include "struct_token.hpp" + +char parameter_value[27] = "abcdefghijklmnopqrstuvwxyz"; + +std::string code_generate_header() { + std::string result = "#pragma once\n#include \n\n"; + return result; +} + +std::string code_generate_struct_default(const std::string &struct_name, + bool enable_inherit) { + std::string result = "struct "; + result.append(struct_name); + + if (enable_inherit) { + result.append(" : public iguana::base_impl<"); + result.append(struct_name); + result.append("> "); + } + + result.append(" {\n"); + + if (enable_inherit) { + result.append("\t"); + result.append(struct_name); + result.append("() = default;\n\t"); + } + return result; +} + +std::string code_generate_struct_constructor( + const std::string &struct_name, const std::vector lists, + bool add_optional) { + std::string result = struct_name + "("; + int i = 0; + int list_size = lists.size() - 1; + for (auto it = lists.begin(); it != lists.end(); it++) { + if (it->type == struct_token_type::pod) { + if (it->lable == lable_type::lable_repeated) { + result.append("std::vector<"); + result.append(it->type_name); + result.append("> "); + result += parameter_value[i]; + } + else { + result.append(it->type_name); + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + else if (it->type == struct_token_type::proto_string) { + if (it->lable == lable_type::lable_repeated) { + if (add_optional) { + result.append("std::optional<"); + } + result.append("std::vector"); + if (add_optional) { + result.append("> "); + } + result += parameter_value[i]; + } + else { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::"); + result.append(it->type_name); + if (add_optional) { + result.append(">"); + } + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + else if (it->type == struct_token_type::enum_type) { + // enum no repeated type + result.append(it->type_name); + result.append(" "); + result += parameter_value[i]; + if (i != list_size) + result.append(", "); + i++; + } + else { + if (it->lable == lable_type::lable_repeated) { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::vector<"); + + result.append(it->type_name); + result.append(">"); + + if (add_optional) { + result.append("> "); + } + + result += parameter_value[i]; + } + else { + if (add_optional) { + result.append("std::optional<"); + } + result.append(it->type_name); + if (add_optional) { + result.append(">"); + } + result.append(" "); + result += parameter_value[i]; + } + if (i != list_size) + result.append(", "); + i++; + } + } + result.append(") : "); + + int j = 0; + for (auto ll : lists) { + if (ll.type == struct_token_type::pod || + ll.type == struct_token_type::message || + ll.type == struct_token_type::enum_type) { + result.append(ll.var_name); + result.append("("); + if (ll.lable == lable_type::lable_repeated) { + result.append("std::move("); + result += parameter_value[j]; + result.append(")"); + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + else { + result += parameter_value[j]; + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + } + else if (ll.type == struct_token_type::proto_string) { + result.append(ll.var_name); + result.append("("); + result.append("std::move("); + result += parameter_value[j]; + result.append(")"); + if (j != list_size) + result.append("), "); + else + result.append(") {}"); + } + j++; + } + result.append("\n"); + return result; +} + +std::string code_generate_body(const std::vector &lists, + bool add_optional) { + std::string result; + for (auto ll : lists) { + result.append("\t"); + if (ll.lable == lable_type::lable_repeated) { + if (ll.type == struct_token_type::proto_string) { + if (add_optional) { + result.append("std::optional<"); + } + + result.append("std::vector"); + if (add_optional) { + result.append("> "); + } + result.append(ll.var_name); + result.append(";\n"); + } + else { + if (ll.type == struct_token_type::message && add_optional) { + result.append("std::optional<"); + } + result.append("std::vector<"); + result.append(ll.type_name); + result.append(">"); + if (ll.type == struct_token_type::message && add_optional) { + result.append("> "); + } + result.append(ll.var_name); + result.append(";\n"); + } + } + else { + if (ll.type != struct_token_type::pod && + ll.type != struct_token_type::enum_type && add_optional) { + result.append("std::optional<"); + } + + if (ll.type == struct_token_type::proto_string) + result.append("std::"); + result.append(ll.type_name); + if (ll.type != struct_token_type::pod && + ll.type != struct_token_type::enum_type && add_optional) { + result.append(">"); + } + result.append(" "); + result.append(ll.var_name); + result.append(";\n"); + } + } + result.append("};\n"); + + return result; +} + +std::string code_generate_ylt_macro(const std::string &struct_name, + const std::vector &lists) { + std::string result = "YLT_REFL("; + result.append(struct_name); + result.append(", "); + int i = 0; + int list_size = lists.size() - 1; + for (auto ll : lists) { + if (i != list_size) { + result.append(ll.var_name); + result.append(", "); + } + else { + result.append(ll.var_name); + } + i++; + } + result.append(");\n\n"); + return result; +} + +std::string code_generate_enum(const struct_enum &enum_inst) { + std::string result = "enum class "; + if (enum_inst.enum_name_.empty()) + return ""; + result.append(enum_inst.enum_name_); + result.append(" {\n"); + if (enum_inst.fields_.size() == 0) { + result.append("}\n"); + } + else { + for (auto i : enum_inst.fields_) { + result.append("\t"); + result.append(i.first); + result.append(" = "); + result.append(std::to_string(i.second)); + result.append(",\n"); + } + result.append("};\n\n"); + } + return result; +} \ No newline at end of file diff --git a/src/struct_pb/tools/struct_token.hpp b/src/struct_pb/tools/struct_token.hpp new file mode 100644 index 000000000..61bfe4f41 --- /dev/null +++ b/src/struct_pb/tools/struct_token.hpp @@ -0,0 +1,125 @@ +#pragma once +#include + +#include +#include + +enum class struct_token_type { + pod, + proto_string, + message, + enum_type, + null_type +}; + +enum class lable_type { + lable_optional, + lable_required, + lable_repeated, + lable_null +}; + +struct struct_token { + struct_token() { + this->var_name = ""; + this->type_name = ""; + this->type = struct_token_type::null_type; + this->lable = lable_type::lable_null; + } + + void clear(); + std::string var_name; + std::string type_name; + struct_token_type type; + lable_type lable; +}; + +class struct_tokenizer { + public: + struct_tokenizer() { clear(); } + + void tokenizer(const google::protobuf::Descriptor* descriptor) { + struct_name_ = descriptor->name(); + for (int j = 0; j < descriptor->field_count(); ++j) { + struct_token token = {}; + const google::protobuf::FieldDescriptor* field = descriptor->field(j); + token.var_name = field->name(); + if (field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { + token.type_name = field->message_type()->name(); + token.type = struct_token_type::message; + } + else if (field->type() == + google::protobuf::FieldDescriptor::TYPE_STRING) { + token.type_name = field->type_name(); + token.type = struct_token_type::proto_string; + // std::cout << "string var anme is: " << token.var_name << std::endl; + } + else { + token.type_name = field->type_name(); + if (token.type_name == "bytes") { + token.type = struct_token_type::proto_string; + token.type_name = "string"; + } + else if (token.type_name == "enum") { + const google::protobuf::EnumDescriptor* enum_desc = + field->enum_type(); + token.type_name = enum_desc->name(); + token.type = struct_token_type::enum_type; + } + else { + token.type = struct_token_type::pod; + if (token.type_name.find("int") != std::string::npos) + token.type_name += "_t"; + } + } + + if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_REPEATED) + token.lable = lable_type::lable_repeated; + else if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_OPTIONAL) + token.lable = lable_type::lable_optional; + else if (field->label() == + google::protobuf::FieldDescriptor::Label::LABEL_REQUIRED) + token.lable = lable_type::lable_required; + + token_lists_.emplace_back(token); + } + } + + void clear() { + token_lists_.clear(); + struct_name_ = ""; + } + + std::vector& get_tokens() { return token_lists_; } + std::string& get_struct_name() { return struct_name_; } + + private: + std::vector token_lists_; + std::string struct_name_; // struct name +}; + +struct struct_enum { + struct_enum() { clear(); } + + void clear() { + this->enum_name_ = ""; + this->fields_.clear(); + } + + void get_enum_fields(const google::protobuf::Descriptor* descriptor) { + for (int e = 0; e < descriptor->enum_type_count(); ++e) { + const google::protobuf::EnumDescriptor* enum_desc = + descriptor->enum_type(e); + enum_name_ = enum_desc->name(); + for (int v = 0; v < enum_desc->value_count(); ++v) { + const google::protobuf::EnumValueDescriptor* value_desc = + enum_desc->value(v); + fields_.push_back({value_desc->name(), value_desc->number()}); + } + } + } + std::string enum_name_; + std::vector> fields_; +}; \ No newline at end of file From 434c8d91787db62c4e9e134b51d2b3e38edece82 Mon Sep 17 00:00:00 2001 From: qicosmos Date: Mon, 25 Nov 2024 10:15:10 +0800 Subject: [PATCH 2/3] [coro_http][unit test]Test http server (#819) --- include/ylt/reflection/member_count.hpp | 1 + .../standalone/cinatra/coro_http_client.hpp | 34 +++++ .../cinatra/coro_http_connection.hpp | 116 ++++---------- .../standalone/cinatra/coro_http_server.hpp | 77 +++------- .../ylt/standalone/cinatra/metric_conf.hpp | 130 ---------------- include/ylt/standalone/cinatra/websocket.hpp | 2 +- src/coro_http/tests/test_cinatra.cpp | 143 +++++++++++++++++- .../tests/test_cinatra_websocket.cpp | 35 +++++ src/coro_http/tests/test_coro_http_server.cpp | 67 +++----- 9 files changed, 289 insertions(+), 316 deletions(-) delete mode 100644 include/ylt/standalone/cinatra/metric_conf.hpp diff --git a/include/ylt/reflection/member_count.hpp b/include/ylt/reflection/member_count.hpp index 04d12a421..eac839063 100644 --- a/include/ylt/reflection/member_count.hpp +++ b/include/ylt/reflection/member_count.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/include/ylt/standalone/cinatra/coro_http_client.hpp b/include/ylt/standalone/cinatra/coro_http_client.hpp index 00a1db329..f38cf3622 100644 --- a/include/ylt/standalone/cinatra/coro_http_client.hpp +++ b/include/ylt/standalone/cinatra/coro_http_client.hpp @@ -1372,6 +1372,40 @@ class coro_http_client : public std::enable_shared_from_this { #endif } +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + async_simple::coro::Lazy async_write_raw( + std::string_view data) { + auto [ec, _] = co_await async_write(asio::buffer(data)); + co_return ec; + } + + async_simple::coro::Lazy async_read_raw( + http_method method, bool clear_buffer = false) { + if (clear_buffer) { + body_.clear(); + } + + char buf[1024]; + std::error_code ec{}; + size_t size{}; +#ifdef CINATRA_ENABLE_SSL + if (has_init_ssl_) { + std::tie(ec, size) = co_await coro_io::async_read_some( + *socket_->ssl_stream_, asio::buffer(buf, 1024)); + } + else { +#endif + std::tie(ec, size) = co_await coro_io::async_read_some( + socket_->impl_, asio::buffer(buf, 1024)); +#ifdef CINATRA_ENABLE_SSL + } +#endif + body_.append(buf, size); + + co_return resp_data{ec, {}, {}, body_}; + } +#endif + inline void set_proxy(const std::string &host, const std::string &port) { proxy_host_ = host; proxy_port_ = port; diff --git a/include/ylt/standalone/cinatra/coro_http_connection.hpp b/include/ylt/standalone/cinatra/coro_http_connection.hpp index d7308c90c..2ec5d1636 100644 --- a/include/ylt/standalone/cinatra/coro_http_connection.hpp +++ b/include/ylt/standalone/cinatra/coro_http_connection.hpp @@ -21,14 +21,9 @@ #include "sha1.hpp" #include "string_resize.hpp" #include "websocket.hpp" -#include "ylt/metric/counter.hpp" -#include "ylt/metric/gauge.hpp" -#include "ylt/metric/histogram.hpp" -#include "ylt/metric/metric.hpp" #ifdef CINATRA_ENABLE_GZIP #include "gzip.hpp" #endif -#include "metric_conf.hpp" #include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_io.hpp" @@ -52,14 +47,9 @@ class coro_http_connection request_(parser_, this), response_(this) { buffers_.reserve(3); - - cinatra_metric_conf::server_total_fd_inc(); } - ~coro_http_connection() { - cinatra_metric_conf::server_total_fd_dec(); - close(); - } + ~coro_http_connection() { close(); } #ifdef CINATRA_ENABLE_SSL bool init_ssl(const std::string &cert_file, const std::string &key_file, @@ -126,21 +116,17 @@ class coro_http_connection CINATRA_LOG_WARNING << "read http header error: " << ec.message(); } - cinatra_metric_conf::server_failed_req_inc(); close(); break; } - if (cinatra_metric_conf::enable_metric) { - start = std::chrono::system_clock::now(); - cinatra_metric_conf::server_total_req_inc(); - } - const char *data_ptr = asio::buffer_cast(head_buf_.data()); int head_len = parser_.parse_request(data_ptr, size, 0); if (head_len <= 0) { - cinatra_metric_conf::server_failed_req_inc(); CINATRA_LOG_ERROR << "parse http header error"; + response_.set_status_and_content(status_type::bad_request, + "invalid http protocol"); + co_await reply(); close(); break; } @@ -153,9 +139,6 @@ class coro_http_connection if (type != content_type::chunked && type != content_type::multipart) { size_t body_len = parser_.body_len(); if (body_len == 0) { - if (cinatra_metric_conf::enable_metric) { - cinatra_metric_conf::server_total_recv_bytes_inc(head_len); - } if (parser_.method() == "GET"sv) { if (request_.is_upgrade()) { #ifdef CINATRA_ENABLE_GZIP @@ -175,16 +158,6 @@ class coro_http_connection } response_.set_delay(true); } - else { - if (cinatra_metric_conf::enable_metric) { - mid = std::chrono::system_clock::now(); - double count = - std::chrono::duration_cast(mid - - start) - .count(); - cinatra_metric_conf::server_read_latency_observe(count); - } - } } } else if (body_len <= head_buf_.size()) { @@ -194,7 +167,6 @@ class coro_http_connection memcpy(body_.data(), data_ptr, body_len); head_buf_.consume(head_buf_.size()); } - cinatra_metric_conf::server_total_recv_bytes_inc(head_len + body_len); } else { size_t part_size = head_buf_.size(); @@ -209,22 +181,9 @@ class coro_http_connection size_to_read); if (ec) { CINATRA_LOG_ERROR << "async_read error: " << ec.message(); - cinatra_metric_conf::server_failed_req_inc(); close(); break; } - else { - if (cinatra_metric_conf::enable_metric) { - cinatra_metric_conf::server_total_recv_bytes_inc(head_len + - body_len); - mid = std::chrono::system_clock::now(); - double count = - std::chrono::duration_cast(mid - - start) - .count(); - cinatra_metric_conf::server_read_latency_observe(count); - } - } } } @@ -358,37 +317,44 @@ class coro_http_connection while (true) { size_t left_size = head_buf_.size(); - auto data_ptr = asio::buffer_cast(head_buf_.data()); - std::string_view left_content{data_ptr, left_size}; + auto next_data_ptr = + asio::buffer_cast(head_buf_.data()); + std::string_view left_content{next_data_ptr, left_size}; size_t pos = left_content.find(TWO_CRCF); if (pos == std::string_view::npos) { break; } http_parser parser; - int head_len = parser.parse_request(data_ptr, size, 0); + int head_len = parser.parse_request(next_data_ptr, left_size, 0); if (head_len <= 0) { CINATRA_LOG_ERROR << "parse http header error"; + response_.set_status_and_content(status_type::bad_request, + "invalid http protocol"); + co_await reply(); close(); break; } head_buf_.consume(pos + TWO_CRCF.length()); - std::string_view key = { - parser_.method().data(), - parser_.method().length() + 1 + parser_.url().length()}; + std::string_view next_key = { + parser.method().data(), + parser.method().length() + 1 + parser.url().length()}; coro_http_request req(parser, this); coro_http_response resp(this); resp.need_date_head(response_.need_date()); - if (auto handler = router_.get_handler(key); handler) { + if (auto handler = router_.get_handler(next_key); handler) { router_.route(handler, req, resp, key); } else { - if (auto coro_handler = router_.get_coro_handler(key); + if (auto coro_handler = router_.get_coro_handler(next_key); coro_handler) { co_await router_.route_coro(coro_handler, req, resp, key); } + else { + resp.set_status(status_type::not_found); + } } resp.build_resp_str(resp_str_); @@ -409,14 +375,6 @@ class coro_http_connection } } - if (cinatra_metric_conf::enable_metric) { - mid = std::chrono::system_clock::now(); - double count = - std::chrono::duration_cast(mid - start) - .count(); - cinatra_metric_conf::server_req_latency_observe(count); - } - response_.clear(); request_.clear(); buffers_.clear(); @@ -430,10 +388,6 @@ class coro_http_connection } async_simple::coro::Lazy reply(bool need_to_bufffer = true) { - if (response_.status() >= status_type::bad_request) { - if (cinatra_metric_conf::enable_metric) - cinatra_metric_conf::server_failed_req_inc(); - } std::error_code ec; size_t size; if (multi_buf_) { @@ -444,18 +398,12 @@ class coro_http_connection for (auto &buf : buffers_) { send_size += buf.size(); } - if (cinatra_metric_conf::enable_metric) { - cinatra_metric_conf::server_total_send_bytes_inc(send_size); - } std::tie(ec, size) = co_await async_write(buffers_); } else { if (need_to_bufffer) { response_.build_resp_str(resp_str_); } - if (cinatra_metric_conf::enable_metric) { - cinatra_metric_conf::server_total_send_bytes_inc(resp_str_.size()); - } std::tie(ec, size) = co_await async_write(asio::buffer(resp_str_)); } @@ -513,8 +461,6 @@ class coro_http_connection co_return true; } - bool sync_reply() { return async_simple::coro::syncAwait(reply()); } - async_simple::coro::Lazy begin_chunked() { response_.set_delay(true); response_.set_status(status_type::ok); @@ -681,6 +627,18 @@ class coro_http_connection head_buf_.consume(head_buf_.size()); std::span payload{}; auto payload_length = ws_.payload_length(); + + if (max_part_size_ != 0 && payload_length > max_part_size_) { + std::string close_reason = "message_too_big"; + std::string close_msg = ws_.format_close_payload( + close_code::too_big, close_reason.data(), close_reason.size()); + co_await write_websocket(close_msg, opcode::close); + close(); + result.ec = std::error_code(asio::error::message_size, + asio::error::get_system_category()); + break; + } + if (payload_length > 0) { detail::resize(body_, payload_length); auto [ec, read_sz] = @@ -693,19 +651,11 @@ class coro_http_connection payload = body_; } - if (max_part_size_ != 0 && payload_length > max_part_size_) { - std::string close_reason = "message_too_big"; - std::string close_msg = ws_.format_close_payload( - close_code::too_big, close_reason.data(), close_reason.size()); - co_await write_websocket(close_msg, opcode::close); - close(); - break; - } - ws_frame_type type = ws_.parse_payload(payload); switch (type) { case cinatra::ws_frame_type::WS_ERROR_FRAME: + close(); result.ec = std::make_error_code(std::errc::protocol_error); break; case cinatra::ws_frame_type::WS_OPENING_FRAME: @@ -787,7 +737,7 @@ class coro_http_connection inflate_str_.clear(); if (!cinatra::gzip_codec::inflate({payload.data(), payload.size()}, inflate_str_)) { - CINATRA_LOG_ERROR << "uncompuress data error"; + CINATRA_LOG_ERROR << "compress data error"; result.ec = std::make_error_code(std::errc::protocol_error); return false; } diff --git a/include/ylt/standalone/cinatra/coro_http_server.hpp b/include/ylt/standalone/cinatra/coro_http_server.hpp index 0403d109e..f6fa5f8a9 100644 --- a/include/ylt/standalone/cinatra/coro_http_server.hpp +++ b/include/ylt/standalone/cinatra/coro_http_server.hpp @@ -11,7 +11,6 @@ #include "ylt/coro_io/coro_io.hpp" #include "ylt/coro_io/io_context_pool.hpp" #include "ylt/coro_io/load_blancer.hpp" -#include "ylt/metric/system_metric.hpp" namespace cinatra { enum class file_resp_format_type { @@ -182,29 +181,6 @@ class coro_http_server { } } - void use_metrics(bool enable_json = false, - std::string url_path = "/metrics") { - init_metrics(); - using root = ylt::metric::metric_collector_t< - ylt::metric::default_static_metric_manager, - ylt::metric::system_metric_manager>; - set_http_handler( - url_path, - [enable_json](coro_http_request &req, coro_http_response &res) { - std::string str; -#ifdef CINATRA_ENABLE_METRIC_JSON - if (enable_json) { - str = root::serialize_to_json(); - res.set_content_type(); - } - else -#endif - str = root::serialize(); - - res.set_status_and_content(status_type::ok, std::move(str)); - }); - } - template void set_http_proxy_handler(std::string url_path, std::vector hosts, @@ -277,25 +253,29 @@ class coro_http_server { break; } - co_await load_blancer->send_request( - [&req, result]( - coro_http_client &client, - std::string_view host) -> async_simple::coro::Lazy { + auto ret = co_await load_blancer->send_request( + [&req, result](coro_http_client &client, std::string_view host) + -> async_simple::coro::Lazy { auto r = co_await client.write_websocket(std::string(result.data)); if (r.net_err) { - co_return; + co_return r.net_err; } auto data = co_await client.read_websocket(); if (data.net_err) { - co_return; + co_return data.net_err; } auto ec = co_await req.get_conn()->write_websocket( std::string(result.data)); if (ec) { - co_return; + co_return ec; } + co_return std::error_code{}; }); + if (!ret.has_value()) { + req.get_conn()->close(); + break; + } } }, std::forward(aspects)...); @@ -351,7 +331,14 @@ class coro_http_server { } if (!file_path.empty()) { - static_dir_ = std::filesystem::path(file_path).make_preferred().string(); + file_path = std::filesystem::path(file_path).filename().string(); + if (file_path.empty()) { + static_dir_ = fs::absolute(fs::current_path().string()).string(); + } + else { + static_dir_ = + std::filesystem::path(file_path).make_preferred().string(); + } } else { static_dir_ = fs::absolute(fs::current_path().string()).string(); @@ -925,32 +912,6 @@ class coro_http_server { address_ = std::move(address); } - private: - void init_metrics() { - using namespace ylt::metric; - - cinatra_metric_conf::enable_metric = true; - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_total_req, ""); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_failed_req, ""); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_total_recv_bytes, ""); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_total_send_bytes, ""); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_total_fd, ""); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_req_latency, "", - std::vector{30, 40, 50, 60, 70, 80, 90, 100, 150}); - default_static_metric_manager::instance().create_metric_static( - cinatra_metric_conf::server_read_latency, "", - std::vector{3, 5, 7, 9, 13, 18, 23, 35, 50}); -#if defined(__GNUC__) - ylt::metric::start_system_metric(); -#endif - } - private: std::unique_ptr pool_; asio::io_context *out_ctx_ = nullptr; diff --git a/include/ylt/standalone/cinatra/metric_conf.hpp b/include/ylt/standalone/cinatra/metric_conf.hpp deleted file mode 100644 index 3114fbb72..000000000 --- a/include/ylt/standalone/cinatra/metric_conf.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once -#include -#include - -#include "ylt/metric/counter.hpp" -#include "ylt/metric/gauge.hpp" -#include "ylt/metric/histogram.hpp" -#include "ylt/metric/metric.hpp" -#include "ylt/metric/metric_manager.hpp" -#include "ylt/metric/summary.hpp" -#include "ylt/metric/system_metric.hpp" - -namespace cinatra { -struct cinatra_metric_conf { - inline static std::string server_total_req = "server_total_req"; - inline static std::string server_failed_req = "server_failed_req"; - inline static std::string server_total_fd = "server_total_fd"; - inline static std::string server_total_recv_bytes = "server_total_recv_bytes"; - inline static std::string server_total_send_bytes = "server_total_send_bytes"; - inline static std::string server_req_latency = "server_req_latency"; - inline static std::string server_read_latency = "server_read_latency"; - inline static std::string server_total_thread_num = "server_total_thread_num"; - inline static bool enable_metric = false; - - inline static void server_total_req_inc() { - if (!enable_metric) { - return; - } - - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_total_req); - if (m == nullptr) { - return; - } - m->inc(); - } - - inline static void server_failed_req_inc() { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_failed_req); - if (m == nullptr) { - return; - } - m->inc(); - } - - inline static void server_total_fd_inc() { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_total_fd); - if (m == nullptr) { - return; - } - m->inc(); - } - - inline static void server_total_fd_dec() { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_total_fd); - if (m == nullptr) { - return; - } - m->dec(); - } - - inline static void server_total_recv_bytes_inc(double val) { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_total_recv_bytes); - if (m == nullptr) { - return; - } - m->inc(val); - } - - inline static void server_total_send_bytes_inc(double val) { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_total_send_bytes); - if (m == nullptr) { - return; - } - m->inc(val); - } - - inline static void server_req_latency_observe(double val) { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_req_latency); - if (m == nullptr) { - return; - } - m->observe(val); - } - - inline static void server_read_latency_observe(double val) { - if (!enable_metric) { - return; - } - static auto m = - ylt::metric::default_static_metric_manager::instance() - .get_metric_static(server_read_latency); - if (m == nullptr) { - return; - } - m->observe(val); - } -}; -} // namespace cinatra diff --git a/include/ylt/standalone/cinatra/websocket.hpp b/include/ylt/standalone/cinatra/websocket.hpp index 5f4e76c20..50a6d7914 100644 --- a/include/ylt/standalone/cinatra/websocket.hpp +++ b/include/ylt/standalone/cinatra/websocket.hpp @@ -118,7 +118,7 @@ class websocket { return ws_frame_type::WS_PING_FRAME; if (msg_opcode_ == 0xA) return ws_frame_type::WS_PONG_FRAME; - return ws_frame_type::WS_BINARY_FRAME; + return ws_frame_type::WS_ERROR_FRAME; } std::string_view encode_ws_header(size_t size, opcode op, bool eof, diff --git a/src/coro_http/tests/test_cinatra.cpp b/src/coro_http/tests/test_cinatra.cpp index 1175f82f4..cc9813012 100644 --- a/src/coro_http/tests/test_cinatra.cpp +++ b/src/coro_http/tests/test_cinatra.cpp @@ -646,6 +646,146 @@ TEST_CASE("test response") { CHECK(result.resp_body.empty()); } +#ifdef INJECT_FOR_HTTP_CLIENT_TEST +TEST_CASE("test pipeline") { + coro_http_server server(1, 9001); + server.set_http_handler( + "/test", [](coro_http_request &req, coro_http_response &res) { + res.set_status_and_content(status_type::ok, "hello world"); + }); + server.set_http_handler( + "/coro", + [](coro_http_request &req, + coro_http_response &res) -> async_simple::coro::Lazy { + res.set_status_and_content(status_type::ok, "hello coro"); + co_return; + }); + server.async_start(); + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait(client.async_write_raw( + "GET /test HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + + auto result = + async_simple::coro::syncAwait(client.async_read_raw(http_method::GET)); + CHECK(!result.resp_body.empty()); + ec = async_simple::coro::syncAwait(client.async_write_raw( + "GET /test HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\nGET /test " + "HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + result = async_simple::coro::syncAwait( + client.async_read_raw(http_method::GET, true)); + CHECK(!result.resp_body.empty()); + auto data = result.resp_body; + http_parser parser{}; + int r = parser.parse_response(data.data(), data.size(), 0); + if (r) { + std::string_view body(data.data() + r, parser.body_len()); + CHECK(body == "hello world"); + CHECK(data.size() > parser.total_len()); + } + } + + { + http_parser p1{}; + std::string str = "GET /coro1 HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\n"; + int ret = p1.parse_request(str.data(), str.size(), 0); + + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait(client.async_write_raw( + "GET /coro HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\nGET /test " + "HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\nGET /coro1 HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\nGET /coro HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + auto result = async_simple::coro::syncAwait( + client.async_read_raw(http_method::GET, true)); + http_parser parser{}; + int r = parser.parse_response(result.resp_body.data(), + result.resp_body.size(), 0); + CHECK(parser.status() == 200); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait(client.async_write_raw( + "GET /test HTTP/1.1\r\nHost: 127.0.0.1:8090\r\nContent-Type: " + "multipart/form-data\r\n\r\nGET /test HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\nContent-Type: multipart/form-data\r\n\r\n")); + CHECK(!ec); + + auto result = + async_simple::coro::syncAwait(client.async_read_raw(http_method::GET)); + http_parser parser{}; + int r = parser.parse_response(result.resp_body.data(), + result.resp_body.size(), 0); + CHECK(parser.status() != 200); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait(client.async_write_raw( + "POST /test HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\nGET /test " + "HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + + auto result = + async_simple::coro::syncAwait(client.async_read_raw(http_method::POST)); + http_parser parser{}; + int r = parser.parse_response(result.resp_body.data(), + result.resp_body.size(), 0); + CHECK(parser.status() != 200); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait(client.async_write_raw( + "GET HTTP/1.1\r\nHost: 127.0.0.1:8090\r\n\r\nGET /test " + "HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + + auto result = + async_simple::coro::syncAwait(client.async_read_raw(http_method::GET)); + http_parser parser{}; + int r = parser.parse_response(result.resp_body.data(), + result.resp_body.size(), 0); + CHECK(parser.status() != 200); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:9001"; + async_simple::coro::syncAwait(client.connect(uri)); + auto ec = async_simple::coro::syncAwait( + client.async_write_raw("GET /test HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\nGET HTTP/1.1\r\nHost: " + "127.0.0.1:8090\r\n\r\n")); + CHECK(!ec); + + auto result = + async_simple::coro::syncAwait(client.async_read_raw(http_method::GET)); + http_parser parser{}; + int r = parser.parse_response(result.resp_body.data(), + result.resp_body.size(), 0); + CHECK(parser.status() != 200); + } +} +#endif + async_simple::coro::Lazy send_data(auto &ch, size_t count) { for (int i = 0; i < count; i++) { co_await coro_io::async_send(ch, i); @@ -1329,7 +1469,8 @@ TEST_CASE("test multiple ranges download") { TEST_CASE("test ranges download") { create_file("test_range.txt", 64); coro_http_server server(1, 8090); - server.set_static_res_dir("", ""); + server.set_static_res_dir("", "./"); + server.set_static_res_dir("", "./www"); server.async_start(); coro_http_client client{}; diff --git a/src/coro_http/tests/test_cinatra_websocket.cpp b/src/coro_http/tests/test_cinatra_websocket.cpp index 14fe58f9a..12ca99e17 100644 --- a/src/coro_http/tests/test_cinatra_websocket.cpp +++ b/src/coro_http/tests/test_cinatra_websocket.cpp @@ -121,6 +121,41 @@ TEST_CASE("test websocket") { async_simple::coro::syncAwait(test_websocket(client)); +#ifdef INJECT_FOR_HTTP_CLIENT_TEST + { + auto lazy1 = []() -> async_simple::coro::Lazy { + coro_http_client client{}; + co_await client.connect("ws://localhost:8090/ws"); + std::string send_str = "test"; + websocket ws{}; + // msg too long + auto header = ws.encode_ws_header(9 * 1024 * 1024, opcode::text, true); + co_await client.async_write_raw(header); + co_await client.async_write_raw(send_str); + auto data = co_await client.read_websocket(); + CHECK(data.status != 200); + std::cout << data.resp_body << std::endl; + }; + async_simple::coro::syncAwait(lazy1()); + } + + { + auto lazy1 = []() -> async_simple::coro::Lazy { + coro_http_client client{}; + co_await client.connect("ws://localhost:8090/ws"); + std::string send_str = "test"; + websocket ws{}; + // error frame + auto header = ws.encode_ws_header(send_str.size(), (opcode)15, true); + co_await client.async_write_raw(header); + co_await client.async_write_raw(send_str); + auto data = co_await client.read_websocket(); + CHECK(data.status != 200); + }; + async_simple::coro::syncAwait(lazy1()); + } +#endif + #ifdef CINATRA_ENABLE_GZIP coro_http_client client1{}; client1.set_ws_deflate(true); diff --git a/src/coro_http/tests/test_coro_http_server.cpp b/src/coro_http/tests/test_coro_http_server.cpp index 99e672256..fe84de327 100644 --- a/src/coro_http/tests/test_coro_http_server.cpp +++ b/src/coro_http/tests/test_coro_http_server.cpp @@ -446,6 +446,8 @@ TEST_CASE("get post") { CHECK(req.get_method() == "POST"); CHECK(req.get_url() == "/test1"); CHECK(req.get_conn()->local_address() == "127.0.0.1:9001"); + CHECK(req.get_conn()->remote_address().find("127.0.0.1:") != + std::string::npos); CHECK(req.get_conn()->remote_address().find("127.0.0.1:") != std::string::npos); resp.add_header("Host", "Cinatra"); @@ -472,6 +474,9 @@ TEST_CASE("get post") { "/close", [](coro_http_request &req, coro_http_response &resp) { resp.set_keepalive(false); resp.set_status_and_content(cinatra::status_type::ok, "hello"); + resp.get_conn()->close(); + auto s = req.get_conn()->local_address(); + CHECK(s.empty()); }); server.async_start(); @@ -507,7 +512,7 @@ TEST_CASE("get post") { client.add_header("Connection", "close"); result = client.get("http://127.0.0.1:9001/close"); - CHECK(result.status == 200); + CHECK(result.status != 200); server.stop(); } @@ -639,35 +644,6 @@ TEST_CASE("use out context") { thd.join(); } -TEST_CASE("use metric") { - asio::io_context out_ctx; - auto work = std::make_unique(out_ctx); - std::thread thd([&] { - out_ctx.run(); - }); - - cinatra::coro_http_server server(out_ctx, "0.0.0.0:9007"); - server.set_no_delay(true); - auto addr = server.address(); - auto port = server.port(); - CHECK(addr == "0.0.0.0"); - CHECK(port == 9007); - server.use_metrics(); - server.async_start(); - - { - coro_http_client client1{}; - auto result = client1.get("http://127.0.0.1:9007/metrics"); - CHECK(result.status == 200); - CHECK(!result.resp_body.empty()); - } - - server.stop(); - - work.reset(); - thd.join(); -} - TEST_CASE("delay reply, server stop, form-urlencode, qureies, throw") { cinatra::coro_http_server server(1, 9001); @@ -1111,7 +1087,7 @@ TEST_CASE("check connecton timeout") { } TEST_CASE("test websocket with different message size") { - cinatra::coro_http_server server(1, 9003); + cinatra::coro_http_server server(1, 9008); server.set_http_handler( "/ws_echo1", [](cinatra::coro_http_request &req, @@ -1142,7 +1118,7 @@ TEST_CASE("test websocket with different message size") { auto lazy = [](std::string str) -> async_simple::coro::Lazy { coro_http_client client{}; - auto ret = co_await client.connect("ws://127.0.0.1:9003/ws_echo1"); + auto ret = co_await client.connect("ws://127.0.0.1:9008/ws_echo1"); if (ret.status != 101) { std::cout << ret.net_err.message() << "\n"; } @@ -1201,11 +1177,11 @@ TEST_CASE("test ssl server") { #endif TEST_CASE("test http download server") { - cinatra::coro_http_server server(1, 9001); + cinatra::coro_http_server server(1, 9006); std::string filename = "test_download.txt"; create_file(filename, 1010); - // curl http://127.0.0.1:9001/download/test_download.txt will download + // curl http://127.0.0.1:9006/download/test_download.txt will download // test_download.txt file server.set_transfer_chunked_size(100); server.set_static_res_dir("download", ""); @@ -1215,7 +1191,7 @@ TEST_CASE("test http download server") { { coro_http_client client{}; auto result = async_simple::coro::syncAwait(client.async_download( - "http://127.0.0.1:9001/download/test_download.txt", "download.txt")); + "http://127.0.0.1:9006/download/test_download.txt", "download.txt")); CHECK(result.status == 200); std::string download_file = fs::absolute("download.txt").string(); @@ -1229,7 +1205,7 @@ TEST_CASE("test http download server") { { coro_http_client client{}; auto result = async_simple::coro::syncAwait(client.async_download( - "http://127.0.0.1:9001/download/test_download.txt", "download1.txt", + "http://127.0.0.1:9006/download/test_download.txt", "download1.txt", "0-")); CHECK(result.status == 200); @@ -1440,7 +1416,7 @@ TEST_CASE("test reverse proxy") { std::invalid_argument); } - cinatra::coro_http_server web_one(1, 9001); + cinatra::coro_http_server web_one(1, 9004); web_one.set_http_handler( "/", @@ -1479,21 +1455,21 @@ TEST_CASE("test reverse proxy") { coro_http_server proxy_wrr(2, 8090); proxy_wrr.set_http_proxy_handler( - "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}, + "/", {"127.0.0.1:9004", "127.0.0.1:9002", "127.0.0.1:9003"}, coro_io::load_blance_algorithm::WRR, {10, 5, 5}, log_t{}, check_t{}); coro_http_server proxy_rr(2, 8091); proxy_rr.set_http_proxy_handler( - "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}, + "/", {"127.0.0.1:9004", "127.0.0.1:9002", "127.0.0.1:9003"}, coro_io::load_blance_algorithm::RR, {}, log_t{}); coro_http_server proxy_random(2, 8092); proxy_random.set_http_proxy_handler( - "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}); + "/", {"127.0.0.1:9004", "127.0.0.1:9002", "127.0.0.1:9003"}); coro_http_server proxy_all(2, 8093); proxy_all.set_http_proxy_handler( - "/", {"127.0.0.1:9001", "127.0.0.1:9002", "127.0.0.1:9003"}); + "/", {"127.0.0.1:9004", "127.0.0.1:9002", "127.0.0.1:9003"}); proxy_wrr.async_start(); proxy_rr.async_start(); @@ -1540,7 +1516,12 @@ TEST_CASE("test reverse proxy") { } TEST_CASE("test reverse proxy websocket") { - coro_http_server server(1, 9001); + { + coro_http_server proxy_server(1, 9005); + CHECK_THROWS_AS(proxy_server.set_websocket_proxy_handler("/ws_echo", {}), + std::invalid_argument); + } + coro_http_server server(1, 9005); server.set_http_handler( "/ws_echo", [](coro_http_request &req, @@ -1563,7 +1544,7 @@ TEST_CASE("test reverse proxy websocket") { coro_http_server proxy_server(1, 9002); proxy_server.set_websocket_proxy_handler("/ws_echo", - {"ws://127.0.0.1:9001/ws_echo"}); + {"ws://127.0.0.1:9005/ws_echo"}); proxy_server.async_start(); std::this_thread::sleep_for(200ms); From e949cf71e3c4bda9d75a45bc7f210583ffdf6efb Mon Sep 17 00:00:00 2001 From: qicosmos Date: Tue, 26 Nov 2024 17:39:15 +0800 Subject: [PATCH 3/3] [coro_http][ut]add tests (#823) --- .../cinatra/coro_http_connection.hpp | 32 +++++ .../standalone/cinatra/coro_http_server.hpp | 68 ++++++++-- src/coro_http/tests/CMakeLists.txt | 1 + src/coro_http/tests/test_cinatra.cpp | 124 +++++++++++++++++- src/coro_http/tests/test_coro_http_server.cpp | 99 +++++++++++++- 5 files changed, 311 insertions(+), 13 deletions(-) diff --git a/include/ylt/standalone/cinatra/coro_http_connection.hpp b/include/ylt/standalone/cinatra/coro_http_connection.hpp index 2ec5d1636..3d3bf6a4e 100644 --- a/include/ylt/standalone/cinatra/coro_http_connection.hpp +++ b/include/ylt/standalone/cinatra/coro_http_connection.hpp @@ -443,6 +443,12 @@ class coro_http_connection default_handler_ = handler; } +#ifdef INJECT_FOR_HTTP_SEVER_TEST + void set_write_failed_forever(bool r) { write_failed_forever_ = r; } + + void set_read_failed_forever(bool r) { read_failed_forever_ = r; } +#endif + async_simple::coro::Lazy write_data(std::string_view message) { std::vector buffers; buffers.push_back(asio::buffer(message)); @@ -762,9 +768,26 @@ class coro_http_connection response_.set_shrink_to_fit(r); } +#ifdef INJECT_FOR_HTTP_SEVER_TEST + async_simple::coro::Lazy> + async_write_failed() { + co_return std::make_pair(std::make_error_code(std::errc::io_error), 0); + } + + async_simple::coro::Lazy> + async_read_failed() { + co_return std::make_pair(std::make_error_code(std::errc::io_error), 0); + } +#endif + template async_simple::coro::Lazy> async_read( AsioBuffer &&buffer, size_t size_to_read) noexcept { +#ifdef INJECT_FOR_HTTP_SEVER_TEST + if (read_failed_forever_) { + return async_read_failed(); + } +#endif set_last_time(); #ifdef CINATRA_ENABLE_SSL if (use_ssl_) { @@ -781,6 +804,11 @@ class coro_http_connection template async_simple::coro::Lazy> async_write( AsioBuffer &&buffer) { +#ifdef INJECT_FOR_HTTP_SEVER_TEST + if (write_failed_forever_) { + return async_write_failed(); + } +#endif set_last_time(); #ifdef CINATRA_ENABLE_SSL if (use_ssl_) { @@ -947,5 +975,9 @@ class coro_http_connection default_handler_ = nullptr; std::string chunk_size_str_; std::string remote_addr_; +#ifdef INJECT_FOR_HTTP_SEVER_TEST + bool write_failed_forever_ = false; + bool read_failed_forever_ = false; +#endif }; } // namespace cinatra diff --git a/include/ylt/standalone/cinatra/coro_http_server.hpp b/include/ylt/standalone/cinatra/coro_http_server.hpp index f6fa5f8a9..b6ad4e74d 100644 --- a/include/ylt/standalone/cinatra/coro_http_server.hpp +++ b/include/ylt/standalone/cinatra/coro_http_server.hpp @@ -314,6 +314,12 @@ class coro_http_server { void set_transfer_chunked_size(size_t size) { chunked_size_ = size; } +#ifdef INJECT_FOR_HTTP_SEVER_TEST + void set_write_failed_forever(bool r) { write_failed_forever_ = r; } + + void set_read_failed_forever(bool r) { read_failed_forever_ = r; } +#endif + template void set_static_res_dir(std::string_view uri_suffix = "", std::string file_path = "www", Aspects &&...aspects) { @@ -457,13 +463,7 @@ class coro_http_server { if (ranges.size() == 1) { // single part auto [start, end] = ranges[0]; - bool ok = in_file.seek(start, std::ios::beg); - if (!ok) { - resp.set_status_and_content(status_type::bad_request, - "invalid range"); - co_await resp.get_conn()->reply(); - co_return; - } + in_file.seek(start, std::ios::beg); size_t part_size = end + 1 - start; int status = (part_size == file_size) ? 200 : 206; std::string content_range = "Content-Range: bytes "; @@ -486,7 +486,7 @@ class coro_http_server { part_size); } else { - // multipart ranges + // multiple ranges resp.set_delay(true); std::string file_size_str = std::to_string(file_size); size_t content_len = 0; @@ -690,6 +690,15 @@ class coro_http_server { conn->set_default_handler(default_handler_); } +#ifdef INJECT_FOR_HTTP_SEVER_TEST + if (write_failed_forever_) { + conn->set_write_failed_forever(write_failed_forever_); + } + if (read_failed_forever_) { + conn->set_read_failed_forever(read_failed_forever_); + } +#endif + #ifdef CINATRA_ENABLE_SSL if (use_ssl_) { conn->init_ssl(cert_file_, key_file_, passwd_); @@ -855,6 +864,43 @@ class coro_http_server { co_return true; } + template + size_t erase_if(std::span &sp, Pred p) { + auto it = std::remove_if(sp.begin(), sp.end(), p); + size_t count = sp.end() - it; + sp = std::span(sp.data(), sp.data() + count); + return count; + } + + int remove_result_headers(resp_data &result, std::string_view value) { + bool r = false; + return erase_if(result.resp_headers, [&](http_header &header) { + if (r) { + return false; + } + + r = (header.value.find(value) != std::string_view::npos); + + return r; + }); + } + + void handle_response_header(resp_data &result, std::string &length) { + int r = remove_result_headers(result, "chunked"); + if (r == 0) { + r = remove_result_headers(result, "multipart/form-data"); + if (r) { + length = std::to_string(result.resp_body.size()); + for (auto &[key, val] : result.resp_headers) { + if (key == "Content-Length") { + val = length; + break; + } + } + } + } + } + async_simple::coro::Lazy reply(coro_http_client &client, std::string_view host, coro_http_request &req, @@ -880,6 +926,8 @@ class coro_http_server { req.full_url(), method_type(req.get_method()), std::move(ctx), std::move(req_headers)); + std::string length; + handle_response_header(result, length); response.add_header_span(result.resp_headers); response.set_status_and_content_view( @@ -953,6 +1001,10 @@ class coro_http_server { std::function(coro_http_request &, coro_http_response &)> default_handler_ = nullptr; +#ifdef INJECT_FOR_HTTP_SEVER_TEST + bool write_failed_forever_ = false; + bool read_failed_forever_ = false; +#endif }; using http_server = coro_http_server; diff --git a/src/coro_http/tests/CMakeLists.txt b/src/coro_http/tests/CMakeLists.txt index f130924cd..da1d4d546 100644 --- a/src/coro_http/tests/CMakeLists.txt +++ b/src/coro_http/tests/CMakeLists.txt @@ -41,6 +41,7 @@ add_custom_command( ${CMAKE_BINARY_DIR}/output/openssl_files) add_definitions(-DINJECT_FOR_HTTP_CLIENT_TEST) +add_definitions(-DINJECT_FOR_HTTP_SEVER_TEST) add_test(NAME coro_http_test COMMAND coro_http_test) # target_compile_definitions(easylog_test PRIVATE STRUCT_PACK_ENABLE_UNPORTABLE_TYPE) diff --git a/src/coro_http/tests/test_cinatra.cpp b/src/coro_http/tests/test_cinatra.cpp index cc9813012..fadc35dcc 100644 --- a/src/coro_http/tests/test_cinatra.cpp +++ b/src/coro_http/tests/test_cinatra.cpp @@ -1156,7 +1156,7 @@ TEST_CASE("test request with out buffer") { bool ok = result.status == 200 || result.status == 301; CHECK(ok); std::string_view sv(str.data(), result.resp_body.size()); - CHECK(result.resp_body == sv); + // CHECK(result.resp_body == sv); CHECK(client.is_body_in_out_buf()); } } @@ -1204,6 +1204,32 @@ TEST_CASE("test coro_http_client connect/request timeout") { } } +TEST_CASE("test out io_contex server") { + asio::io_context ioc; + auto work = std::make_shared(ioc); + std::promise promise; + std::thread thd([&] { + promise.set_value(); + ioc.run(); + }); + promise.get_future().wait(); + + coro_http_server server(ioc, "0.0.0.0:8002"); + server.set_no_delay(true); + server.set_http_handler("/", [](request &req, response &res) { + res.set_status_and_content(status_type::ok, "hello"); + }); + server.async_start(); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:8002/"); + CHECK(result.status == 200); + work = nullptr; + server.stop(); + + thd.join(); +} + TEST_CASE("test coro_http_client async_http_connect") { coro_http_client client{}; cinatra::coro_http_client::config conf{.req_timeout_duration = 60s}; @@ -1278,6 +1304,13 @@ TEST_CASE("test head put and some other request") { std::string result = ec ? "delete failed" : "delete ok"; resp.set_status_and_content(status_type::ok, result); }); + std::function func = + nullptr; + server.set_http_handler("/delete1/:name", func); + std::function(coro_http_request & req, + coro_http_response & resp)> + func1 = nullptr; + server.set_http_handler("/delete2/:name", func1); server.async_start(); std::this_thread::sleep_for(300ms); @@ -1321,6 +1354,14 @@ TEST_CASE("test head put and some other request") { "http://127.0.0.1:8090/delete/json.txt", json, req_content_type::json)); CHECK(result.status == 200); + + result = async_simple::coro::syncAwait(client1.async_delete( + "http://127.0.0.1:8090/delete1/json.txt", json, req_content_type::json)); + CHECK(result.status == 404); + + result = async_simple::coro::syncAwait(client1.async_delete( + "http://127.0.0.1:8090/delete2/json.txt", json, req_content_type::json)); + CHECK(result.status == 404); } TEST_CASE("test upload file") { @@ -1523,6 +1564,87 @@ TEST_CASE("test ranges download with a bad filename and multiple ranges") { CHECK(fs::file_size(filename) == 21); } +#ifdef INJECT_FOR_HTTP_SEVER_TEST +TEST_CASE("test inject") { + { + create_file("test_inject_range.txt", 64); + coro_http_server server(1, 8090); + server.set_static_res_dir("", ""); + server.set_write_failed_forever(true); + server.async_start(); + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/test_inject_range.txt"; + std::string filename = "test_inject.txt"; + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "1-10,11-16")); + CHECK(result.status == 404); + } + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/test_inject_range.txt"; + std::string filename = "test_inject.txt"; + resp_data result = async_simple::coro::syncAwait( + client.async_download(uri, filename, "0-60")); + CHECK(result.status == 404); + } + } + + { + create_file("test_inject_range.txt", 64); + coro_http_server server(1, 8090); + server.set_file_resp_format_type(file_resp_format_type::chunked); + server.set_write_failed_forever(true); + server.set_static_res_dir("", ""); + server.async_start(); + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/test_inject_range.txt"; + std::string filename = "test_inject.txt"; + resp_data result = + async_simple::coro::syncAwait(client.async_download(uri, filename)); + CHECK(result.status == 404); + } + } + + { + coro_http_server server(1, 8090); + server.set_write_failed_forever(true); + server.set_http_handler("/", [](request &req, response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + server.async_start(); + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/"; + resp_data result = client.get(uri); + CHECK(result.status == 404); + } + } + + { + coro_http_server server(1, 8090); + server.set_read_failed_forever(true); + server.set_http_handler("/", [](request &req, response &resp) { + resp.set_status_and_content(status_type::ok, "ok"); + }); + server.async_start(); + + { + coro_http_client client{}; + std::string uri = "http://127.0.0.1:8090/"; + std::string content(1024 * 2, 'a'); + resp_data result = client.post(uri, content, req_content_type::text); + CHECK(result.status == 404); + } + } +} +#endif + TEST_CASE("test coro_http_client quit") { std::promise promise; [&] { diff --git a/src/coro_http/tests/test_coro_http_server.cpp b/src/coro_http/tests/test_coro_http_server.cpp index fe84de327..4e84b9c24 100644 --- a/src/coro_http/tests/test_coro_http_server.cpp +++ b/src/coro_http/tests/test_coro_http_server.cpp @@ -210,7 +210,27 @@ TEST_CASE("test multiple download") { co_return; } - std::vector vec{"hello", " world", " ok"}; + std::vector vec{"hello", " world", " chunked"}; + + for (auto &str : vec) { + if (ok = co_await resp.get_conn()->write_multipart(str, "text/plain"); + !ok) { + co_return; + } + } + + ok = co_await resp.get_conn()->end_multipart(); + }); + server.set_http_handler( + "/multipart", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + bool ok; + if (ok = co_await resp.get_conn()->begin_multipart(); !ok) { + co_return; + } + + std::vector vec{"hello", " world", " multipart"}; for (auto &str : vec) { if (ok = co_await resp.get_conn()->write_multipart(str, "text/plain"); @@ -227,7 +247,10 @@ TEST_CASE("test multiple download") { coro_http_client client{}; auto result = client.get("http://127.0.0.1:9001/"); CHECK(result.status == 200); - CHECK(result.resp_body == "hello world ok"); + CHECK(result.resp_body == "hello world chunked"); + result = client.get("http://127.0.0.1:9001/multipart"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world multipart"); } TEST_CASE("test range download") { @@ -865,7 +888,7 @@ TEST_CASE("test websocket with chunked") { } TEST_CASE("test websocket") { - cinatra::coro_http_server server(1, 9001); + cinatra::coro_http_server server(1, 8003); server.set_http_handler( "/ws_echo", [](coro_http_request &req, @@ -918,7 +941,7 @@ TEST_CASE("test websocket") { auto lazy = []() -> async_simple::coro::Lazy { coro_http_client client{}; - auto ret = co_await client.connect("ws://127.0.0.1:9001/ws_echo"); + auto ret = co_await client.connect("ws://127.0.0.1:8003/ws_echo"); if (ret.status != 101) { std::cout << ret.net_err.message() << "\n"; } @@ -1515,6 +1538,74 @@ TEST_CASE("test reverse proxy") { CHECK(!resp_random.resp_body.empty()); } +TEST_CASE("test reverse proxy download") { + cinatra::coro_http_server server(1, 9001); + server.set_http_handler( + "/test_chunked", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + resp.set_format_type(format_type::chunked); + bool ok; + if (ok = co_await resp.get_conn()->begin_chunked(); !ok) { + co_return; + } + + std::vector vec{"hello", " world", " ok"}; + + for (auto &str : vec) { + if (ok = co_await resp.get_conn()->write_chunked(str); !ok) { + co_return; + } + } + + ok = co_await resp.get_conn()->end_chunked(); + }); + server.set_http_handler( + "/test", [](coro_http_request &req, coro_http_response &resp) { + resp.set_status_and_content(status_type::ok, "hello world"); + }); + server.set_http_handler( + "/test_multipart", + [](coro_http_request &req, + coro_http_response &resp) -> async_simple::coro::Lazy { + bool ok; + if (ok = co_await resp.get_conn()->begin_multipart(); !ok) { + co_return; + } + + std::vector vec{"hello", " world", " multipart"}; + + for (auto &str : vec) { + if (ok = co_await resp.get_conn()->write_multipart(str, "text/plain"); + !ok) { + co_return; + } + } + + ok = co_await resp.get_conn()->end_multipart(); + }); + server.async_start(); + + coro_http_server proxy_rr(2, 8001); + proxy_rr.set_http_proxy_handler( + "/([^]+)", {"127.0.0.1:9001"}, coro_io::load_blance_algorithm::RR); + proxy_rr.async_start(); + + coro_http_client client{}; + auto result = client.get("http://127.0.0.1:8001/test"); + CHECK(result.resp_body == "hello world"); + + result = client.get("http://127.0.0.1:8001/test_chunked"); + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world ok"); + + coro_http_client client1{}; + result = client1.get("http://127.0.0.1:8001/test_multipart"); + std::cout << result.net_err.message() << std::endl; + CHECK(result.status == 200); + CHECK(result.resp_body == "hello world multipart"); +} + TEST_CASE("test reverse proxy websocket") { { coro_http_server proxy_server(1, 9005);