在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:restserver开源软件地址:https://gitee.com/calvinwilliams/restserver开源软件介绍:一个小巧、高效、低耗的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)
3. "Hello world"上一节功能特性里有说到应用服务平台(可执行程序)restserver启动后装载应用(动态库)工作,因为restserver比Apache、Tomcat多封装了一层RESTful,所以应用要实现的内容就很少了,这有助于减少应用开发量,也尽可能瘦化应用代码结构,简洁的就是最好的。 本节Hello示例代码在源码路径 只有一个源文件 #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请求是 运行过程:
$ cd $HOME$ restserver src/restserver/example/hello/restserver.conf 注意:指定的配置文件路径根据你放源码的目录按需调整。注意:生产运行需要用nohup放置后台跑。
测试示例 $ curl "http://localhost:7911/hello"Hello restserver 看到打招呼了没?测试成功! 4. 架构设计4.1. 开发架构启动restserver时装载动态库对外提供服务,restserver是应用服务平台,动态库放所有应用服务逻辑。 完整的开发架构左边是第三方库/工具(其实大多数也是我的开源项目^_^),右边是public library(项目内公共模块)、rest server context(平台环境上下文模块)、restserver api(平台提供应用API模块)、rest service controler(服务控制器模块),中间从下到上、按调用顺序分别是main(启动入口层)、tcpdaemon(进程与通讯管理层)、tcpmain(HTTP通讯收发层)、process http request(HTTP应用处理接口层)和restful service application(RESTful应用逻辑层)。 第三方库/工具说明表:
模块/层说明表:
4.2. 过程架构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进入src目录编译链接 $ cd src$ make -f makefile.Linux install 如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterjson/,库文件安装在$HOME/lib/libfasterjson.so。 5.1.1.2. tcpdaemon进入src目录编译链接 $ cd src$ make -f makefile.Linux install 如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/tcpdaemon/,库文件安装在$HOME/lib/libtcpdaemon.a。 5.1.1.3. fasterhttp进入src目录编译链接 $ cd src$ make -f makefile.Linux install 如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterhttp/,库文件安装在$HOME/lib/libfasterhttp.so。 5.1.2. 源码编译安装restserver进入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是这样的。
7.2. 跑起来其实就是指定某个配置文件运行restserver,restserver装载应用动态库,对外提供RESTful服务。 restserver命令行参数很简单 $ restserverUSAGE : 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. 错误码宏
8.2. 工具宏8.2.1. 简单缓冲区格式化宏8.2.1.1. STRNCMPSTRN
示例 char buf1[...] ;char buf2[...] ;...if( STRNCMPSTRN( buf1 , strlen(buf1) , == , buf2 , strlen(buf2) ) ){ ...} 8.2.1.2. STRNCMPSTR
示例 char buf1[...] ;char buf2[...] ;...if( STRNCMPSTR( buf1 , strlen(buf1) , == , buf2 ) ){ ...} 8.2.1.3. STRNCMPRSTR
示例 char buf1[...] ;...if( STRNCMPRSTR( buf1 , strlen(buf1) , == , "hello" ) ){ ...} 8.2.1.4. HTTP_NEWLINE
8.2.1.5. HTML_NEWLINE
8.2.1.6. HTML_RETURN_NEWLINE
8.2.1.7. BUFNPRINTF
示例 char buf[...] ;int buf_len ;int count ;...BUFNPRINTF( buf , sizeof(buf) , buf_len , "count[%d]" , count ) 注意:buf_len会自动累加和封顶。 8.2.1.8. BUFPRINTF
示例 char buf[...] ;int buf_len ;int count ;...BUFPRINTF( buf , buf_len , "count[%d]" , count ) 注意:buf_len会自动累加和封顶。 8.2.1.9. BUFNSTRCAT
示例 char buf[...] ;int buf_len ;...BUFNSTRCAT( buf , sizeof(buf) , buf_len , "ok" ) 注意:buf_len会自动累加和封顶。 8.2.1.10. BUFSTRCAT
示例 char buf[...] ;int buf_len ;...BUFSTRCAT( buf , buf_len , "ok" ) 注意:buf_len会自动累加和封顶。 8.3. 函数原型8.3.1. 应用动态库函数原型8.3.1.1. funcInitRestApplication
8.3.1.2. funcCallRestApplication
8.3.1.3. funcCleanRestApplication
8.4. API函数8.4.1. 查询RESTful请求信息类8.4.1.1. RSAPIGetHttpMethodPtr
全部评论
专题导读
上一篇:manaphp: ManaPHP 秉承 "普及PHP协程, 促进PHP发展" 的理念而创造,采用Swo ...发布时间:2022-03-23下一篇:Resty: sample restful framework发布时间:2022-03-23热门推荐
热门话题
阅读排行榜
|
请发表评论