Chromium 网络栈之UploadDataStream


发布于 2021-11-22


UploadDataStreamURLRequest 请求时上传的数据。Chromium 网络栈对数据的上传支持接口还比较底层原始,比如对 Http 的 multipart/form-data 上传数据还不支持,需要自己的去构造。

UploadDataStream

传输方式

UploadDataStream 有两个子类:

  • ChunkedUploadDataStream,支持 chunked 分块方式传输数据,适合不知道数据大小的场景
  • ElementsUploadDataStream,已知数据大小方式传输数据

数据以一系列分块的 chunked 形式进行发送。 Content-Length 首部在这种情况下不被发送。在每一个分块的开头需要添加当前分块的长度,以十六进制的形式表示,后面紧跟着 '\r\n' ,之后是分块本身,后面也是'\r\n' 。终止块是一个常规的分块,不同之处在于其长度为0。终止块后面是一个挂载(trailer),由一系列(或者为空)的实体消息首部构成。

ElementsUploadDataStream

ElementsUploadDataStream 包含了多个 UploadElementReaderUploadElementReader 有两类:

  • UploadBytesElementReader,以字节的模式读取存在于内存的数据
  • UploadFileElementReader,以文件的模式读取数据

注意UploadBytesElementReader 没有管理内存缓冲区数据的生命周期,如果缓存区的内存失效,读取会出现问题。可以使用 UploadOwnedBytesElementReader 管理内存缓冲区数据的生命周期。

HttpMultipartUploadDataStreamBuilder

实现一个 HttpMultipartUploadDataStreamBuilder ,用来构造支持 multipart/form-dataUploadDataStream

http_multipart_upload_data_stream_builder.h 文件内容:

#ifndef HTTP_MULTIPART_UPLOAD_STREAM_BUILDER_H_
#define HTTP_MULTIPART_UPLOAD_STREAM_BUILDER_H_

#include <memory>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/task_runner.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/upload_element_reader.h"

class HttpMultipartUploadDataStreamBuilder {
 public:
  HttpMultipartUploadDataStreamBuilder();
  ~HttpMultipartUploadDataStreamBuilder();

  void AddReader(std::unique_ptr<net::UploadElementReader> reader);
  void AddFormDataString(const std::string& name, const std::string& value);
  bool AddFormDataFile(const std::string& name,
                       const std::string& content_type,
                       const std::string& filename,
                       const base::FilePath& file_path,
                       base::TaskRunner* task_runner);

  std::unique_ptr<net::ElementsUploadDataStream> Build();
  std::string GetBoundary();
  std::string GetContentType();

 private:
  std::string boundary_;
  std::vector<std::unique_ptr<net::UploadElementReader>> readers_;
  DISALLOW_COPY_AND_ASSIGN(HttpMultipartUploadDataStreamBuilder);
};

#endif  // HTTP_MULTIPART_UPLOAD_STREAM_BUILDER_H_

-------------------------------------------------------------------------------

http_multipart_upload_data_stream_builder.cc  文件内容:

#include "http_multipart_upload_data_stream_builder.h"

#include <string>

#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_file_element_reader.h"

HttpMultipartUploadDataStreamBuilder::HttpMultipartUploadDataStreamBuilder() {
  boundary_ = "----WebKitFormBoundary";
  for (size_t i = 0; i < 10; i++) {
    char c = base::RandInt(97, 122);
    boundary_.push_back(c);
  }
}

HttpMultipartUploadDataStreamBuilder::~HttpMultipartUploadDataStreamBuilder() {}

void HttpMultipartUploadDataStreamBuilder::AddReader(
    std::unique_ptr<net::UploadElementReader> reader) {
  readers_.emplace_back(std::move(reader));
}

void HttpMultipartUploadDataStreamBuilder::AddFormDataString(
    const std::string& name,
    const std::string& value) {
  std::unique_ptr<net::UploadOwnedBytesElementReader> boundary_reader(
      net::UploadOwnedBytesElementReader::CreateWithString(
          std::string("--") + boundary_ + std::string("\r\n")));
  readers_.emplace_back(std::move(boundary_reader));

  std::string content_disposition = base::StringPrintf(
      "Content-Disposition: form-data; name=\"%s\"\r\n\r\n", name.c_str());
  std::unique_ptr<net::UploadOwnedBytesElementReader>
      content_disposition_reader(
          net::UploadOwnedBytesElementReader::CreateWithString(
              content_disposition));
  readers_.emplace_back(std::move(content_disposition_reader));

  std::unique_ptr<net::UploadOwnedBytesElementReader> value_reader(
      net::UploadOwnedBytesElementReader::CreateWithString(value));
  readers_.emplace_back(std::move(value_reader));

  readers_.emplace_back(
      std::move(net::UploadOwnedBytesElementReader::CreateWithString("\r\n")));
}

bool HttpMultipartUploadDataStreamBuilder::AddFormDataFile(
    const std::string& name,
    const std::string& content_type,
    const std::string& filename,
    const base::FilePath& file_path,
    base::TaskRunner* task_runner) {
  std::unique_ptr<net::UploadOwnedBytesElementReader> boundary_reader(
      net::UploadOwnedBytesElementReader::CreateWithString(
          std::string("--") + boundary_ + std::string("\r\n")));
  readers_.emplace_back(std::move(boundary_reader));

  std::string content_disposition = base::StringPrintf(
      "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n",
      name.c_str(), filename.c_str());
  std::unique_ptr<net::UploadOwnedBytesElementReader>
      content_disposition_reader(
          net::UploadOwnedBytesElementReader::CreateWithString(
              content_disposition));
  readers_.emplace_back(std::move(content_disposition_reader));

  std::string content_type_string =
      base::StringPrintf("Content-Type: %s\r\n\r\n", content_type.c_str());
  std::unique_ptr<net::UploadOwnedBytesElementReader> content_type_reader(
      net::UploadOwnedBytesElementReader::CreateWithString(
          content_type_string));
  readers_.emplace_back(std::move(content_type_reader));

  base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_ASYNC |
                                 base::File::FLAG_READ);
  std::unique_ptr<net::UploadFileElementReader> file_reader =
      std::make_unique<net::UploadFileElementReader>(
          task_runner, std::move(file), file_path, 0, file.GetLength(),
          base::Time());
  readers_.emplace_back(std::move(file_reader));

  readers_.emplace_back(
      std::move(net::UploadOwnedBytesElementReader::CreateWithString("\r\n")));

  return true;
}

std::unique_ptr<net::ElementsUploadDataStream>
HttpMultipartUploadDataStreamBuilder::Build() {
  std::string final_boundary = std::string("--") + boundary_ + std::string("--\r\n");
  readers_.emplace_back(std::move(
      net::UploadOwnedBytesElementReader::CreateWithString(final_boundary)));

  return std::make_unique<net::ElementsUploadDataStream>(std::move(readers_),
                                                         0);
}

std::string HttpMultipartUploadDataStreamBuilder::GetBoundary() {
  return boundary_;
}

std::string HttpMultipartUploadDataStreamBuilder::GetContentType() {
  return base::StringPrintf("Content-Type: multipart/form-data; boundary=%s",
                            boundary_.c_str());
}

------------------------------------------------------------------------------

  HttpMultipartUploadDataStreamBuilder stream_builder;
  stream_builder.AddFormDataString("hello", "world");
  stream_builder.AddFormDataString("test", "123");
  stream_builder.AddFormDataFile(
      "testfile", "image/png", "a.png",
      base::FilePath(L"D:\\local_web_server\\raw.png"),
      g_main_task_runner.get());
  std::unique_ptr<net::ElementsUploadDataStream> stream =
      stream_builder.Build();

  url_request->set_upload(std::move(stream));

最后实际的效果如下:

POST http://127.0.0.1:8000/net/test_upload/ HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Content-Length: 341
Content-Type: Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeshtkosozg
User-Agent: 
Accept-Encoding: gzip, deflate

----WebKitFormBoundaryeshtkosozg
Content-Disposition: form-data; name="hello"

world
----WebKitFormBoundaryeshtkosozg
Content-Disposition: form-data; name="test"

123
----WebKitFormBoundaryeshtkosozg
Content-Disposition: form-data; name="testfile"; filename="a.png"
Content-Type: image/png

 PNG
....

参考