Mongoose library realizes http file upload

1. Preliminary knowledge

1.1 URL codec

It is often used to encode parameters in url links and request bodies in application/x-www-form-urlencoded format.
Since the parameters of url look like key1=value1&key2=value2, if the key or value contains characters such as = &, it will cause The parsing is confusing, so an encoding is needed to replace these symbols that may cause ambiguity.
For example: http://localhost/src/components/global/Checkbox.vue?type=style&index=0
This link is followed by ? The parameter part, that is, type=style&index=0,
these are two key-value pairs, the type value is style, and the index value is 0.
If the current value of type is a=b, then the parameter part is finally assembled into type=a=b&index=0, It can be seen that there is already a bit of ambiguity, but due to the & split, it may be able to be parsed.
If the value of type is assumed to be a&c=d, then after assembly, it will be type=a&c=d&index=0. Obviously, if this string is parsed by the program, Tianwang Laozi When it comes, it will be parsed into three parts: the value of type is a, the value of c is d, and the value of index is 0

1.2 Request Body Encoding Format

In the Http protocol, the request body has various formats, such as:

  • application/x-www-form-urlencoded, a very common format, that is, the same as the parameters in the url, it is a string in the format of key=value, and this string is url-encoded, and url decoding needs to be performed before parsing.
  • multipart/form-data, you can upload multiple key-value pairs/files. The specific format will be elaborated below.
  • application/json, as the name suggests, is in json format
  • application/xml, xml format, that is, tags like HTML
  • text/plain, text
  • application/octet-stream, binary data, and only one file can be uploaded. If you pass a single file (picture, audio and video), it is very happy to use this, it does not need to be parsed, the entire request body is the file, but you need to use other methods to upload the file name and other information of the file.

If there is a request body, Content-Type should be used in the request header to describe the encoding format used

1.3 form-data format

If the request body is in form-data format, in the request header, we should be able to find the value of Content-Type as multipart/form-data and it will be followed by a boundary:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

Here boundary is used for parsing the request body.
Let's first take a look at what the request body looks like in form-data format:

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="myfile"; filename="hello.gif" filename*=UTF-8''hello.gif
Content-Type: image/gif


{
    
    二进制数据}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="mytext"

coolight
------WebKitFormBoundary7MA4YWxkTrZu0gW--

There are two parts in this example request body:

  • A dynamic picture with the file name hello.gif
  • A key-value pair, the key is mytext, and the value is coolight

The boundary value in the Content-Type of the request header just now is used to divide the data in the request body. Add two - before the boundary, namely: –{boundary}, and use it as a separate line as a separate mark.
Note that there are two - after the split mark in the last line, that is --{boundary} –
let’s look at the second part of the example first, that is, the key-value pair mytext=coolight
, its format is as follows, in which the newline (\r\n) mark Out:
Note that newline\r\n is also part of the format

--{
    
    boundary}(\r\n)
Content-Disposition: form-data; name="{key}"(\r\n)
(\r\n)
{
    
    value}(\r\n)
--{
    
    boundary}

Then there is the first part, the format when uploading files:
the difference from the key-value pair format is that there is an extra filename after the name, and there is an optional filename* after it. This is because if the filename contains non-ASCII characters such as Chinese, the filename may be garbled when parsing due to the different encodings of the client and server, so an additional filename* may be transmitted, specifying the encoding format used, such as UTF-8, and Note that its value is the encoding method followed by two single quotes ', and then directly the filename of the corresponding encoding, and there are no double quotes at both ends of the entire string.
Note that there are two more lines of data after the line Content-Type.

--{
    
    boundary}(\r\n)
Content-Disposition: form-data; name="{key}"; filename="{filename}"(\r\n)[; filename*={
    
    编码方式}''{
    
    对应编码的filename}]
Content-Type: {
    
    文件格式}(\r\n)
(\r\n)
(\r\n)
{
    
    二进制数据}(\r\n)
--{
    
    boundary}

2. Mongoose file upload implementation

Since mongoose does not provide the file upload function, we need to build the file upload process ourselves

2.1 Build request header

The header contains at least the following 3 parts

  • "POST /upload HTTP/1.1\r\n" means post file upload
  • "Content-Type: multipart/form-data; Boundary=" can contain multiple form-data
  • "Content-Length: "File length = file header + file length + file tail

2.2 Build the body header

  • boundary: add a line to indicate the beginning of a form-data
  • Content-Disposition: The type form-data needs to be specified, and the name is file and filename
  • Content-Type: Refer to the encoding format of the request body, such as uploading a jpg image, then here is image/jpeg

2.3 Send header and body header

2.4 Send file stream cyclically

2.5 Send body to end segmentation

A single line of boundary indicates the end of a form-data

3. Complete code

#include <iostream>
#include "mongoose.h"
#include <string>

static const uint64_t s_timeout_ms = 1500;

static void ev_handler(struct mg_connection* conn, int ev, void* ev_data, void *fn_data) {
    
    
    if (ev == MG_EV_OPEN) {
    
    
        // Connection created. Store connect expiration time in c->data
        *(uint64_t*)conn->data = mg_millis() + s_timeout_ms;
    }
    else if (ev == MG_EV_POLL) {
    
    
        if (mg_millis() > *(uint64_t*)conn->data &&
            (conn->is_connecting || conn->is_resolving)) {
    
    
            mg_error(conn, "Connect timeout");
        }
    }
    else if (ev == MG_EV_CONNECT) {
    
    
        // Connected to server. Extract host name from URL
    }
    else if (ev == MG_EV_HTTP_MSG) {
    
    
        struct mg_http_message* hm = (struct mg_http_message*)ev_data;
        // 处理HTTP响应
        std::cout << "Response body: \n" << hm->body.ptr << std::endl;
        bool* done = (bool*)fn_data;
        *done = true;
    }
}
int main() {
    
    
    std::string url {
    
    "http://192.168.31.86:8081/upload"};

    // 读取要上传的文件
    FILE* fp = fopen("E:\\code\\Yolov5_Tensorrt_Win10-master\\pictures\\bus.jpg", "rb");
    if (fp == NULL) {
    
    
        std::cout << "Failed to open file" << std::endl;
        return 1;
    }
    //get file len
    fseek(fp, 0, SEEK_END);
    int file_size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    struct mg_mgr mgr;
    struct mg_connection* conn;
    mg_mgr_init(&mgr);

    // create http connection
    bool done = false;
    conn = mg_http_connect(&mgr, url.c_str(), ev_handler, &done);
    if (conn == NULL) {
    
    
        std::cout << "Failed to connect" << std::endl;
        return 1;
    }

    // Build HTTP request body
    std::string boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
    std::string body_header = "--" + boundary + "\r\n"
        "Content-Disposition: form-data; name=\"file\"; filename=\"bus.jpg\"\r\n"
        "Content-Type: image/jpeg\r\n\r\n";

    std::string body_end = "\r\n--" + boundary + "--\r\n";

    // Build HTTP headers
    struct mg_str host = mg_url_host(url.c_str());

    std::string headers = "POST /upload HTTP/1.1\r\n"
        "Host: "+ std::string(host.ptr) + "\r\n"
        "Connection: keep-alive\r\n"
        "Content-Type: multipart/form-data; Boundary=" + boundary + "\r\n"
        "Content-Length: " + std::to_string(body_header.length() + file_size + body_end.length()) + "\r\n\r\n";

    // Send HTTP request
    mg_send(conn, headers.c_str(), headers.length());
    mg_send(conn, body_header.c_str(), body_header.length());

    // 逐块读取文件并发送数据
    char buffer[1024] = {
    
     0 };
    size_t bytes_read = 0;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
    
    
        mg_send(conn, buffer, bytes_read);
    }
    mg_send(conn, body_end.c_str(), body_end.length());

    fclose(fp);

    // 等待响应
    while (!done) {
    
    
        mg_mgr_poll(&mgr, 1000);
    }
    mg_mgr_free(&mgr);
    
    return 0;
}

Preliminary knowledge reference link:
https://blog.coolight.cool/http-cform-data%E8%A7%A3%E6%9E%90/?replytocom=41295

Guess you like

Origin blog.csdn.net/wyw0000/article/details/131434243