基于boost的multipart/form-data上传文件

概要

基于 boost.asio 封装的 HTTP 上传库,支持普通 POST 和 multipart/form-data 两种形式的上传。需要使用 C++11 编译。
http_upload

HTTP 上传库流程图

调用httpUpload
文件数组为空?
普通HTTP POST请求
是否有流数据?
使用HttpUpload发送流数据
使用HttpUpload发送文件数据
发送流数据
计算总长度
发送请求头部
发送流数据
发送文件数据
发送尾部
读取响应
响应状态码是否为200?
上传成功
上传失败
返回失败状态

流程解析:

  1. 调用 httpUpload 函数:根据参数判断是否需要执行普通 HTTP POST 请求,或者使用 HttpUpload 类处理文件上传或带有流数据的上传。

  2. 文件数组为空?:如果没有文件需要上传,则执行普通的 HTTP POST 请求。

  3. 是否有流数据?:如果有流数据,则使用 HttpUpload 发送流数据;否则,使用 HttpUpload 发送文件数据。

  4. 发送流数据:构建并发送流数据的请求体。

  5. 计算总长度:计算包含所有文件和流数据的请求体总长度。

  6. 发送请求头部:发送 HTTP 请求的头部信息,包括 Host、Content-Type 等。

  7. 发送文件数据:逐个发送文件的数据,每次发送一部分数据,直到完成。

  8. 发送尾部:发送 multipart/form-data 请求的结束标识。

  9. 读取响应:从服务器读取并处理响应。

  10. 判断响应状态码:如果状态码为 200,则认为上传成功,否则认为上传失败。

  11. 返回结果:根据上传的成功或失败,返回相应的状态码给调用者。

头文件

#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);
}

代码基本解释

  1. 头文件 http_upload.h
    定义了一系列 HTTP 上传和 POST 的函数接口。

  2. 实现文件
    包含了具体的实现细节。

  3. 类 HttpConncetion
    负责建立和管理 HTTP 连接,包含读取响应和发送数据的功能。

  4. 类 HttpPost
    继承 HttpConncetion,用于处理普通的 POST 请求。

  5. 类 HttpUpload
    继承 HttpConncetion,用于处理文件上传,支持多文件和流数据的 multipart/form-data 格式。

  6. 函数 httpUpload 和 httpPost

    • 提供不同形式的上传和 POST 接口,利用上述类实现具体功能。

    • httpUpload:支持单文件上传、多文件上传和流数据上传。

    • httpPost:支持普通的 HTTP POST 请求。

优化建议

  • 性能优化:考虑使用异步操作(如 async_write)来提高上传性能,特别是对于大文件的上传。
  • 错误处理:增加更详细的错误处理机制,例如处理网络异常、服务器错误等情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值