如何用C++解析HTTP多部分表单数据并在二进制缓冲区中查找分隔符
在大型二进制缓冲区中定位HTTP Multipart分隔符的C++实现方法
核心思路
HTTP Multipart的分隔符是ASCII文本格式(普通分隔符为--+boundary,结束分隔符为--+boundary+--,均带\r\n后缀),可直接按字节序列匹配。针对大型缓冲区,优先采用KMP算法实现高效匹配,避免暴力匹配的低效问题。
实现步骤
1. 解析并构造分隔符字节序列
先从HTTP请求头的Content-Type字段提取boundary,再转换为二进制格式的分隔符序列:
#include <vector> #include <cstddef> #include <string> #include <stdexcept> #include <algorithm> // 从Content-Type头提取boundary,处理可能的引号包裹 std::string extract_boundary(const std::string& content_type) { size_t boundary_pos = content_type.find("boundary="); if (boundary_pos == std::string::npos) { throw std::invalid_argument("Invalid Content-Type header"); } std::string boundary = content_type.substr(boundary_pos + 9); if (!boundary.empty() && boundary.front() == '"') { auto end_quote = boundary.find_last_of('"'); if (end_quote != std::string::npos) { boundary = boundary.substr(1, end_quote - 1); } } return boundary; } // 构造普通分隔符字节序列:--<boundary>\r\n std::vector<std::byte> build_regular_delimiter(const std::string& boundary) { std::string delimiter_str = "--" + boundary + "\r\n"; std::vector<std::byte> delimiter; delimiter.reserve(delimiter_str.size()); for (char c : delimiter_str) { delimiter.push_back(static_cast<std::byte>(c)); } return delimiter; } // 构造结束分隔符字节序列:--<boundary>--\r\n std::vector<std::byte> build_end_delimiter(const std::string& boundary) { std::string delimiter_str = "--" + boundary + "--\r\n"; std::vector<std::byte> delimiter; delimiter.reserve(delimiter_str.size()); for (char c : delimiter_str) { delimiter.push_back(static_cast<std::byte>(c)); } return delimiter; }
2. 用KMP算法高效查找匹配
KMP算法通过预处理生成部分匹配表(LPS),将匹配时间复杂度优化到O(n+m)(n为缓冲区长度,m为分隔符长度),适合处理大型二进制数据:
// 生成KMP算法的部分匹配表 std::vector<size_t> compute_lps(const std::vector<std::byte>& pattern) { size_t n = pattern.size(); std::vector<size_t> lps(n, 0); size_t len = 0; // 最长前缀后缀的长度 size_t i = 1; while (i < n) { if (pattern[i] == pattern[len]) { len++; lps[i] = len; i++; } else { len = len != 0 ? lps[len - 1] : 0; if (len == 0) { lps[i] = 0; i++; } } } return lps; } // 在二进制缓冲区中查找所有分隔符的起始位置 std::vector<size_t> find_all_delimiters(const std::vector<std::byte>& buffer, const std::vector<std::byte>& delimiter) { std::vector<size_t> matches; if (delimiter.empty() || buffer.size() < delimiter.size()) { return matches; } std::vector<size_t> lps = compute_lps(delimiter); size_t buf_idx = 0; size_t pat_idx = 0; while (buf_idx < buffer.size()) { if (delimiter[pat_idx] == buffer[buf_idx]) { buf_idx++; pat_idx++; } if (pat_idx == delimiter.size()) { matches.push_back(buf_idx - pat_idx); pat_idx = lps[pat_idx - 1]; } else if (buf_idx < buffer.size() && delimiter[pat_idx] != buffer[buf_idx]) { pat_idx = pat_idx != 0 ? lps[pat_idx - 1] : 0; if (pat_idx == 0) { buf_idx++; } } } return matches; }
3. 分割缓冲区为多个数据块
拿到所有分隔符位置后,将缓冲区分割为对应的part,存入vector<vector<std::byte>>:
std::vector<std::vector<std::byte>> split_multipart_buffer( const std::vector<std::byte>& buffer, const std::vector<size_t>& regular_matches, const size_t end_match_pos ) { std::vector<std::vector<std::byte>> parts; size_t start = 0; size_t delimiter_len = regular_matches.empty() ? 0 : build_regular_delimiter("").size(); for (size_t match_pos : regular_matches) { if (match_pos > start) { parts.emplace_back(buffer.begin() + start, buffer.begin() + match_pos); } start = match_pos + delimiter_len; } if (end_match_pos > start) { parts.emplace_back(buffer.begin() + start, buffer.begin() + end_match_pos); } return parts; }
注意事项
- 分段接收场景:若缓冲区从网络分段接收,需保留上一段末尾长度为
分隔符长度-1的字节,与下一段拼接后再查找,避免跨段分隔符被截断。 - 内容空行处理:每个part的头信息与实际内容间存在
\r\n\r\n空行,分割后需跳过该部分才能获取真实文件内容。 - 规范兼容性:严格遵循HTTP规范,分隔符必须包含
\r\n后缀,避免误匹配二进制内容中类似的字节序列。
内容的提问来源于stack exchange,提问作者Troy Hamilton
相关产品推荐
相关产品推荐

