概要
基于 boost.asio
封装的 HTTP 上传库,支持普通 POST 和 multipart/form-data
两种形式的上传。需要使用 C++11 编译。
http_upload
HTTP 上传库流程图
流程解析:
-
调用
httpUpload
函数:根据参数判断是否需要执行普通 HTTP POST 请求,或者使用HttpUpload
类处理文件上传或带有流数据的上传。 -
文件数组为空?:如果没有文件需要上传,则执行普通的 HTTP POST 请求。
-
是否有流数据?:如果有流数据,则使用
HttpUpload
发送流数据;否则,使用HttpUpload
发送文件数据。 -
发送流数据:构建并发送流数据的请求体。
-
计算总长度:计算包含所有文件和流数据的请求体总长度。
-
发送请求头部:发送 HTTP 请求的头部信息,包括 Host、Content-Type 等。
-
发送文件数据:逐个发送文件的数据,每次发送一部分数据,直到完成。
-
发送尾部:发送
multipart/form-data
请求的结束标识。 -
读取响应:从服务器读取并处理响应。
-
判断响应状态码:如果状态码为 200,则认为上传成功,否则认为上传失败。
-
返回结果:根据上传的成功或失败,返回相应的状态码给调用者。
头文件
#pragma once
#include <string>
#include <vector>
#include <tuple>
// 上传单个文件
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::string &filename, const char *fileData, uint64_t fileDataSize);
// 上传单个文件
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::string &filename, const std::string &fileData);
// 同时上传文件名数组和字符串(如 JSON 流)
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::vector<std::string> &filenames, const std::string &stream = "");
// 同时上传文件内容数组和字符串(如 JSON 流)
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::vector<std::tuple<std::string, std::string>> &files, const std::string &stream = "");
// 普通 HTTP POST
int httpPost(const std::string &host, const std::string &port, const std::string &url, const std::string &data);
实现文件
#include "http_upload.h"
#include <iostream>
#include <ostream>
#include <algorithm>
#include <boost/asio.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <fstream>
#include <sstream>
using boost::asio::ip::tcp;
// HTTP 连接类
class HttpConncetion {
public:
HttpConncetion(const std::string &h, const std::string &p, const std::string &s, tcp::socket &skt)
: m_host(h), m_port(p), m_url(s), m_socket(skt) {}
~HttpConncetion() = default;
// 读取响应
int readRespone() {
boost::asio::streambuf response;
boost::asio::read_until(m_socket, response, "\r\n");
std::istream response_stream(&response);
std::string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (!response_stream || http_version.substr(0, 5) != "HTTP/") {
std::cout << "Invalid response\n";
return -2;
}
if (status_code != 200) {
std::cout << "Response returned with status code " << status_code << "\n";
return -3;
}
boost::asio::read_until(m_socket, response, "\r\n\r\n");
boost::system::error_code error;
while (boost::asio::read(m_socket, response, boost::asio::transfer_at_least(1), error)) {
std::cout << &response;
}
if (error != boost::asio::error::eof) {
return -4;
}
return 0;
}
// 发送数据
void sendData(std::string data) {
m_socket.write_some(boost::asio::buffer(data));
}
void sendData(const char *p, uint32_t len) {
m_socket.write_some(boost::asio::buffer(p, len));
}
protected:
tcp::socket &m_socket;
std::string m_prefix = "--";
std::string m_boundary = boost::uuids::to_string(boost::uuids::random_generator()());
std::string m_newline = "\r\n";
std::string m_host;
std::string m_port;
std::string m_url;
};
// 普通 POST 请求类
class HttpPost : public HttpConncetion {
public:
HttpPost(const std::string &host, const std::string &port, const std::string &url,
const std::string &data, tcp::socket &skt)
: HttpConncetion(host, port, url, skt), m_data(data) {}
// 发送数据
int sendData() {
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "POST " << m_url << " HTTP/1.1\r\n";
request_stream << "Host: " << m_host << ":" << m_port << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Content-Length: " << m_data.length() << "\r\n";
request_stream << "Content-Type: application/x-www-form-urlencoded\r\n";
request_stream << "Connection: close\r\n\r\n";
request_stream << m_data;
try {
boost::asio::write(m_socket, request);
} catch (const std::exception &e) {
std::cerr << e.what() << '\n';
return -1;
}
return 0;
}
private:
const std::string &m_data;
};
// 文件上传类
class HttpUpload : public HttpConncetion {
public:
HttpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::vector<std::tuple<std::string, std::string>> &files,
const std::string &streamData, tcp::socket &skt)
: HttpConncetion(host, port, url, skt), m_files(files), m_streamData(streamData) {
m_dataContentDisposition = "Content-Disposition: form-data; name=\"stream_data\"";
for (auto &f : m_files) {
std::string str = "Content-Disposition: form-data; name=\"" + std::get<0>(f) + "\"; filename=\"" + std::get<0>(f) + "\"";
m_fileContentDispositions.push_back(str);
}
}
// 计算总长度
uint64_t calculateTotalLength() {
uint32_t fixLen = m_prefix.length() + m_boundary.length() + m_newline.length() +
m_newline.length() + m_newline.length();
uint32_t tailLen = m_prefix.length() + m_boundary.length() + m_prefix.length() + m_newline.length();
uint64_t requestLength = 0;
if (!m_streamData.empty()) {
requestLength += m_prefix.length() + m_boundary.length() + m_newline.length();
requestLength += m_dataContentDisposition.size();
requestLength += m_newline.length() + m_newline.length();
requestLength += m_streamData.size();
requestLength += m_newline.size();
}
for (size_t i = 0; i < m_files.size(); i++) {
requestLength += m_prefix.length() + m_boundary.length() + m_newline.length();
requestLength += m_fileContentDispositions[i].size();
requestLength += m_newline.length() + m_newline.length();
requestLength += std::get<1>(m_files[i]).size();
requestLength += m_newline.size();
}
requestLength += tailLen;
return requestLength;
}
// 发送头部
void sendHeader() {
uint64_t requestLength = calculateTotalLength();
std::stringstream ss;
ss << "POST " << m_url << " HTTP/1.1" << m_newline;
ss << "Host: " << m_host << ":" << m_port << m_newline;
ss << "Accept: */*" << m_newline;
ss << "Content-Length: " << requestLength << m_newline;
ss << "Content-Type: multipart/form-data; boundary=" << m_boundary << m_newline;
ss << "Connection: close" << m_newline << m_newline;
sendData(ss.str());
}
// 发送流数据
void sendStream() {
if (!m_streamData.empty()) {
std::stringstream ss;
ss << m_prefix << m_boundary << m_newline;
ss << m_dataContentDisposition << m_newline;
ss << m_newline << m_streamData << m_newline;
sendData(ss.str());
}
}
// 发送文件
int sendFiles() {
for (size_t i = 0; i < m_files.size(); i++) {
auto &f = m_files[i];
std::stringstream ss;
ss << m_prefix << m_boundary << m_newline;
ss << m_fileContentDispositions[i] << m_newline << m_newline;
sendData(ss.str());
auto &fileContent = std::get<1>(f);
auto dataSize = fileContent.size();
const char *pData = fileContent.c_str();
uint64_t sent = 0;
while (sent < dataSize) {
try {
uint64_t now = std::min((uint64_t)(dataSize - sent), (uint64_t)(1024 * 1024));
sendData(pData + sent, now);
sent += now;
} catch (const std::exception
&e) {
std::cerr << e.what() << '\n';
return -1;
}
}
sendData(m_newline);
}
return 0;
}
// 发送尾部
void sendTail() {
sendData(m_prefix + m_boundary + m_prefix + m_newline);
}
private:
const std::vector<std::tuple<std::string, std::string>> &m_files;
const std::string &m_streamData;
std::string m_dataContentDisposition;
std::vector<std::string> m_fileContentDispositions;
};
// 上传单个文件
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::string &filename, const char *pData, uint64_t dataSize) {
std::vector<std::tuple<std::string, std::string>> vec;
std::string data(dataSize, 0);
std::copy(pData, pData + dataSize, data.begin());
vec.push_back(std::make_tuple(filename, data));
return httpUpload(host, port, url, vec);
}
// 上传单个文件
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::string &filename, const std::string &data) {
std::vector<std::tuple<std::string, std::string>> vec;
vec.push_back(std::make_tuple(filename, data));
return httpUpload(host, port, url, vec);
}
// 普通 HTTP POST
int httpPost(const std::string &host, const std::string &port, const std::string &url, const std::string &data) {
try {
boost::asio::io_service io_service;
if (io_service.stopped()) io_service.reset();
tcp::resolver resolver(io_service);
tcp::resolver::query query(host, port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
HttpPost http(host, port, url, data, socket);
http.sendData();
return http.readRespone();
} catch (std::exception &e) {
std::cout << e.what() << '\n';
return -5;
}
return 0;
}
// 上传多个文件和流数据
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::vector<std::tuple<std::string, std::string>> &files, const std::string &stream) {
try {
boost::asio::io_service io_service;
if (io_service.stopped()) io_service.reset();
tcp::resolver resolver(io_service);
tcp::resolver::query query(host, port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
HttpUpload http(host, port, url, files, stream, socket);
http.sendHeader();
http.sendStream();
http.sendFiles();
http.sendTail();
return http.readRespone();
} catch (const std::exception &e) {
std::cout << e.what() << '\n';
return -5;
}
return 0;
}
// 上传多个文件
int httpUpload(const std::string &host, const std::string &port, const std::string &url,
const std::vector<std::string> &filenames, const std::string &stream) {
std::vector<std::tuple<std::string, std::string>> vec;
for (const auto &file : filenames) {
std::ifstream is(file, std::ifstream::binary);
std::ostringstream oss;
oss << is.rdbuf();
vec.push_back(std::make_tuple(file, oss.str()));
}
return httpUpload(host, port, url, vec, stream);
}
代码基本解释
-
头文件 http_upload.h:
定义了一系列 HTTP 上传和 POST 的函数接口。 -
实现文件:
包含了具体的实现细节。 -
类 HttpConncetion:
负责建立和管理 HTTP 连接,包含读取响应和发送数据的功能。 -
类 HttpPost:
继承 HttpConncetion,用于处理普通的 POST 请求。 -
类 HttpUpload:
继承 HttpConncetion,用于处理文件上传,支持多文件和流数据的 multipart/form-data 格式。 -
函数 httpUpload 和 httpPost:
-
提供不同形式的上传和 POST 接口,利用上述类实现具体功能。
-
httpUpload:支持单文件上传、多文件上传和流数据上传。
-
httpPost:支持普通的 HTTP POST 请求。
-
优化建议
- 性能优化:考虑使用异步操作(如
async_write
)来提高上传性能,特别是对于大文件的上传。 - 错误处理:增加更详细的错误处理机制,例如处理网络异常、服务器错误等情况。