1. 预备知识
1.1 URL编解码
常用于url链接和application/x-www-form-urlencoded格式的请求体中对参数进行编码
由于url的参数的样子是key1=value1&key2=value2,如果key或者value中包含= &等字符,就会导致解析时混乱了,因此需要一种编码来把这些可能引起歧义的符号替换掉
例如:http://localhost/src/components/global/Checkbox.vue?type=style&index=0
这个链接中 ? 的后面就是参数部分,即 type=style&index=0
这是两个键值对,type值为style,index值为0
假如现在 type 的值为 a=b,那么参数部分最后组装成 type=a=b&index=0 ,可见已经有点歧义了,但由于&分割,兴许还能解析
如果再假设type的值为 a&c=d,那组装后是 type=a&c=d&index=0,显然这个字符串给程序去解析的话,天王老子来了也会被解析为三个部分:type值为a,c值为d,index值为0
1.2 请求体编码格式
Http协议中,请求体有多种格式,如:
- application/x-www-form-urlencoded,相当常用的格式,即和url中的参数一样,是key=value格式的字符串,且这个字符串是经过url编码的,在解析之前需要进行url解码。
- multipart/form-data,可以上传多个键值对/文件。具体格式下文将着重展开。
- application/json,顾名思义,就是json格式的
- application/xml,xml格式,即像HTML一般的标签
- text/plain,文本
- application/octet-stream,二进制数据,且仅能上传一个文件。如果传单个文件(图片、音视频)使用这个相当快乐,它并不需要解析,整个请求体就是文件,但需要使用其他方式上传文件的文件名等信息。
如果有请求体,则应该在请求头使用 Content-Type 说明使用的编码格式
1.3 form-data格式
如果请求体是form-data格式,则在请求头中,我们应该能找到 Content-Type 的值为 multipart/form-data 且它后面会带一个 boundary:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
这里boundary是解析请求体用的
我们先来看看form-data格式的请求体的样子:
------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--
这个请求体示例中有两个部分:
- 文件名为hello.gif的动态图
- 一个键值对,键为mytext,值为coolight
刚刚在请求头的 Content-Type 中的boundary的值在请求体中是用来分割数据的,在boundary前加两个-,即:–{boundary},并用它独占一行作为分隔标志。
注意最后一行分割标志的后面还有两个-,即 --{boundary}–
我们先看示例的第二部分,即键值对 mytext=coolight
其格式如下,其中把换行(\r\n)标出:
注意换行 \r\n 也是格式的一部分
--{boundary}(\r\n)
Content-Disposition: form-data; name="{key}"(\r\n)
(\r\n)
{value}(\r\n)
--{boundary}
然后是第一部分,上传文件时的格式:
其中与键值对格式不同的是,在name后面多了个filename,再后面还有一个可选的filename*。这是因为如果filename里面包含中文等非ASCII字符时,因客户端和服务端的编码不同而导致解析时filename乱码,因此可能会多传一个filename*,指定使用的编码格式,如UTF-8,且注意它的值是编码方式后紧接着两个单引号’,然后直接是对应编码的filename,整个字符串两端没有双引号。
注意 行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 文件上传实现
由于mongoose并没有提供文件上传功能,因此需要我们自己构建文件上传过程
2.1 构建请求header
header至少包含以下3部分
- “POST /upload HTTP/1.1\r\n” 说明是post 文件upload
- “Content-Type: multipart/form-data; Boundary=” 可包含多个form-data
- "Content-Length: "文件长度=文件头+文件长度+文件尾
2.2 构建body头
- boundary: 加入一行分割表示一个form-data的开始
- Content-Disposition: 需指定类型form-data, name是file及filename
- Content-Type: 参考请求体编码格式,比如上传时jpg图片,那么这里就时image/jpeg
2.3 发送header和body头
2.4 循环发送文件流
2.5 发送body结束分割
单独一行boundary表示一个form-data的结束文章来源:https://www.toymoban.com/news/detail-629462.html
3. 完整代码
#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;
}
预备知识参考链接:
https://blog.coolight.cool/http-cform-data%E8%A7%A3%E6%9E%90/?replytocom=41295文章来源地址https://www.toymoban.com/news/detail-629462.html
到了这里,关于mongoose库实现http文件上传的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!