28 Star 108 Fork 43

calvinwilliams / restserver

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

一个小巧、高效、低耗的C技术栈的RESTful应用服务平台(restserver)

1. 诞生

我家中的PC配置强劲,我使用一直喜欢的firefox浏览器浏览网页,公司里笔记本配置较弱,只能跑chrome浏览器,于是跨浏览器书签同步是个老大难问题,搜了很久也没找到一款的通吃主流浏览器、免费好用的书签插件,于是想自己开发一个,采用RESTful接口风格。

我不懂浏览器端开发,那就先把服务器端写好部署到网上,对外提供服务,其他熟悉浏览器端开发同学有兴趣有时间都可以开发自己品牌的浏览器端插件,对接我的服务端,这会不会是另一种项目协作方式呢。

目前服务器端应用服务平台C技术栈有Apache,JAVA技术栈有Tomcat,不过这些都是HTTP服务器,没有对RESTful层做封装,开发起来很累,而且将来计划部署到我的阿里云服务器上,但只有1GB内存,采用JAVA技术栈的话能启动起来就不错了,不指望能对外提供正常规模的服务,一盘算,先研发一个C技术栈的RESTful应用服务平台,再在上面开发浏览器书签SaaS服务,这就是本项目的起源。

首先取个简单好记的名字,由于是RESTful接口风格的服务平台,就叫做“restserver”吧 ^_^

2. 简介

restserver是一个小巧、高效、低耗的C技术栈的RESTful应用服务平台。

小巧是因为链接出来的可执行程序只有300多KB,应用接口库80KB,本体源码都在一个目录中,手写的大概一千行左右,用预置好的makefile一条命令就能完成源码编译安装。

高效是因为她完全用C编写而成,采用多进程+多路复用模型,参考Nginx。

低耗是因为空载运行只占了几MB内存,特别适合买不起高配云服务器的个人开发者。对于企业来说,现在动不动就要求8、16、32GB内存配置,如果软件能低耗运行,节省下来的硬件支出也是相当可观,或者说相同配置的硬件上能对外提供更大容量的应用服务。

C技术栈在前面已经提到了,考虑到现实情况,我要在网上唯一拥有的服务器上运行,只能用C写,所以,“缺乏资金”和“懒”一样,都是人类文明进步的原动力。

经过两个晚上和周末两天的集中研发,感谢老婆、孩子的不打扰之恩,也感谢以前的我留下来那么多封装良好的库、工具、框架,我只手写了大概一千多行就组装出了一个可运行的版本,又经过几个晚上的打磨,restserver横空出世,还是那句话,脑子里想想和动手去做是两件完全不同的事,实现核心功能和打造完整软件又是另外两件完全不同的事。

restserver功能特性如下:(截止版本v0.8.0)

  • HTTP核心功能:如侦听IP、PORT、域名匹配、超时控制。
  • HTTP安全控制:防御巨量HTTP头选项、防御巨大HTTP头、防御巨大HTTP体。
  • 平台封装至RESTful层:与Apache、Tomcat封装HTTP层相比,封装层次更高,应用无需处理HTTP层的众多细节,自带RESTful控制器直接分派到RESTful服务入口,应用接口直接提供RESTful编程接口。你也可以编写自己的控制器替换自带控制器。
  • 多进程+多路复用模型:充分利用多核环境,防御慢速TCP,支持巨量TCP连接和同时收发,且性能卓越。
  • 可执行程序+动态库模式:restserver是应用服务平台(可执行程序),启动后装载应用(动态库),外来请求被平台接收和解析,转交给应用动态库处理,处理完后返回平台,发送响应回去,平台和应用的部署运行边界解耦清晰。
  • 运行模式:以前给公司研发的多款平台框架沉淀下来的优秀设计思想,测试模式即时装卸应用,重构应用后无需重启平台,生产模式预装载应用,性能无损耗,谁说鱼与熊掌不可兼得?那是教条!
  • 平台自有日志设施:可配置日志文件名、日志等级,同时应用也能使用。

3. "Hello world"

上一节功能特性里有说到应用服务平台(可执行程序)restserver启动后装载应用(动态库)工作,因为restserver比Apache、Tomcat多封装了一层RESTful,所以应用要实现的内容就很少了,这有助于减少应用开发量,也尽可能瘦化应用代码结构,简洁的就是最好的。

本节Hello示例代码在源码路径example/hello/,你可以在安装完restserver后自行到hello目录中编译运行、修改代码再编译运行,体验绕过HTTP直接编写RESTful服务的方便性。

只有一个源文件hello.c,直接上代码:

#include "restserver_api.h"

funcRestServiceEntry GET_hello;
int GET_hello( struct RestServerContext *ctx )
{
	char		response[1024] ;
	int		response_len ;
	
	int		nret = 0 ;
	
	/* 初始化临时缓冲区 */
	memset( response , 0x00 , sizeof(response) );
	response_len = 0 ;
	
	/* 填充hello信息 */
	response_len = snprintf( response , sizeof(response) , "Hello restserver\n" ) ;
	
	/* 构造HTTP缓冲区 */
	nret = RSAPIFormatHttpResponse( ctx , response , response_len , NULL ) ;
	if( nret )
		return nret;
	
	return 0;
}

static struct RestServiceConfig		g_rest_services_config[] = {
		{ "GET" , "/hello" , GET_hello } ,
		{ "" , "" , NULL }
	} ;

funcInitRestApplication InitRestApplication;
int InitRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 创建RESTful服务控制器 */
	ctl = RSAPICreateRestServiceControler( g_rest_services_config ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_CREATE_RESTSERVICECONTROLER;
	
	/* 设置RESTful服务控制器到restserver动态库对象运行实例中 */
	RSAPISetUserData( ctx , ctl );
	
	return 0;
}

funcCallRestApplication CallRestApplication;
int CallRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	int				nret = 0 ;
	
	/* 从restserver动态库对象运行实例中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_GET_RESTSERVICECONTROLER;
	
	/* 让RESTful服务控制器分派服务处理入口 */
	nret = RSAPIDispatchRestServiceControler( ctl , ctx ) ;
	if( nret )
		return nret;
	
	return 0;
}

funcCleanRestApplication CleanRestApplication;
int CleanRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 从restserver动态库对象运行实例中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	
	/* 销毁RESTful服务控制器 */
	RSAPIDestroyRestServiceControler( ctl );
	
	return 0;
}

每一个应用(动态库)中必须有三个函数InitRestApplication、CallRestApplication和CleanRestApplication,分别在动态库装载时、HTTP请求发生时、卸载时调用。

当HTTP请求是"GET /hello",RESTful服务控制器从配置中查出服务处理入口函数是GET_helloHello服务处理逻辑为填充临时缓冲区,最后构造HTTP响应缓冲区。

运行过程:

  1. 先打开第一个窗口,启动restserver
$ cd $HOME
$ restserver src/restserver/example/hello/restserver.conf

注意:指定的配置文件路径根据你放源码的目录按需调整。 注意:生产运行需要用nohup放置后台跑。

  1. 再打开第二个窗口,用curl发起HTTP请求

测试示例Hello的脚本是src/restserver/example/hello/hello.sh,也可以直接执行,里面就一行

$ curl "http://localhost:7911/hello"
Hello restserver

看到打招呼了没?测试成功!

4. 架构设计

4.1. 开发架构

images/deve_arch_1.png

启动restserver时装载动态库对外提供服务,restserver是应用服务平台,动态库放所有应用服务逻辑。

images/deve_arch_2.png

完整的开发架构左边是第三方库/工具(其实大多数也是我的开源项目^_^),右边是public library(项目内公共模块)、rest server context(平台环境上下文模块)、restserver api(平台提供应用API模块)、rest service controler(服务控制器模块),中间从下到上、按调用顺序分别是main(启动入口层)、tcpdaemon(进程与通讯管理层)、tcpmain(HTTP通讯收发层)、process http request(HTTP应用处理接口层)和restful service application(RESTful应用逻辑层)。

第三方库/工具说明表:

库/工具项目名及版本 简介 说明 备注
list 链表函数库 负责链表数据结构实现 从Linux Kernel里挖出来的
LOGC v1.3.2 日志函数库 负责输出日志;已复制到restserver里 我的开源项目,传送门 开源中国github
fasterjson v1.1.8 JSON解析函数库 和DirectStruct配合,负责解析配置文件(JSON格式);需要自行安装 我的开源项目,传送门 开源中国github
DirectStruct v1.14.0 报文序列化/反序列化代码自动生成器 根据定义文件,自动生成调用fasterjson的源代码;改造开发时才需要安装 我的开源项目,传送门 开源中国github
tcpdaemon v1.5.0 TCP通讯服务端框架函数库 负责指定通讯服务端模型的进程、通讯管理;需要自行安装 我的开源项目,传送门 开源中国github
fasterhttp v1.5.0 HTTP解析函数库 负责接收、解析和发送HTTP通讯数据;需要自行安装 我的开源项目,传送门 开源中国github

模块/层说明表:

模块/层名 简介 说明
public library 项目内公共模块 本项目内公共宏、函数等代码资源
rest server context 平台环境上下文模块 包裹了平台内部流转的状态、数据、第三方库实例对象等,便于函数间调用
restserver api 平台提供应用API模块 平台内各模块功能的API包装层,提供给RESTful应用逻辑层调用,作为应用获取HTTP、RESTful等信息的接口
rest service controler 服务控制器模块 根据HTTP请求方法和URI分派调用对应RESTful服务入口的路由;用户可替换
main 启动入口层 初始化环境、装载配置文件等启动时处理
tcpdaemon 进程与通讯管理层 负责多进程+多路复用模型的进程、通讯管理;由第三方库tcpdaemon实现,静态库未libtcpdaemon.a,主要函数为tcpdaemon
tcpmain HTTP通讯收发层 作为tcpdaemon回调函数,当TCP事件发生时被调用,负责新连接到来、数据可读、数据可写、对端连接关闭时的处理
process http request HTTP应用处理接口层 作为平台与应用之间的调用层,负责装载应用动态库,调用动态库应用构造函数、应用析构函数、RESTful请求处理函数
restful service application RESTful应用逻辑层 应用实现的层,提供应用构造函数、应用析构函数、RESTful请求处理函数给平台调用,调用平台服务控制器模块,让其根据HTTP方法和URI分派到指定的RESTful服务逻辑入口

4.2. 过程架构

images/proc_arch_2.png

restserver启动后,装载配置文件,构造tcpdaemon通讯管理引擎参数,以多进程+多路复用模型调用tcpdaemon。

tcpdaemon创建TCP服务端侦听,创建多进程,创建多路复用环境,注册TCP侦听事件,进入事件处理主循环,当有事件发生时调用之前设置的回调函数tcpmain。

tcpmain分支不同的事件做相应处理:如果是TCP侦听事件,创建TCP会话和会话的HTTP环境,注册已连接会话结构到tcpdaemon,通知tcpdaemon注册TCP会话数据可读事件;如果是TCP会话数据可读事件,非堵塞接收HTTP报文,如果接收完整,调用ProcessHttpRequest,通知tcpdaemon改注册TCP会话数据可写事件;如果是TCP会话数据可读事件,非堵塞发送HTTP报文,如果发送完成,再判断是否需要保持连接,是就改注册TCP会话数据可读事件,否就通知关闭tcpdaemon触发关闭事件;如果是TCP连接关闭事件,清理HTTP环境和关闭TCP会话,注销已连接会话结构。

ProcessHttpRequest创建平台上下文环境,然后解析HTTP请求到平台上下文环境中,然后如果没有装载动态库则装载之,然后如果没有调用过动态库中的应用构造函数InitRestApplication则调用之,然后调用动态库中的RESTful请求处理函数CallRestApplication,然后如果是测试模式调用动态库中的应用析构函数CleanRestApplication及卸载动态库。

应用动态库的构造函数InitRestApplication创建RESTful服务控制器,装载RESTful服务路由表,最后设置RESTful服务控制器到restserver平台上下文环境中。

应用动态库的RESTful请求处理函数CallRestApplication从restserver平台上下文环境中取出RESTful服务控制器,最后让RESTful服务控制器分派服务处理入口。

应用动态库的细节函数CleanRestApplication从restserver平台上下文环境中取出RESTful服务控制器,最后销毁RESTful服务控制器。

RESTful服务控制器分派函数RSAPIDispatchRestServiceControler在RESTful路由表查询符合当前RESTful请求的服务,调用服务入口。

注意:restserver自带的RESTful服务控制器并不是必须的,你可以自己写一个替代自带版本。

5. 安装部署

5.1. 源码编译安装

restserver依赖我的另外几个开源项目,首先下载那几个开源项目源码编译安装。

5.1.1. 源码编译安装依赖项目

5.1.1.1. fasterjson

开源中国github下载解压源码或clone项目。

进入src目录编译链接

$ cd src
$ make -f makefile.Linux install

如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterjson/,库文件安装在$HOME/lib/libfasterjson.so。

5.1.1.2. tcpdaemon

开源中国github下载解压源码或clone项目。

进入src目录编译链接

$ cd src
$ make -f makefile.Linux install

如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/tcpdaemon/,库文件安装在$HOME/lib/libtcpdaemon.a。

5.1.1.3. fasterhttp

开源中国github下载解压源码或clone项目。

进入src目录编译链接

$ cd src
$ make -f makefile.Linux install

如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterhttp/,库文件安装在$HOME/lib/libfasterhttp.so。

5.1.2. 源码编译安装restserver

开源中国github下载解压源码或clone项目。

进入src目录编译链接

$ cd src
$ make -f makefile.Linux install

如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/restserver/,库文件安装在$HOME/lib/librestserver_api.so,可执行程序安装在$HOME/bin/restserver。

5.1.3. 源码编译安装restserver的应用示例example

进入example目录编译链接

$ cd src
$ make -f makefile.Linux install

如果没有报错则说明源码编译安装成功,应用示例动态库文件安装在$HOME/so/RS_hello.so、$HOME/so/RS_rsapi。

6. 开发应用

6.1. 应用和服务

从前面我们知道,可执行程序restserver启动后装载动态库实现业务逻辑对外服务,业务开发也就是开发一批服务,构建成一个应用,运行时restserver服务平台装载应用(动态库)即可。

应用动态库中必须存在三个函数,一个是动态库的应用构造函数InitRestApplication,第二个是RESTful请求处理函数CallRestApplication,最后一个是应用析构函数CleanRestApplication,分别用于服务平台restserver装载动态库时、RESTful服务到来时和卸载动态库时调用。由于restserver提供了服务控制器模块(也可以自行研发),在构造函数中根据预配置的RESTful方法和URI路由表创建一个服务控制器实例,当RESTful请求到来时,平台调用RESTful请求处理函数,函数执行该服务控制器分派函数,分派函数用当前RESTful请求方法和URI查询控制器路由表,找到RESTful服务入口函数,从入口进入业务逻辑,当然最后卸载动态库时调用析构函数中销毁之。

6.2. 一个通用代码模板

一般的,一个应用动态库有如下代码框架:

#include "restserver_api.h"

funcRestServiceEntry GET_hello;
int GET_hello( struct RestServerContext *ctx )
{
	char		response[1024] ;
	int		response_len ;
	
	int		nret = 0 ;
	
	/* 初始化临时缓冲区 */
	memset( response , 0x00 , sizeof(response) );
	response_len = 0 ;
	
	...
	
	/* 构造HTTP缓冲区 */
	nret = RSAPIFormatHttpResponse( ctx , response , response_len , NULL ) ;
	if( nret )
		return nret;
	
	return 0;
}
static struct RestServiceConfig		g_rest_services_config[] = {
		{ "GET" , "/hello" , GET_hello } ,
		{ "" , "" , NULL }
	} ;

funcInitRestApplication InitRestApplication;
int InitRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 创建RESTful服务控制器 */
	ctl = RSAPICreateRestServiceControler( g_rest_services_config ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_CREATE_RESTSERVICECONTROLER;
	
	/* 设置RESTful服务控制器到restserver平台上下文环境中 */
	RSAPISetUserData( ctx , ctl );
	
	return 0;
}

funcCallRestApplication CallRestApplication;
int CallRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	int				nret = 0 ;
	
	/* 从restserver平台上下文环境中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_GET_RESTSERVICECONTROLER;
	
	/* 让RESTful服务控制器分派服务处理入口 */
	nret = RSAPIDispatchRestServiceControler( ctl , ctx ) ;
	if( nret )
		return nret;
	
	return 0;
}

funcCleanRestApplication CleanRestApplication;
int CleanRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 从restserver平台上下文环境中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	
	/* 销毁RESTful服务控制器 */
	RSAPIDestroyRestServiceControler( ctl );
	
	return 0;
}

开发真正要做的,就是写RESTful服务函数实现业务逻辑,配置进RESTful控制器路由配置表g_rest_services_config。

在hello示例中,RESTful服务函数简单的把打招呼信息压入HTTP缓冲区

funcRestServiceEntry GET_hello;
int GET_hello( struct RestServerContext *ctx )
{
	char		response[1024] ;
	int		response_len ;
	
	int		nret = 0 ;
	
	/* 初始化临时缓冲区 */
	memset( response , 0x00 , sizeof(response) );
	response_len = 0 ;
	
	/* 填充hello信息 */
	response_len = snprintf( response , sizeof(response) , "Hello restserver\n" ) ;
	
	/* 构造HTTP响应缓冲区 */
	nret = RSAPIFormatHttpResponse( ctx , response , response_len , NULL ) ;
	if( nret )
		return nret;
	
	return 0;
}

注意:RSAPIFormatHttpResponse是restserver_api提供的构造HTTP响应缓冲区函数,第二个参数和第三个参数输入HTTP体数据和长度,第四个参数及以后是可变参数序列,类似snprintf的format机制,用于输入HTTP头(Content-length随HTTP体数据输入自动附加)。

注意:构建restserver应用时比如makefile,编译需要指定restserver_api头文件目录,链接需要指定librestserver_api.so库文件。示例hello自带的makefile是基于make工具mktpl2的依赖makefile,其无依赖版本是同目录里的makefile.Linux。

注意:针对大型项目而言,你应该把RESTful服务函数拆到一个个独立的.c文件,这样就要跟随自己的经验,创建头文件声明它们,构建时分别编译和最后一起链接。

注意:开发数据库应用时,一般会在应用动态库构造函数中加入打开数据库逻辑,析构函数中加入断开数据库逻辑,以便在RESTful服务函数里直接发送SQL。构造函数和析构函数里面还能加入其它需要初始化和销毁时操作的逻辑,平台保证会按时序完整执行。

在下一个示例rsapi中,RESTful服务函数演示了调用众多restserver_api函数获取RESTful信息,把它们都返回给前端

int GET_rsapi( struct RestServerContext *ctx )
{
	char		response[4096] ;
	int		response_len ;
	
	char		*method = NULL ;
	int		method_len ;
	char		*uri = NULL ;
	int		uri_len ;
	
	int		uri_paths_count ;
	int		uri_path_index ;
	char		*uri_path = NULL ;
	int		uri_path_len ;
	
	int		queries_count ;
	int		query_index ;
	char		*key = NULL ;
	int		key_len ;
	char		*value = NULL ;
	int		value_len ;
	
	int		nret = 0 ;
	
	/* 初始化临时缓冲区 */
	memset( response , 0x00 , sizeof(response) );
	response_len = 0 ;
	
	/* 获取HTTP方法 */
	method = RSAPIGetHttpMethodPtr( ctx , & method_len ) ;
	BUFPRINTF( response , response_len , "method[%.*s]\n" , method_len,method )
	
	/* 获取HTTP路径 */
	uri = RSAPIGetHttpUriPtr( ctx , & uri_len ) ;
	BUFPRINTF( response , response_len , "uri[%.*s]\n" , uri_len,uri )
	
	/* 获取已分解后的路径段 */
	uri_paths_count = RSAPIGetHttpUriPathsCount( ctx ) ;
	BUFPRINTF( response , response_len , "uri_paths_count[%d]\n" , uri_paths_count ) ;
	for( uri_path_index = 1 ; uri_path_index <= uri_paths_count ; uri_path_index++ )
	{
		uri_path = RSAPIGetHttpUriPathPtr( ctx , uri_path_index , & uri_path_len ) ;
		BUFPRINTF( response , response_len , "uri_path[%.*s]\n" , uri_path_len,uri_path )
	}
	
	/* 获取已分解后的参数段 */
	queries_count = RSAPIGetHttpUriQueriesCount( ctx ) ;
	BUFPRINTF( response , response_len , "queries_count[%d]\n" , queries_count ) ;
	for( query_index = 1 ; query_index <= queries_count ; query_index++ )
	{
		key = RSAPIGetHttpUriQueryKeyPtr( ctx , query_index , & key_len ) ;
		value = RSAPIGetHttpUriQueryValuePtr( ctx , query_index , & value_len ) ;
		BUFPRINTF( response , response_len , "query[%d][%.*s][%.*s]\n" , query_index , key_len,key , value_len,value ) ;
	}
	
	/* 构造HTTP缓冲区 */
	nret = RSAPIFormatHttpResponse( ctx , response , response_len , NULL ) ;
	if( nret )
		return nret;
	
	return 0;
}

注意:填充临时缓存区时使用了restserver_api提供的格式化字符串宏,简化了代码,但这些宏还比较初级,用户可以使用自己所拥有的更完善更成熟的字符串/缓冲区格式化库来代替之。

注意:如果你设计的接口规范中,HTTP体塞入JSON作为业务报文格式,你可以使用restserver自带的fasterjson+DirectStruct(前面安装依赖时没有提到)组合来帮助你实现JSON报文的快速序列化和反序列化,仅仅只需要调用一个函数即可,参阅restserver内部实现是如何读取配置文件,详细参见fasterjson和DirectStruct开源项目说明文档。

6.3. 自研RESTful服务控制器

如果对自带的RESTful服务控制器不满意,可以自行研发一个,在应用动态库中使用之,代码框架提供如下:

#include "restserver_api.h"

funcInitRestApplication InitRestApplication;
int InitRestApplication( struct RestServerContext *ctx )
{
	...

	return 0;
}

funcCallRestApplication CallRestApplication;
int CallRestApplication( struct RestServerContext *ctx )
{
	...

	return 0;
}

funcCleanRestApplication CleanRestApplication;
int CleanRestApplication( struct RestServerContext *ctx )
{
	...
	
	return 0;
}

注意:RESTful信息在平台上下文环境ctx中,可以通过restserver_api层函数访问之;HTTP信息在ctx深处,调用RSAPIGetHttpEnv传入ctx传出struct HttpEnv *http,然后使用fasterhttp函数库函数访问之,别忘了编译时包含fasterhttp.h。

7. 部署运行

假设现在一组RESTful服务函数包裹成的一个应用(动态库)已经构建完毕,你肯定亟不可待地想跑一把,你还需要学习运行前的最后一块内容:

7.1. 配置文件

restserver配置文件一般取名为"restserver.conf",当然你也可以命名成其它诸如"this_is_the_restserver_config.ini"。

{
	"log" :
	{
		"log_pathfilename" : "$HOME$/log/restserver.log" ,
		"log_level" : "DEBUG"
	} ,
	"server" :
	{
		"test_mode" : 1 ,
		"workers_count" : 2 ,
		"application_so_pathfilename" : "$HOME$/so/RS_hello.so"
	} ,
	"http" :
	{
		"listen_ip" : "0" ,
		"listen_port" : 7911 ,
		"domain" : "localhost:7911" ,
		"timeout_seconds" : 60 ,
		"headers_count_hardmax" : 128 ,
		"headers_len_hardmax" : 4096 ,
		"header_content_length_val_hardmax" : 1024000
	}
}

我们拿示例hello的配置文件来讲解。

restserver配置文件里面分三个段:log(日志配置)、server(进程和服务器配置)、http(HTTP和RESTful配置),至少v0.8.0是这样的。

配置项路径 简介 说明
log.log_pathfilename 日志文件名 可以配成相对路径或绝对路径,还支持内嵌环境变量"$...$"
log.log_level 日志等级 枚举空间:DEBUG、INFO、WARN、ERROR、FATAL
server.test_mode 是否是测试模式 1:测试模式
0:生产模式
当设置成测试模式时,每个RESTful请求前都会装载应用动态库,每个RESTful请求后都会卸载应用动态库,是不是特别适合调试?
server.workers_count 多进程+多路复用模型的进程数 决定了应用处理最多并发数,但不是通讯最大并发数,多路复用模型支持成千上万的通讯并发数
server.application_so_pathfilename 应用动态库文件名 格式同日志文件名
http.listen_ip HTTP或者说RESTful服务器侦听地址 根据网络规划和安全要求设置成合适的IP;"0"代表内外兼修
http.listen_port RESTful服务器侦听端口
http.domain 域名 参考其它HTTP服务器;客户端请求的域名必须和服务端设置的域名一致
http.timeout_seconds 超时时间 单位:秒
防止一些连接占着茅坑不拉屎
http.headers_count_hardmax HTTP报文头选项的最大数量 安全防御用
http.headers_len_hardmax HTTP报文头的最大长度 安全防御用
http.header_content_length_val_hardmax HTTP报文体的最大长度 安全防御用

7.2. 跑起来

其实就是指定某个配置文件运行restserver,restserver装载应用动态库,对外提供RESTful服务。

restserver命令行参数很简单

$ restserver
USAGE : restserver config_file

我把源码包中的配置文件复制到$HOME/etc/里改了改,就可以运行了

$ restserver etc/restserver.conf

此时命令行会卡住,可以开启另外一个窗口查看日志文件$HOME/log/restserver.log,再用curl发起一个HTTP请求测试一下

$ curl "http://localhost:7911/hello"
Hello restserver

注意:第一个"//"和"/"之间会被浏览器/curl取出来当作域名上送,restserver会根据配置文件匹配域名。 注意:第一个"/"后面的是URI,测试前请确保当前装载的应用动态库路由表里已预置。

好了,测试没问题的话,就用ctl+c中断运行。正式运行命令应该长这个样子

$ nohup restserver etc/restserver.conf &
$

8. 平台应用接口开发参考

8.1. 错误码宏

宏名 宏说明
RESTSERVER_ERROR_URI_FIRST_CHARACTER 当前HTTP请求的URI地址首字符必须是'/'
RESTSERVER_ERROR_TOO_MANY_HTTP_URI_PATHS 当前HTTP请求的URI的目录文件端数太多
RESTSERVER_ERROR_URI_FIRST_CHARACTER_IN_CONFIG 源码RESTful路由表的URI地址首字符必须是'/'
RESTSERVER_ERROR_TOO_MANY_HTTP_URI_PATHS_IN_CONFIG 源码RESTful路由表的URI的目录文件端数太多
RESTSERVER_ERROR_RESTSERVICE_ENTRY_RETURN RESTful服务入口函数返回处理失败
RESTSERVER_ERROR_HTTP_DOMAIN 当前HTTP请求的域名与配置文件中的不一致
RESTSERVER_ERROR_PLACEHOLDER_LACK_OF_CLOSE 配置文件中的目录路径中的环境变量占位符前后'$'不能匹配
RESTSERVER_ERROR_BUFFER_OVERFLOW 应用缓冲区溢出
RESTSERVER_ERROR_SOMETHING 其它错误
RESTSERVER_FATAL_CREATE_RESTSERVICECONTROLER 构造RESTful服务控制器失败
RESTSERVER_FATAL_GET_RESTSERVICECONTROLER 得到RESTful服务控制器失败
RESTSERVER_FATAL_ENV_VAR_NOT_FOUND 环境变量未找到
RESTSERVER_FATAL_SOMETHING 其它致命错误

8.2. 工具宏

8.2.1. 简单缓冲区格式化宏

8.2.1.1. STRNCMPSTRN

宏定义 #define STRNCMPSTRN(_str1_,_str1_len_,_cmp_,_str2_,_str2_len_) ( (_str1_len_) _cmp_ (_str2_len_) && STRNCMP( (_str1_) , _cmp_ , (_str2_) , (_str2_len_) ) )
宏说明 带长度的比较两个字符数组
输入参数 _str1_ : 字符数组1
_str1_len_ : 字符数组1长度
_cmp_ : 比较符
_str2_ : 字符数组2
_str2_len_ : 字符数组2长度
返回值 0 : 构造成功
大于0 : 字符数组1大
小于0 : 字符数组2大

示例

char	buf1[...] ;
char	buf2[...] ;
...
if( STRNCMPSTRN( buf1 , strlen(buf1) , == , buf2 , strlen(buf2) ) )
{
	...
}

8.2.1.2. STRNCMPSTR

宏定义 #define STRNCMPSTRN(_str1_,_str1_len_,_cmp_,_str2_) STRNCMPSTRN( (_str1_) , (_str1_len_) , _cmp_ , (_str2_) , (strlen(_str2_)) )
宏说明 带长度的比较两个字符数组,其中第二个字符数组自动计算长度
输入参数 _str1_ : 字符数组1
_str1_len_ : 字符数组1长度
_cmp_ : 比较符
_str2_ : 字符数组2
返回值 0 : 构造成功
大于0 : 字符数组1大
小于0 : 字符数组2大

示例

char	buf1[...] ;
char	buf2[...] ;
...
if( STRNCMPSTR( buf1 , strlen(buf1) , == , buf2 ) )
{
	...
}

8.2.1.3. STRNCMPRSTR

宏定义 #define STRNCMPRSTR(_str1_,_str1_len_,_cmp_,_const_str2_) STRNCMPSTRN( (_str1_) , (_str1_len_) , _cmp_ , (_literal_str2_) , (sizeof(_literal_str2_)-1) )
宏说明 带长度的比较两个字符数组,其中第二个字符数组为字面量
输入参数 _str1_ : 字符数组1
_str1_len_ : 字符数组1长度
_cmp_ : 比较符
_literal_str2_ : 字符数组2
返回值 0 : 构造成功
大于0 : 字符数组1大
小于0 : 字符数组2大

示例

char	buf1[...] ;
...
if( STRNCMPRSTR( buf1 , strlen(buf1) , == , "hello" ) )
{
	...
}

8.2.1.4. HTTP_NEWLINE

宏定义 #define HTTP_NEWLINE "\r\n"
宏说明 便于格式化HTTP时加入换行

8.2.1.5. HTML_NEWLINE

宏定义 #define HTML_NEWLINE "
"
宏说明 便于格式化HTML时加入小换行

8.2.1.6. HTML_RETURN_NEWLINE

宏定义 #define HTML_RETURN_NEWLINE "

"
宏说明 便于格式化HTML时加入大换行

8.2.1.7. BUFNPRINTF

宏定义 #define BUFNPRINTF(_buf_base_,_buf_size_,_str_len_,_format_,...) ...
宏说明 格式化字符串追加到缓冲区
输入参数 _buf_base_ : 缓冲区
_buf_size_ : 缓冲区大小
_str_len_: 缓冲区内有效字符串长度
_format_,... : 要追加的格式化串和参数集
返回值 (无)

示例

char	buf[...] ;
int	buf_len ;
int	count ;
...
BUFNPRINTF( buf , sizeof(buf) , buf_len , "count[%d]" , count )

注意:buf_len会自动累加和封顶。

8.2.1.8. BUFPRINTF

宏定义 #define BUFPRINTF(_buf_base_,_str_len_,_format_,...) BUFNPRINTF(_buf_base_,sizeof(_buf_base_),_str_len_,_format_,__VA_ARGS__)
宏说明 格式化字符串追加到缓冲区,不用给sizeof(_buf_base_)
输入参数 _buf_base_ : 缓冲区
_str_len_: 缓冲区内有效字符串长度
_format_,... : 要追加的格式化串和参数集
返回值 (无)

示例

char	buf[...] ;
int	buf_len ;
int	count ;
...
BUFPRINTF( buf , buf_len , "count[%d]" , count )

注意:buf_len会自动累加和封顶。

8.2.1.9. BUFNSTRCAT

宏定义 #define BUFNSTRCAT(buf_base,buf_size,str_len,cat_str) ...
宏说明 格式化字符串追加到缓冲区
输入参数 _buf_base_ : 缓冲区
_buf_size_ : 缓冲区大小
_str_len_: 缓冲区内有效字符串长度
_cat_str_,... : 要追加的字符串
返回值 (无)

示例

char	buf[...] ;
int	buf_len ;
...
BUFNSTRCAT( buf , sizeof(buf) , buf_len , "ok" )

注意:buf_len会自动累加和封顶。

8.2.1.10. BUFSTRCAT

宏定义 #define BUFSTRCAT(_buf_base_,_str_len_,_cat_str_) BUFNSTRCAT(_buf_base_,sizeof(_buf_base_),_str_len_,_cat_str_)
宏说明 格式化字符串追加到缓冲区
输入参数 _buf_base_ : 缓冲区
_str_len_: 缓冲区内有效字符串长度
_cat_str_,... : 要追加的字符串
返回值 (无)

示例

char	buf[...] ;
int	buf_len ;
...
BUFSTRCAT( buf , buf_len , "ok" )

注意:buf_len会自动累加和封顶。

8.3. 函数原型

8.3.1. 应用动态库函数原型

8.3.1.1. funcInitRestApplication

函数原型 typedef int funcInitRestApplication( struct RestServerContext *ctx );
函数说明 当应用动态库第一次装载时被调用
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 0 : 构造成功
非0 : 失败,具体失败原因见错误宏

8.3.1.2. funcCallRestApplication

函数原型 typedef int funcCallRestApplication( struct RestServerContext *ctx );
函数说明 当每次HTTP请求到来时被调用
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 0 : 构造成功
非0 : 失败,具体失败原因见错误宏

8.3.1.3. funcCleanRestApplication

函数原型 typedef int funcCleanRestApplication( struct RestServerContext *ctx );
函数说明 当应用动态库最后卸载时被调用
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 0 : 构造成功
非0 : 失败,具体失败原因见错误宏

8.4. API函数

8.4.1. 查询RESTful请求信息类

8.4.1.1. RSAPIGetHttpMethodPtr

函数原型 char *RSAPIGetHttpMethodPtr( struct RestServerContext *ctx , int *p_method_len );
函数说明 从平台上下文环境中得到当前HTTP请求的方法
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 int *p_method_len : 如果传入,返回时赋值为HTTP请求方法的长度
返回值 char * : HTTP请求方法;没有C字符串结束符,须按长度访问

8.4.1.2. RSAPIGetHttpUriPtr

函数原型 char *RSAPIGetHttpUriPtr( struct RestServerContext *ctx , int *p_uri_len );
函数说明 从平台上下文环境中得到当前HTTP请求的方法
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 int *p_method_len : 如果传入,返回时赋值为HTTP请求方法的长度
返回值 char * : HTTP请求方法;没有C字符串结束符,须按长度访问

8.4.1.3. RSAPIGetHttpUriPathsCount

函数原型 int RSAPIGetHttpUriPathsCount( struct RestServerContext *ctx );
函数说明 从平台上下文环境中得到当前HTTP请求URI的分解出来的目录文件数量
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 int : 分解出来的目录文件数量

8.4.1.4. RSAPIGetHttpUriPathPtr

函数原型 char *RSAPIGetHttpUriPathPtr( struct RestServerContext *ctx , int index , int *p_path_len );
函数说明 从平台上下文环境中得到当前HTTP请求URI的分解出来的某段目录文件名
输入参数 struct RestServerContext *ctx : 平台上下文环境< br />int index : 分解出来的目录文件名段序号;从1开始
int *p_path_len : 目录文件名长度
输出参数 (无)
返回值 char * : 分解出来的某段目录文件名;没有C字符串结束符,须按长度访问

8.4.1.5. RSAPIGetHttpUriQueriesCount

函数原型 int RSAPIGetHttpUriQueriesCount( struct RestServerContext *ctx );
函数说明 从平台上下文环境中得到当前HTTP请求URI的分解出来的参数数量
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 int : 分解出来的参数数量

8.4.1.6. RSAPIGetHttpUriQueryKeyPtr

函数原型 char *RSAPIGetHttpUriQueryKeyPtr( struct RestServerContext *ctx , int index , int *p_key_len );
函数说明 从平台上下文环境中得到当前HTTP请求URI的分解出来的某段参数名
输入参数 struct RestServerContext *ctx : 平台上下文环境< br />int index : 分解出来的参数段序号;从1开始
int *p_key_len : 参数名长度
输出参数 (无)
返回值 char * : 分解出来的某段参数名;没有C字符串结束符,须按长度访问

8.4.1.7. RSAPIGetHttpUriQueryValuePtr

函数原型 char *RSAPIGetHttpUriQueryValuePtr( struct RestServerContext *ctx , int index , int *p_value_len );
函数说明 从平台上下文环境中得到当前HTTP请求URI的分解出来的某段参数值
输入参数 struct RestServerContext *ctx : 平台上下文环境< br />int index : 分解出来的参数段序号;从1开始
int *p_key_len : 参数值长度
输出参数 (无)
返回值 char * : 分解出来的某段参数值;没有C字符串结束符,须按长度访问

8.4.1.8. RSAPIGetHttpRequestBodyPtr

函数原型 char *RSAPIGetHttpRequestBodyPtr( struct RestServerContext *ctx , int *p_body_len );
函数说明 从平台上下文环境中得到当前HTTP请求体
输入参数 struct RestServerContext *ctx : 平台上下文环境< br />int *p_body_len : HTTP请求体长度
输出参数 (无)
返回值 char * : HTTP请求体;没有C字符串结束符,须按长度访问

8.4.2. 构造RESTful响应信息类

8.4.2.1. RSAPIFormatHttpResponse

函数原型 int RSAPIFormatHttpResponse( struct RestServerContext *ctx , char *http_response_body , int http_response_body_len , char *http_header_format , ... );
函数说明 构造HTTP响应
输入参数 struct RestServerContext *ctx : 平台上下文环境
char *http_response_body : HTTP响应体
int http_response_body_len : HTTP响应体长度
char *http_header_format , ... : HTTP响应头;如果有多行的话,要用"\r\n"分隔
输出参数 (无)
返回值 0 : 构造成功
非0 : 失败,具体失败原因见错误宏

8.4.3. RESTful服务控制器类

8.4.3.1. RSAPICreateRestServiceControler

函数原型 struct RestServiceControler *RSAPICreateRestServiceControler( struct RestServiceConfig *config_array );
函数说明 用代码中配置的RESTful路由表,构造RESTful服务控制器
输入参数 struct RestServiceConfig *config_array : 在代码中配置的RESTful路由表;http_method是HTTP请求头方法,http_uri_paths_match是HTTP请求头URI,目录文件名可用"{}"通配,如"/books/{}",pfuncRestServiceEntry是RESTful服务入口函数
输出参数 (无)
返回值 struct RestServiceControler * : RESTful服务控制器

8.4.3.2. RSAPIDispatchRestServiceControler

函数原型 int RSAPIDispatchRestServiceControler( struct RestServiceControler *ctl , struct RestServerContext *ctx );
函数说明 用当前HTTP请求,查询RESTful服务控制器,分派调用RESTful服务入口函数
输入参数 struct RestServiceControler *ctl : RESTful服务控制器
struct RestServerContext *ctx : 平台上下文环境,内有当前HTTP请求
输出参数 (无)
返回值 0 : 分派成功
非0 : 失败,具体失败原因见错误宏

8.4.3.3. RSAPIDestroyRestServiceControler

函数原型 void RSAPIDestroyRestServiceControler( struct RestServiceControler *ctl );
函数说明 销毁RESTful服务控制器
输入参数 struct RestServiceControler *ctl : RESTful服务控制器
输出参数 (无)
返回值 (无)

8.4.4. 其它类

8.4.4.1. RSAPIGetHttpEnv

函数原型 struct HttpEnv *RSAPIGetHttpEnv( struct RestServerContext *ctx );
函数说明 从平台上下文环境中得到HTTP环境,后续可以使用fasterhttp库操作该环境,编译时包含其头文件,链接时包含其库文件
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 HTTP环境

8.4.4.2. RSAPISetUserData

函数原型 void RSAPISetUserData( struct RestServerContext *ctx , void *user_data );
函数说明 设置用户自定义变量到平台上下文环境中
输入参数 struct RestServerContext *ctx : 平台上下文环境
void *user_data : 用户自定义变量地址
输出参数 (无)
返回值 (无)

8.4.4.3. RSAPIGetUserData

函数原型 void *RSAPIGetUserData( struct RestServerContext *ctx );
函数说明 从平台上下文环境中拿出某个用户自定义变量
输入参数 struct RestServerContext *ctx : 平台上下文环境
输出参数 (无)
返回值 viud * : 用户自定义变量地址

8.5. 综合示例

自带示例源码rsapi很好的演示了平台应用接口的使用,源码目录在demo/rsapi/rsapi.c

#include "restserver_api.h"

funcRestServiceEntry GET_hello;
int GET_hello( struct RestServerContext *ctx )
{
	char		response[4096] ;
	int		response_len ;
	
	char		*method = NULL ;
	int		method_len ;
	char		*uri = NULL ;
	int		uri_len ;
	
	int		uri_paths_count ;
	int		uri_path_index ;
	char		*uri_path = NULL ;
	int		uri_path_len ;
	
	int		queries_count ;
	int		query_index ;
	char		*key = NULL ;
	int		key_len ;
	char		*value = NULL ;
	int		value_len ;
	
	int		nret = 0 ;
	
	/* 初始化临时缓冲区 */
	memset( response , 0x00 , sizeof(response) );
	response_len = 0 ;
	
	/* 获取HTTP方法 */
	method = RSAPIGetHttpMethodPtr( ctx , & method_len ) ;
	BUFPRINTF( response , response_len , "method[%.*s]\n" , method_len,method )
	
	/* 获取HTTP路径 */
	uri = RSAPIGetHttpUriPtr( ctx , & uri_len ) ;
	BUFPRINTF( response , response_len , "uri[%.*s]\n" , uri_len,uri )
	
	/* 获取已分解后的路径段 */
	uri_paths_count = RSAPIGetHttpUriPathsCount( ctx ) ;
	BUFPRINTF( response , response_len , "uri_paths_count[%d]\n" , uri_paths_count ) ;
	for( uri_path_index = 1 ; uri_path_index <= uri_paths_count ; uri_path_index++ )
	{
		uri_path = RSAPIGetHttpUriPathPtr( ctx , uri_path_index , & uri_path_len ) ;
		BUFPRINTF( response , response_len , "uri_path[%.*s]\n" , uri_path_len,uri_path )
	}
	
	/* 获取已分解后的参数段 */
	queries_count = RSAPIGetHttpUriQueriesCount( ctx ) ;
	BUFPRINTF( response , response_len , "queries_count[%d]\n" , queries_count ) ;
	for( query_index = 1 ; query_index <= queries_count ; query_index++ )
	{
		key = RSAPIGetHttpUriQueryKeyPtr( ctx , query_index , & key_len ) ;
		value = RSAPIGetHttpUriQueryValuePtr( ctx , query_index , & value_len ) ;
		BUFPRINTF( response , response_len , "query[%d][%.*s][%.*s]\n" , query_index , key_len,key , value_len,value ) ;
	}
	
	/* 构造HTTP缓冲区 */
	nret = RSAPIFormatHttpResponse( ctx , response , response_len , NULL ) ;
	if( nret )
		return nret;
	
	return 0;
}

/* RESTful服务控制器 服务配置区 */
static struct RestServiceConfig		g_rest_services_config[] = {
		{ "GET" , "/rsapi" , GET_hello } ,
		{ "" , "" , NULL }
	} ;

funcInitRestApplication InitRestApplication;
int InitRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 创建RESTful服务控制器 */
	ctl = RSAPICreateRestServiceControler( g_rest_services_config ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_CREATE_RESTSERVICECONTROLER;
	
	/* 设置RESTful服务控制器到restserver平台上下文环境中 */
	RSAPISetUserData( ctx , ctl );
	
	return 0;
}

funcCallRestApplication CallRestApplication;
int CallRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	int				nret = 0 ;
	
	/* 从restserver平台上下文环境中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	if( ctl == NULL )
		return RESTSERVER_FATAL_GET_RESTSERVICECONTROLER;
	
	/* 让RESTful服务控制器分派服务处理入口 */
	nret = RSAPIDispatchRestServiceControler( ctl , ctx ) ;
	if( nret )
		return nret;
	
	return 0;
}

funcCleanRestApplication CleanRestApplication;
int CleanRestApplication( struct RestServerContext *ctx )
{
	struct RestServiceControler	*ctl = NULL ;
	
	/* 从restserver平台上下文环境中取出RESTful服务控制器 */
	ctl = RSAPIGetUserData( ctx ) ;
	
	/* 销毁RESTful服务控制器 */
	RSAPIDestroyRestServiceControler( ctl );
	
	return 0;
}

9. 性能压测

restserver性能如何呢?和Tomcat做个性能比赛。

硬件环境 >>>

CPU: Intel Core i5-7500 3.40GHz 3.40GHz

内存: 1 GB

硬盘: ST1000DM010

软件环境 >>>

操作系统:外部是Windows 10(64bits),装了Vmware,虚机里面是Red Hat Enterprise Linux Server release 7.4 (Maipo)

JDK: openjdk version "1.8.0_131"

restserver: restserver v0.8.0

Tomcat: apache-tomcat-10.0.0-M1

比赛规则 >>>

分别开发restserver和Tomcat的性能测试应用,restserver应用源码在benchmark/restserver/press/,Tomcat应用源码在benchmark/Tomcat/TestAgainstRestServer/,尽量单次HTTP载荷差不多。

并发100,总共10万次HTTP请求。先交替做十次预热,然后交替做十次压测。

取restserver正式第一次输出

$ ab -c 100 -n 100000 -k "http://localhost:7911/TestAgainstRestServer/hello"
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            7911

Document Path:          /TestAgainstRestServer/hello
Document Length:        17 bytes

Concurrency Level:      100
Time taken for tests:   0.647 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    100000
Total transferred:      19300000 bytes
HTML transferred:       1700000 bytes
Requests per second:    154499.81 [#/sec] (mean)
Time per request:       0.647 [ms] (mean)
Time per request:       0.006 [ms] (mean, across all concurrent requests)
Transfer rate:          29119.59 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       3
Processing:     0    1   0.6      1      12
Waiting:        0    1   0.6      1      12
Total:          0    1   0.6      1      12

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      1
  99%      2
 100%     12 (longest request)

再取Tomcat第一次输出

$ ab -c 100 -n 100000 -k "http://192.168.6.74:8080/TestAgainstRestServer/hello"
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.6.74 (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        
Server Hostname:        192.168.6.74
Server Port:            8080

Document Path:          /TestAgainstRestServer/hello
Document Length:        17 bytes

Concurrency Level:      100
Time taken for tests:   1.765 seconds
Complete requests:      100000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    99048
Total transferred:      17772392 bytes
HTML transferred:       1700000 bytes
Requests per second:    56662.49 [#/sec] (mean)
Time per request:       1.765 [ms] (mean)
Time per request:       0.018 [ms] (mean, across all concurrent requests)
Transfer rate:          9834.26 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       3
Processing:     0    2   1.4      1      37
Waiting:        0    2   1.4      1      37
Total:          0    2   1.4      1      37

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      2
  75%      2
  80%      2
  90%      3
  95%      4
  98%      5
  99%      5
 100%     37 (longest request)

总传输字节数restserver比Tomcat大一点。

下面是每秒请求量吞吐数据:

序号 restserver每秒请求量吞吐 Tomcat每秒请求量吞吐
1 154499 56662
2 171290 56355
3 151884 58522
4 159883 55684
5 157284 57360
6 153655 56929
7 155253 56602
8 157085 55785
9 153904 57366
10 154064 55273

曲线图我就不画了,很明显,restserver每秒请求量吞吐达到Tomcat的三倍。

下面是请求平均延迟数据:

序号 restserver请求平均延迟 Tomcat请求平均延迟
1 0.006 0.018
2 0.006 0.018
3 0.007 0.017
4 0.006 0.018
5 0.006 0.017
6 0.007 0.018
7 0.006 0.018
8 0.006 0.018
9 0.006 0.017
10 0.006 0.018

曲线图也不画了,很明显,restserver请求平均延迟只有Tomcat的1/3。

结论:restserver在性能上完全碾压Tomcat!

10. 最后

restserver,一个小巧、高效、低耗的C技术栈的RESTful应用服务平台。

欢迎使用restserver,如果你使用中碰到了问题请告诉我,谢谢 ^_^

源码托管地址 : 开源中国github

关于作者:厉华,左手C,右手JAVA,写过小到性能卓越方便快捷的日志库、HTTP解析器、日志采集器等,大到交易平台/中间件等,分布式系统实践者,容器技术专研者,目前在某城商行负责基础架构。

通过邮箱可以联系我 : 网易Gmail

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

一个轻量级、高性能、耗资源极少的RESTful应用服务平台 展开 收起
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
C
1
https://gitee.com/calvinwilliams/restserver.git
git@gitee.com:calvinwilliams/restserver.git
calvinwilliams
restserver
restserver
release

搜索帮助