cinatra--一个高效易用的c++ http框架

cinatra是一个高性能易用的http框架,它是用modern c++(c++17)开发的,它的目标是提供一个快速开发的c++ http框架。它的主要特点如下:

  1. 统一而简单的接口
  2. header-only
  3. 跨平台
  4. 高效
  5. 支持面向切面编程

cinatra目前支持了http1.1/1.0和websocket, 你可以用它轻易地开发一个http服务器,比如常见的数据库访问服务器、文件上传下载服务器、实时消息推送服务器,你也可以基于cinatra开发一个mqtt服务器。

如何使用

编译依赖

cinatra是基于boost.asio开发的,所以需要boost库,同时也需要支持c++17的编译器,依赖项:

  1. boost.asio
  2. c++17编译器(gcc7.2,clang4.0, vs2017 update15.5)

使用

cinatra是header-only的,直接引用头文件既可。

快速示例

示例1:一个简单的hello world

#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");
	server.set_http_handler<GET, POST>("/", [](const request& req, response& res) {
		res.set_status_and_content(status_type::ok, "hello world");
	});

	server.run();
	return 0;
}

5行代码就可以实现一个简单http服务器了,用户不需要关注多少细节,直接写业务逻辑就行了。

示例2:展示如何取header和query以及错误返回

#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");
	server.set_http_handler<GET, POST>("/test", [](const request& req, response& res) {
		auto name = req.get_header_value("name");
		if (name.empty()) {
			res.set_status_and_content(status_type::bad_request, "no name");
			return;
		}

		auto id = req.get_query_value("id");
		if (id.empty()) {
			res.set_status_and_content(status_type::bad_request);
			return;
		}

		res.set_status_and_content(status_type::ok, "hello world");
	});

	server.run();
	return 0;
}

示例3:面向切面的http服务器

#include "http_server.hpp"
using namespace cinatra;

//日志切面
struct log_t
{
	bool before(const request& req, response& res) {
		std::cout << "before log" << std::endl;
		return true;
	}

	bool after(const request& req, response& res) {
		std::cout << "after log" << std::endl;
		return true;
	}
};

//校验的切面
struct check {
	bool before(const request& req, response& res) {
		std::cout << "before check" << std::endl;
		if (req.get_header_value("name").empty()) {
			res.set_status_and_content(status_type::bad_request);
			return false;
		}

		return true;
	}

	bool after(const request& req, response& res) {
		std::cout << "after check" << std::endl;
		return true;
	}
};

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");
	server.set_http_handler<GET, POST>("/aspect", [](const request& req, response& res) {
		res.set_status_and_content(status_type::ok, "hello world");
	}, check{}, log_t{});

	server.run();
	return 0;
}

本例中有两个切面,一个校验http请求的切面,一个是日志切面,这个切面用户可以根据需求任意增加。本例会先检查http请求的合法性,如果不合法就会返回bad request,合法就会进入下一个切面,即日志切面,六安广播电视大学日志切面会打印出一个before表示进入业务逻辑之前的处理,池州先锋网业务逻辑完成之后会打印after表示业务逻辑结束之后的处理。

示例4:文件上传

cinatra目前支持了multipart和octet-stream格式的上传。

multipart文件上传

#include <atomic>
#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");

	std::atomic_int n = 0;
	//http upload(multipart)
	server.set_http_handler<GET, POST>("/upload_multipart", [&n](const request& req, response& res) {
		assert(req.get_http_type() == http_type::multipart);
		auto state = req.get_state();
		switch (state)
		{
		case cinatra::data_proc_state::data_begin:
		{
			auto file_name_s = req.get_multipart_file_name();
			auto extension = get_extension(file_name_s);

			std::string file_name = std::to_string(n++) + std::string(extension.data(), extension.length());
			auto file = std::make_shared<std::ofstream>(file_name, std::ios::binary);
			if (!file->is_open()) {
				res.set_continue(false);
				return;
			}
			req.get_conn()->set_tag(file);
		}
		break;
		case cinatra::data_proc_state::data_continue:
		{
			if (!res.need_continue()) {
				return;
			}

			auto file = std::any_cast<std::shared_ptr<std::ofstream>>(req.get_conn()->get_tag());
			auto part_data = req.get_part_data();
			file->write(part_data.data(), part_data.length());
		}
		break;
		case cinatra::data_proc_state::data_end:
		{
			std::cout << "one file finished" << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_all_end:
		{
			//all the upstream end
			std::cout << "all files finished" << std::endl;
			res.set_status_and_content(status_type::ok);
		}
		break;
		case cinatra::data_proc_state::data_error:
		{
			//network error
		}
		break;
		}
	});

	server.run();
	return 0;
}

短短几行代码就可以实现一个http文件上传的服务器了,包含了异常处理和错误处理。

octet-stream文件上传

#include <atomic>
#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");

	std::atomic_int n = 0;
	//http upload(octet-stream)
	server.set_http_handler<GET, POST>("/upload_octet_stream", [&n](const request& req, response& res) {
		assert(req.get_http_type() == http_type::octet_stream);
		auto state = req.get_state();
		switch (state)
		{
		case cinatra::data_proc_state::data_begin:
		{
			std::string file_name = std::to_string(n++);;
			auto file = std::make_shared<std::ofstream>(file_name, std::ios::binary);
			if (!file->is_open()) {
				res.set_continue(false);
				return;
			}
			req.get_conn()->set_tag(file);
		}
		break;
		case cinatra::data_proc_state::data_continue:
		{
			if (!res.need_continue()) {
				return;
			}

			auto file = std::any_cast<std::shared_ptr<std::ofstream>>(req.get_conn()->get_tag());
			auto part_data = req.get_part_data();
			file->write(part_data.data(), part_data.length());
		}
		break;
		case cinatra::data_proc_state::data_end:
		{
			std::cout << "one file finished" << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_error:
		{
			//network error
		}
		break;
		}
	});

	server.run();
	return 0;
}

示例5:文件下载

#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");

	//http download(chunked)
	server.set_http_handler<GET, POST>("/download_chunked", [](const request& req, response& res) {
		auto state = req.get_state();
		switch (state)
		{
		case cinatra::data_proc_state::data_begin:
		{
			std::string filename = "2.jpg";
			auto in = std::make_shared<std::ifstream>(filename, std::ios::binary);
			if (!in->is_open()) {
				req.get_conn()->on_error();
				return;
			}

			auto conn = req.get_conn();
			conn->set_tag(in);
			auto extension = get_extension(filename.data());
			auto mime = get_mime_type(extension);
			conn->write_chunked_header(mime);
		}
		break;
		case cinatra::data_proc_state::data_continue:
		{
			auto conn = req.get_conn();
			auto in = std::any_cast<std::shared_ptr<std::ifstream>>(conn->get_tag());

			std::string str;
			const size_t len = 2*1024;
			str.resize(len);

			in->read(&str[0], len);
			size_t read_len = in->gcount();
			if (read_len != len) {
				str.resize(read_len);
			}
			bool eof = (read_len==0|| read_len != len);
			conn->write_chunked_data(std::move(str), eof);
		}
		break;
		case cinatra::data_proc_state::data_end:
		{
			std::cout << "chunked send finish" << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_error:
		{
			//network error
		}
		break;
		}
	});

	server.run();
	return 0;
}

示例6:websocket

#include "http_server.hpp"
using namespace cinatra;

int main() {
	http_server server(std::thread::hardware_concurrency());
	server.listen("0.0.0.0", "8080");

	//web socket
	server.set_http_handler<GET, POST>("/ws", [](const request& req, response& res) {
		assert(req.get_http_type() == http_type::websocket);
		auto state = req.get_state();
		switch (state)
		{
		case cinatra::data_proc_state::data_begin:
		{
			std::cout << "websocket start" << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_continue:
		{
			auto part_data = req.get_part_data();
			//echo
			req.get_conn()->send_ws_msg(std::string(part_data.data(), part_data.length()));
			std::cout << part_data.data() << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_close:
		{
			std::cout << "websocket close" << std::endl;
		}
		break;
		case cinatra::data_proc_state::data_error:
		{
			std::cout << "network error" << std::endl;
		}
		break;
		}
	});

	server.run();
	return 0;
}

性能测试

测试用例:

ab测试:ab -c100 -n5000 127.0.0.1:8080/

服务器返回一个hello。

在一个8核心16G的云主机上测试,qps在9000-13000之间。

对比测试

通过ab测试和boost.beast做对比,二者qps相当,大概是因为二者都是基于boost.asio开发的的原因。cinatra目前还没做专门的性能优化,还有提升空间。

注意事项

文件上传下载,websocket的业务函数是会多次进入的,因此写业务逻辑的时候需要注意,推荐按照示例中的方式去做。

cinatra目前刚开始在生产环境中使用, 还处于开发完善阶段,可能还有一些bug,因此不建议现阶段直接用于生产环境,建议先在测试环境下试用。

试用没问题了再在生产环境中使用,试用过程中发现了问题请及时提issue反馈或者邮件联系我。

测试和使用稳定之后cinatra会发布正式版。

roadmap

  1. 支持ssl
  2. 支持断点续传
  3. 支持session和cookie
  4. 接口优化、性能优化

我希望cinatra有越来越多的人使用并喜欢它,也希望在在使用过程中越来越完善,变成一个强大易用、快速开发的http框架,欢迎大家积极参与cinatra项目,可以提issue也可以发邮件提建议,也可以提pr,形式不限。

这次重构的cinatra几乎是重写了一遍,代码比之前的少了30%以上,接口统一了,http和业务分离,具备更好的扩展性和可维护性。

原文地址:https://www.cnblogs.com/tianshifu/p/8410791.html

时间: 2024-10-08 06:16:44

cinatra--一个高效易用的c++ http框架的相关文章

[email&#160;protected]一个高效的配置管理工具--Ansible configure management--翻译(六)

无书面许可请勿转载 高级playbook Finding files with variables All modules can take variables as part of their arguments by dereferencing them with {{ and }} . You can use this to load a particular file based on a variable. For example, you might want to select a

[email&#160;protected]一个高效的配置管理工具--Ansible configure management--翻译(七)

如无书面授权,请勿转载 Larger Projects Until now, we have been looking at single plays in one playbook file. This approach will work for simple infrastructures, or when using Ansible as a simple deployment mechanism. However, if you have a large and complicated

你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写

思路:java.util.concurrent.locks包下面ReadWriteLock接口,该接口下面的实现类ReentrantReadWriteLock维护了两个锁读锁和解锁,可用该类实现这个功能,很简单 import java.util.Date; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /**  * 你需要实现一个

分享一个简单易用的RPC开源项目—Tatala

http://zijan.iteye.com/blog/2041894 这个项目最早(2008年)是用于一个网络游戏的Cache Server,以及一个电子商务的Web Session服务.后来不断增加新的功能,除了Java还支持C#,到现在已经可以用它来开发网络游戏的服务器.等过些日子我还会开源网络游戏的服务器源码. 关于性能,当时后台相应请求的效率是每秒10W次,现在我在自己的笔记本上测,只有一个客户端与服务器都在一个物理机上(CPU: i7-3610QM; RAM: 8G; OS: Win

Android:一个高效的UI才是一个拉风的UI(二)

趁今晚老大不在偷偷早下班,所以有时间继续跟大伙扯扯UI设计之痛,也算一个是对上篇<Android:一个高效的UI才是一个拉风的UI(一)>的完整补充吧.写得不好的话大家尽管拍砖~(来!砸死我把~) 前言 前篇博客翻箱倒柜的介绍了优化UI设计的两个方法,第一个就是使用尽量少的组件来实现布局功能,第二个就是使用<meger>标签来减少不必要的根节点,这两个方法都可以提高应用UI的运行效率,但是够了吗?远远是不够的,方法就像money一样永远不嫌多,所以不再介绍多一些UI设计优化的方法说

通过Vim+少量插件配置一个高效简洁的IDE

最近本人在看<TCP/IP Illustrated Volume2:The Implementation>这本书,自然要下载4.4BSD-Lite的源代码配合书本一起研读.以前学习Vim的时候就知道Vim可以通过插件的功能来配置一个功能强大的自定义IDE,这次有这么好的机会为什么不利用一下呢?于是在阅读源代码的过程中根据需要一步一步配置了一个简单完整的IDE环境,通过这几天的使用真心觉得Vim好用,速度那个快呀.以前总听别人说Vim如何如何好,这次真的让我感受到了并爱上了Vim这个工具.在这里

一个高效的数据分页的存储过程 可以轻松应付百万数据

一个高效的数据分页的存储过程 可以轻松应付百万数据 CREATE PROCEDURE pageTest --用于翻页的测试--需要把排序字段放在第一列 (@FirstID nvarchar(20)=null, --当前页面里的第一条记录的排序字段的值@LastID nvarchar(20)=null, --当前页面里的最后一条记录的排序字段的值@isNext bit=null, --true 1 :下一页:false 0:上一页@allCount int output, --返回总记录数@pag

【转载】如何成为一个高效、快乐、健康的程序员

只需每天做一些小改变,就可摆脱病痛.保持健康.....学会去积累工作经验和成果就会变得高效......具备这些小习惯就会保持快乐…… 一:高效 互联网发展日新月异,社会科技每天都在发生着翻天覆地的变化,而程序员已经成了这个时代的庞大群体,各种各样的程序员数不胜数,我们每天上下班,不 论你是坐公交还是乘地铁,在你的身边一定有程序员.本人我就是其中的一员,本人主修java语言,从事java类开发工作.由于本人工作时间不长,经验尚 浅,都是学着前辈高人的路子,在慢慢前行,下面我给大家分享下,如何从菜鸟

[email&#160;protected]一个高效的配置管理工具--Ansible configure management--翻译(十二)

如无书面授权,请勿转载 第五章 自定义模块 External inventories In the first chapter we saw how Ansible needs an inventory file, so that it knows where its hosts are and how to access them. Ansible also allows you to specify a script that allows you to fetch the inventor