@@ -12035,6 +12035,97 @@ TEST(MultipartFormDataTest, UploadItemsHasContentLength) {
1203512035 EXPECT_EQ(StatusCode::OK_200, res->status);
1203612036}
1203712037
12038+ TEST(MultipartFormDataTest, ContentProviderCoalescesWrites) {
12039+ // Verify that make_multipart_content_provider coalesces many small segments
12040+ // into fewer sink.write() calls to avoid TCP packet fragmentation (#2410).
12041+ constexpr size_t kItemCount = 1000;
12042+
12043+ UploadFormDataItems items;
12044+ items.reserve(kItemCount);
12045+ for (size_t i = 0; i < kItemCount; i++) {
12046+ items.push_back(
12047+ {"field" + std::to_string(i), "value" + std::to_string(i), "", ""});
12048+ }
12049+
12050+ const std::string boundary = "----test-boundary";
12051+ auto content_length = detail::get_multipart_content_length(items, boundary);
12052+ auto provider = detail::make_multipart_content_provider(items, boundary);
12053+
12054+ // Drive the provider the same way write_content_with_progress does
12055+ size_t write_count = 0;
12056+ size_t total_bytes = 0;
12057+
12058+ DataSink sink;
12059+ size_t offset = 0;
12060+ sink.write = [&](const char *d, size_t l) -> bool {
12061+ (void)d;
12062+ write_count++;
12063+ total_bytes += l;
12064+ offset += l;
12065+ return true;
12066+ };
12067+ sink.is_writable = []() -> bool { return true; };
12068+
12069+ while (offset < content_length) {
12070+ ASSERT_TRUE(provider(offset, content_length - offset, sink));
12071+ }
12072+
12073+ EXPECT_EQ(content_length, total_bytes);
12074+
12075+ // The total number of segments is 3 * kItemCount + 1 = 3001.
12076+ // With buffering into 64KB blocks, write_count should be much smaller.
12077+ auto segment_count = 3 * kItemCount + 1;
12078+ EXPECT_LT(write_count, segment_count / 10);
12079+ }
12080+
12081+ TEST(MultipartFormDataTest, ManyItemsEndToEnd) {
12082+ // Integration test: send many UploadFormDataItems and verify the server
12083+ // receives all of them correctly (#2410).
12084+ constexpr size_t kItemCount = 500;
12085+
12086+ auto handled = false;
12087+
12088+ Server svr;
12089+ svr.Post("/upload", [&](const Request &req, Response &res) {
12090+ EXPECT_EQ(kItemCount, req.form.fields.size());
12091+ for (size_t i = 0; i < kItemCount; i++) {
12092+ auto key = "field" + std::to_string(i);
12093+ auto val = "value" + std::to_string(i);
12094+ auto it = req.form.fields.find(key);
12095+ if (it != req.form.fields.end()) {
12096+ EXPECT_EQ(val, it->second.content);
12097+ } else {
12098+ ADD_FAILURE() << "Missing field: " << key;
12099+ }
12100+ }
12101+ res.set_content("ok", "text/plain");
12102+ handled = true;
12103+ });
12104+
12105+ auto port = svr.bind_to_any_port(HOST);
12106+ auto t = thread([&] { svr.listen_after_bind(); });
12107+ auto se = detail::scope_exit([&] {
12108+ svr.stop();
12109+ t.join();
12110+ ASSERT_FALSE(svr.is_running());
12111+ ASSERT_TRUE(handled);
12112+ });
12113+
12114+ svr.wait_until_ready();
12115+
12116+ UploadFormDataItems items;
12117+ items.reserve(kItemCount);
12118+ for (size_t i = 0; i < kItemCount; i++) {
12119+ items.push_back(
12120+ {"field" + std::to_string(i), "value" + std::to_string(i), "", ""});
12121+ }
12122+
12123+ Client cli(HOST, port);
12124+ auto res = cli.Post("/upload", items);
12125+ ASSERT_TRUE(res);
12126+ EXPECT_EQ(StatusCode::OK_200, res->status);
12127+ }
12128+
1203812129TEST(MultipartFormDataTest, MakeFileProvider) {
1203912130 // Verify make_file_provider sends a file's contents correctly.
1204012131 const std::string file_content(4096, 'Z');
0 commit comments