如何自己编写 HTTP POST 请求 multipart/form-data?

问题描述

我想在 multipart/form-data 内容中向服务器发送 POST 请求。
请求语句必须自己写,没有任何高级API
因为它必须作为调制解调器的 AT command 发送。
下面的curl命令是测试服务端API的命令,这个是我自己想构建的。

# This is what I want to construct myself.
curl -X POST $API_ENDPOINT \
-F time=yyyy-mm-dd-hh:mm:ss \
-F event=1 \
-F RSSi=31 \
-F battery=80 \
-F filename=test.jpg \
-F [email protected] \ # Send an image from my local file system to server.
-w %{http_code}
echo "\r\n"

以下是我发送到调制解调器的内容

/* This is form for multipart/form-data. Is it right?

POST $API_ENDPOINT HTTP/1.1
Host: $HOST
Content-Length: $body_length
Content-Type: multipart/form-data; boundary="boundary"

--boundary
Content-disposition: form-data; name="files"; filename="example.jpg"
Content-Type: image/jpg

--boundary--

*/

// Below C++ code was written for the above POST request.

std::string filename = "example.jpg";
std::string filePath = "example.jpg";

std::string body =
        std::string("--boundary\r\n") + 
        "Content-disposition: form-data; name=\"files\"; filename=\"" + filePath + "\"\r\n" + 
        "Content-Type: image/jpg\r\n" + 
        "\r\n" + 
        "--boundary--\r\n";

std::string header = 
        std::string("POST ") + url + " HTTP/1.1\r\n" + 
        "Host: " + host + "\r\n" + 
        "Content-Length: " + std::to_string(body.length()) + "\r\n" +  
        "Content-Type: multipart/form-data; boundary=\"boundary\"\r\n" + 
        "\r\n";

std::string data = header + body;

...这是服务器日志。

Error: MultipartParser.end(): stream ended unexpectedly: state = PART_DATA

这是我的问题。

  1. 如何在请求正文中添加字段(时间、事件、RSSI 等)?服务器似乎以 格式获取每个字段。如何将其添加到 multipart/form-data 中?
  2. 附加这样的图像文件是否正确?在 curl 命令中,我写了本地图像文件的路径 (-F files) 以及此图像文件将存储在服务器上的名称 (-F filename)。我应该如何在我的申请表中反映这一点?

谢谢兄弟

解决方法

我做到了!没错。


/*
POST $API_ENDPOINT HTTP/1.1
Host: $HOST
Content-Length: $body_length
Content-Type: multipart/form-data; boundary="boundary"

--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="time"

yyyy-mm-dd-hh:mm:ss
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="event"

1
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="rssi"

31
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="battery"

99
--boundary
Content-Type: text/plain
Content-Disposition: form-data; name="filename"

example.jpg
--boundary
Content-Type: image/jpg
Content-Disposition: form-data; name="files"; filename="example.jpg"

....binary bytes of example.jpg...
--boundary--
*/
    const int fd = // File descriptor of my serial port
    const std::string host = // host
    const std::string uri = // uri
    const int tryout = 10 // for modem
    std::string filename = "1996-03-05.jpg";
    std::string filePath = "1996-03-05.jpg";
    std::ifstream bin(filePath,std::ios::binary);
    std::string imageBin((std::istreambuf_iterator<char>(bin)),std::istreambuf_iterator<char>());
    std::string TIMESTAMP = "1996-03-05";
    std::string event = "1";
    std::string rssi = "31";
    std::string battery = "90";

    atcmd::__sendATcmd(fd,"AT+QHTTPCFG=\"contextid\",1\r");
    atcmd::__readBufferUntil(fd,"\r\nOK\r\n",tryout);

    atcmd::__sendATcmd(fd,"AT+QHTTPCFG=\"contenttype\",3\r"); // 3: multipart/form-data
    atcmd::__readBufferUntil(fd,"AT+QHTTPCFG=\"requestheader\",tryout);

    const std::string fullUrl = "http://" + host + uri;
    atcmd::__sendATcmd(fd,("AT+QHTTPURL=" + std::to_string(fullUrl.length()) + "\r").c_str());
    atcmd::__readBufferUntil(fd,"\r\nCONNECT\r\n",tryout);
    atcmd::__sendATcmd(fd,fullUrl.c_str());
    atcmd::__readBufferUntil(fd,tryout);

    std::string body_fields = (
        std::string("--boundary\r\n") + 
        "Content-Type: text/plain\r\n" + 
        "Content-Disposition: form/data; name=\"time\"\r\n" + 
        "\r\n" + 
        TIMESTAMP + "\r\n" + 

        "--boundary\r\n" + 
        "Content-Type: text/plain\r\n" + 
        "Content-Disposition: form/data; name=\"event\"\r\n" + 
        "\r\n" + 
        event + "\r\n" + 

        "--boundary\r\n" + 
        "Content-Type: text/plain\r\n" + 
        "Content-Disposition: form/data; name=\"rssi\"\r\n" + 
        "\r\n" + 
        rssi + "\r\n" +  

        "--boundary\r\n" + 
        "Content-Type: text/plain\r\n" + 
        "Content-Disposition: form/data; name=\"battery\"\r\n" + 
        "\r\n" + 
        battery + "\r\n" + 

        "--boundary\r\n" + 
        "Content-Type: text/plain\r\n" + 
        "Content-Disposition: form/data; name=\"filename\"\r\n" + 
        "\r\n" + 
        filename + "\r\n"
    );

    std::string body_image = (
        std::string("--boundary\r\n") + 
        "Content-Type: image/jpeg\r\n" + 
        "Content-Disposition: form-data; name=\"files\"; filename=\"" + filePath + "\"\r\n" + 
        "\r\n" + 
        imageBin + "\r\n" + 
        "--boundary--\r\n"
    );

    int bodyLen = body_fields.length() + body_image.length();

    std::string header = (
        std::string("POST ") + uri + " HTTP/1.1\r\n" + 
        "Host: " + host + "\r\n" + 
        "Content-Length: " + std::to_string(bodyLen) + "\r\n" +  
        "Content-Type: multipart/form-data; boundary=\"boundary\"\r\n" + 
        "\r\n"
    );

    const std::string dataLen = std::to_string(header.length() + bodyLen);
    std::string maxInputBodyTime = "80";
    std::string maxResponseTime = "80";
    atcmd::__sendATcmd(fd,("AT+QHTTPPOST="
                            + dataLen + ","
                            + maxInputBodyTime + "," 
                            + maxResponseTime + "\r").c_str());
    atcmd::__readBufferUntil(fd,header.c_str());
    atcmd::__sendATcmd(fd,body_fields.c_str());
    atcmd::__sendATcmd(fd,body_image.c_str(),body_image.length());
    atcmd::__readBufferUntil(fd,"AT+QHTTPREAD=80\r");
    std::cout << atcmd::__readBuffer(fd) << std::endl;