基于mongoose + MFC 搭建本本地HTTP服务器

        最近有个需求,需要做一个桌面插件,其实是一个微型桌面应用,可以集成第三方的接口,比如网络摄像头,身份证读卡器等,同时又需要处理前端(谁站的离用户近谁就是前端,常见的是H5,或者其他什么不三不四的管理系统、网站什么的)过来的请求,不管谁了,反正有奶就是娘,人家需要你就去奶。同时还需要处理本地文件上传等需求,文件上传又引入另外一个开源裤curl 。把以上信息整理如下  

  • 需求
  1. http 服务;
  2. GUI 用户界面(摄像头信息预览等);
  3. 其他网络操作(比如文件上传,获取参数);
  4. 集成第三方接口;
  • 结构设计
  1. 方案一是服务+业务的标准流程,而且都是在Windows 平台下实现,正常没这么干的。正常需求从接口流进来,到调度过滤转发,然后业务系统处理任务并更新状态,调度拿到任务状态再推给用户,整个流程结束。这个方案有点儿小题大做了,硬性剥离开服务和业务,而且实现起来人为增加了复杂度。惯性思维+想的太多;
  2. 方案二叫一个应用或者一个服务+多个模块。因为整个应用,使用频率,并发要求并不高,几乎为0。也就是说只要是活的就行;
  • 引入的依赖
  1. 配置部分,因为是MFC应用,使用的是WINAPI;
  2. http 服务,数据格式统一成json, 所以引入jsoncpp, 个人感觉不好用;
  3. http 服务,引入mongoose,体量小,操作简单的web服务器。用soket 也可以,可能我的打开方式有问题。前期用socket ,拼接的格式总有问题,各种坑,自己也对协议的具体格式不是很了解,项目需要,也没有那么多时间去研究协议,所以果断放弃,改用mongoose;
  4. 其他的网络服务,http get/post 都是用curl 实现的;
  5. 第三方依赖;
  • 程序的整体结构,技术细节先不讨论,具体会单写文章讨论,这里综述一下整个项目流程,用到部分C11的特性
  1. http 服务;

本地封装一个httpServer,提供start(), close()等函数支持调用

bool HttpServer::Start()
{
	std::thread t([&](){
		
		mg_mgr_init(&m_mgr, NULL);
		mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), OnHttpEvent);
		if (connection == NULL)
			return false;
		mg_set_protocol_http_websocket(connection);

		printf("starting http server at port: %s\n", m_port.c_str());
		// loop
		while (true)
		{
			if (m_bExitSvr)
			{
				break;
			}
			mg_mgr_poll(&m_mgr, 500); // ms
		}
		return true;
	});
	t.detach();

	return true;
}
bool HttpServer::Close()
{
	m_bExitSvr = true;
	Sleep(1000);

	mg_mgr_free(&m_mgr);
	return true;
}

启动服务和注册外部回调(用于交互)

void CCtWebCameraDlgApp::startHttpSvr()
{
	std::string port = "6080";
	http_server = std::shared_ptr<HttpServer>(new HttpServer);
	http_server->Init(port);

	// 摄像头采集
	http_server->AddHandler("/api/camera", [&](std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback){		
		int iState(200);
		std::string str("init data.");
		CCtWebCameraDlgDlg *dlg = (CCtWebCameraDlgDlg *)theApp.m_pMainWnd;
		if (dlg != nullptr)
		{
			std::string strType = getImgType();
			dlg->getImgBase64(strType.c_str(), str, iState);
		}
	
		rsp_callback(c, str, iState);
		return true;
	});

	// 身份证信息采集
	http_server->AddHandler("/api/idcard", [&](std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback){
		int iState(200);
		std::string str("init data.");
		CCtWebCameraDlgDlg *dlg = (CCtWebCameraDlgDlg *)theApp.m_pMainWnd;
		if (dlg != nullptr)
		{
			dlg->getIdCardInfo(str, iState);
		}

		rsp_callback(c, str, iState);
		return true;
	});

	http_server->Start();
}

处理http协议

void HttpServer::OnHttpEvent(mg_connection *connection, int event_type, void *event_data)
{
	http_message *http_req = (http_message *)event_data;
	switch (event_type)
	{
	case MG_EV_HTTP_REQUEST:
		HandleEvent(connection, http_req);
		break;
    case MG_EV_RECV:    // tcp
        break;
	default:
		break;
	}
}

处理请求并响应

void HttpServer::HandleEvent(mg_connection *connection, http_message *http_req)
{
	std::string req_str = std::string(http_req->message.p, http_req->message.len);
	printf("got request: %s\n", req_str.c_str());


	// 先过滤是否已注册的函数回调
	std::string url = std::string(http_req->uri.p, http_req->uri.len);
	std::string body = std::string(http_req->body.p, http_req->body.len);
	std::string query = std::string(http_req->query_string.p, http_req->query_string.len);


	char var[MAX_PATH];
	var[0] = '\0';
	mg_get_http_var(&http_req->query_string, "callback", var, sizeof(var));        //获取变量
	m_strCallback = var;
	
	mg_get_http_var(&http_req->query_string, "image_type", var, sizeof(var));        //获取变量
	m_strImg_type = var;


	auto it = s_handler_map.find(url);
	if (it != s_handler_map.end())
	{
		ReqHandler handle_func = it->second;
		handle_func(url, body, connection, SendRsp);
	}


	if (route_check(http_req, "/api/camera"))
	{
		SendRsp(connection, "connect success.", 200);
	}
	std::string req_str = std::string(http_req->message.p, http_req->message.len);
	printf("got request: %s\n", req_str.c_str());


	// 先过滤是否已注册的函数回调
	std::string url = std::string(http_req->uri.p, http_req->uri.len);
	std::string body = std::string(http_req->body.p, http_req->body.len);
	std::string query = std::string(http_req->query_string.p, http_req->query_string.len);


	char var[MAX_PATH];
	var[0] = '\0';
	mg_get_http_var(&http_req->query_string, "callback", var, sizeof(var));        //获取变量
	m_strCallback = var;
	
	mg_get_http_var(&http_req->query_string, "image_type", var, sizeof(var));        //获取变量
	m_strImg_type = var;


	auto it = s_handler_map.find(url);
	if (it != s_handler_map.end())
	{
		ReqHandler handle_func = it->second;
		handle_func(url, body, connection, SendRsp);
	}


	if (route_check(http_req, "/api/camera"))
	{
		SendRsp(connection, "connect success.", 200);
	}
}

构造响应信息

void HttpServer::SendRsp(mg_connection *connection, std::string rsp, int state)
{
	// 必须先发送header
	mg_printf(connection, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");

	std::string strSt;
	strSt = "\r\n\"state\":" + std::to_string(state);
	strSt += ",\r\n";

	std::string strRep("([{");
	strRep.append(strSt);
	strRep.append("\"result\": %s \r\n}])");

	std::string strRes(m_strCallback);
	strRes.append(strRep);

	// 以json形式返回
	//mg_printf_http_chunk(connection, "{ \"result\": \"%s\"}", rsp.c_str());
	mg_printf_http_chunk(connection, strRes.c_str(), rsp.c_str());

	// 发送空白字符快,结束当前响应
	mg_send_http_chunk(connection, "", 0);
}

服务部分到这里结束,基本的http请求都可以处理了。值得注意的是跨域访问的时候传入callback=whenreadover,我们填充json结构如下就可以解决跨域的问题

curl  http文件上传, 这个是固定套路,没什么好说的,服务端只要接受对应格式的文件,基本不会有什么问题,亲测

int http_post_file(LPSTR url, LPSTR filename, LPSTR strImg, std::string& strRes)
{
	assert(url != NULL);
	assert(filename != NULL);

	std::string strName = getfileNm(filename);

	int ret = -1;
	CURL *curl = NULL;
	CURLcode code;
	CURLFORMcode formCode;
	int timeout = 15;

	struct curl_httppost *post = NULL;
	struct curl_httppost *last = NULL;
	struct curl_slist *headerlist = NULL;

	curl_formadd(&post, &last, CURLFORM_COPYNAME, "image_type",
		CURLFORM_COPYCONTENTS, strImg,
		CURLFORM_END);

	curl_formadd(&post, &last, CURLFORM_COPYNAME, "file",
		CURLFORM_FILE, filename,
		CURLFORM_END);

	curl_formadd(&post, &last,
		CURLFORM_COPYNAME, "submit",
		CURLFORM_COPYCONTENTS, "upload",
		CURLFORM_END);

	curl = curl_easy_init();
	if (curl == NULL)
	{
		fprintf(stderr, "curl_easy_init() error./n");
		goto out;
	}

	curl_easy_setopt(curl, CURLOPT_HEADER, 0);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
	curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &process_storage_server_data);

	code = curl_easy_perform(curl);
	if (code != CURLE_OK)
	{
		fprintf(stderr, "curl_easy_perform[%d] error./n", code);
		goto all;
	}

	ret = 0;

all:
	curl_easy_cleanup(curl);
out:
	curl_formfree(post);

	strRes = g_StroageServerResponseResult;
	return ret;
}
  • 这个服务的测试,通过nodejs/curl 发送http 请求,文件上传部分通过formidable 做测试
  • 安装nodejs 运行http_post.js 发送HTTP post 请求,关于nodejs 发送HTTP请求,有篇文章写的特别好,https://my.oschina.net/freddon/blog/513853
var http = require('http');  
var querystring = require('querystring');  
  
var post_data = {  
    image_type: 1,  
    time: new Date().getTime()};//这是需要提交的数据  
var content = querystring.stringify(post_data);  
  
var options = {  
    hostname: '127.0.0.1',  
    port: 6080,  
    path: '/api/camera',  
    method: 'POST',  
    headers: {  
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'  
    }  
};  

callback = function(res){
	console.log('STATUS: ' + res.statusCode);  
    console.log('HEADERS: ' + JSON.stringify(res.headers));  
    res.setEncoding('utf8');  
    res.on('data', function (chunk) {  
		console.log("function call back [ing].......");
        console.log('BODY: ' + chunk);  
	});
	res.on('end', function (chunk){
		console.log("callback end...........");
		console.log('body' + chunk);
	});
}

var req = http.request(options, callback);
  
req.on('error', function (e) {  
    console.log('problem with request: ' + e.message);  
});  
  
// write data to request body  
req.write(content);  
req.end();  

console.log("start http_post to start camera.")
  • 通过curl.exe 发送HTTP post 请求

curl -x 127.0.0.1:8888 -F "[email protected]" -F "image_type=1" http://timing.1qjd.com/Api/Data/uploadInfoImage

# curl 为Windows平台,编译出来的可执行文件,通过命令行形式调用;

#127.0.0.1:8888 设置代理信息,通过一些抓包工具比如Findler可以捕捉到请求;需要开启本地代理服务,这个稍后会单独讲;

# -F 为请求参数,这里不太确定@是什么意思,因为1.jpg和curl.exe 同路径,所以可以直接传文件名,否则应该指定文件全路径;

# http://timing....... 为服务器地址

# 上图蓝色部分为服务器响应信息;

以上零零散散,写的比较乱,结构不够完整,慢慢进步。

猜你喜欢

转载自blog.csdn.net/moyebaobei1/article/details/81042336
MFC