# restserver
**Repository Path**: calvinwilliams/restserver
## Basic Information
- **Project Name**: restserver
- **Description**: 一个轻量级、高性能、耗资源极少的RESTful应用服务平台
- **Primary Language**: C
- **License**: Apache-2.0
- **Default Branch**: release
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 111
- **Forks**: 43
- **Created**: 2020-02-19
- **Last Updated**: 2024-12-08
## Categories & Tags
**Categories**: restful
**Tags**: None
## README
一个小巧、高效、低耗的C技术栈的RESTful应用服务平台(restserver)
==========================================================
- [一个小巧、高效、低耗的C技术栈的RESTful应用服务平台(restserver)](#%e4%b8%80%e4%b8%aa%e5%b0%8f%e5%b7%a7%e9%ab%98%e6%95%88%e4%bd%8e%e8%80%97%e7%9a%84c%e6%8a%80%e6%9c%af%e6%a0%88%e7%9a%84restful%e5%ba%94%e7%94%a8%e6%9c%8d%e5%8a%a1%e5%b9%b3%e5%8f%b0restserver)
- [1. 诞生](#1-%e8%af%9e%e7%94%9f)
- [2. 简介](#2-%e7%ae%80%e4%bb%8b)
- [3. "Hello world"](#3-%22hello-world%22)
- [4. 架构设计](#4-%e6%9e%b6%e6%9e%84%e8%ae%be%e8%ae%a1)
- [4.1. 开发架构](#41-%e5%bc%80%e5%8f%91%e6%9e%b6%e6%9e%84)
- [4.2. 过程架构](#42-%e8%bf%87%e7%a8%8b%e6%9e%b6%e6%9e%84)
- [5. 安装部署](#5-%e5%ae%89%e8%a3%85%e9%83%a8%e7%bd%b2)
- [5.1. 源码编译安装](#51-%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91%e5%ae%89%e8%a3%85)
- [5.1.1. 源码编译安装依赖项目](#511-%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91%e5%ae%89%e8%a3%85%e4%be%9d%e8%b5%96%e9%a1%b9%e7%9b%ae)
- [5.1.1.1. fasterjson](#5111-fasterjson)
- [5.1.1.2. tcpdaemon](#5112-tcpdaemon)
- [5.1.1.3. fasterhttp](#5113-fasterhttp)
- [5.1.2. 源码编译安装restserver](#512-%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91%e5%ae%89%e8%a3%85restserver)
- [5.1.3. 源码编译安装restserver的应用示例example](#513-%e6%ba%90%e7%a0%81%e7%bc%96%e8%af%91%e5%ae%89%e8%a3%85restserver%e7%9a%84%e5%ba%94%e7%94%a8%e7%a4%ba%e4%be%8bexample)
- [6. 开发应用](#6-%e5%bc%80%e5%8f%91%e5%ba%94%e7%94%a8)
- [6.1. 应用和服务](#61-%e5%ba%94%e7%94%a8%e5%92%8c%e6%9c%8d%e5%8a%a1)
- [6.2. 一个通用代码模板](#62-%e4%b8%80%e4%b8%aa%e9%80%9a%e7%94%a8%e4%bb%a3%e7%a0%81%e6%a8%a1%e6%9d%bf)
- [6.3. 自研RESTful服务控制器](#63-%e8%87%aa%e7%a0%94restful%e6%9c%8d%e5%8a%a1%e6%8e%a7%e5%88%b6%e5%99%a8)
- [7. 部署运行](#7-%e9%83%a8%e7%bd%b2%e8%bf%90%e8%a1%8c)
- [7.1. 配置文件](#71-%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6)
- [7.2. 跑起来](#72-%e8%b7%91%e8%b5%b7%e6%9d%a5)
- [8. 平台应用接口开发参考](#8-%e5%b9%b3%e5%8f%b0%e5%ba%94%e7%94%a8%e6%8e%a5%e5%8f%a3%e5%bc%80%e5%8f%91%e5%8f%82%e8%80%83)
- [8.1. 错误码宏](#81-%e9%94%99%e8%af%af%e7%a0%81%e5%ae%8f)
- [8.2. 工具宏](#82-%e5%b7%a5%e5%85%b7%e5%ae%8f)
- [8.2.1. 简单缓冲区格式化宏](#821-%e7%ae%80%e5%8d%95%e7%bc%93%e5%86%b2%e5%8c%ba%e6%a0%bc%e5%bc%8f%e5%8c%96%e5%ae%8f)
- [8.2.1.1. STRNCMPSTRN](#8211-strncmpstrn)
- [8.2.1.2. STRNCMPSTR](#8212-strncmpstr)
- [8.2.1.3. STRNCMPRSTR](#8213-strncmprstr)
- [8.2.1.4. HTTP_NEWLINE](#8214-httpnewline)
- [8.2.1.5. HTML_NEWLINE](#8215-htmlnewline)
- [8.2.1.6. HTML_RETURN_NEWLINE](#8216-htmlreturnnewline)
- [8.2.1.7. BUFNPRINTF](#8217-bufnprintf)
- [8.2.1.8. BUFPRINTF](#8218-bufprintf)
- [8.2.1.9. BUFNSTRCAT](#8219-bufnstrcat)
- [8.2.1.10. BUFSTRCAT](#82110-bufstrcat)
- [8.3. 函数原型](#83-%e5%87%bd%e6%95%b0%e5%8e%9f%e5%9e%8b)
- [8.3.1. 应用动态库函数原型](#831-%e5%ba%94%e7%94%a8%e5%8a%a8%e6%80%81%e5%ba%93%e5%87%bd%e6%95%b0%e5%8e%9f%e5%9e%8b)
- [8.3.1.1. funcInitRestApplication](#8311-funcinitrestapplication)
- [8.3.1.2. funcCallRestApplication](#8312-funccallrestapplication)
- [8.3.1.3. funcCleanRestApplication](#8313-funccleanrestapplication)
- [8.4. API函数](#84-api%e5%87%bd%e6%95%b0)
- [8.4.1. 查询RESTful请求信息类](#841-%e6%9f%a5%e8%af%a2restful%e8%af%b7%e6%b1%82%e4%bf%a1%e6%81%af%e7%b1%bb)
- [8.4.1.1. RSAPIGetHttpMethodPtr](#8411-rsapigethttpmethodptr)
- [8.4.1.2. RSAPIGetHttpUriPtr](#8412-rsapigethttpuriptr)
- [8.4.1.3. RSAPIGetHttpUriPathsCount](#8413-rsapigethttpuripathscount)
- [8.4.1.4. RSAPIGetHttpUriPathPtr](#8414-rsapigethttpuripathptr)
- [8.4.1.5. RSAPIGetHttpUriQueriesCount](#8415-rsapigethttpuriqueriescount)
- [8.4.1.6. RSAPIGetHttpUriQueryKeyPtr](#8416-rsapigethttpuriquerykeyptr)
- [8.4.1.7. RSAPIGetHttpUriQueryValuePtr](#8417-rsapigethttpuriqueryvalueptr)
- [8.4.1.8. RSAPIGetHttpRequestBodyPtr](#8418-rsapigethttprequestbodyptr)
- [8.4.2. 构造RESTful响应信息类](#842-%e6%9e%84%e9%80%a0restful%e5%93%8d%e5%ba%94%e4%bf%a1%e6%81%af%e7%b1%bb)
- [8.4.2.1. RSAPIFormatHttpResponse](#8421-rsapiformathttpresponse)
- [8.4.3. RESTful服务控制器类](#843-restful%e6%9c%8d%e5%8a%a1%e6%8e%a7%e5%88%b6%e5%99%a8%e7%b1%bb)
- [8.4.3.1. RSAPICreateRestServiceControler](#8431-rsapicreaterestservicecontroler)
- [8.4.3.2. RSAPIDispatchRestServiceControler](#8432-rsapidispatchrestservicecontroler)
- [8.4.3.3. RSAPIDestroyRestServiceControler](#8433-rsapidestroyrestservicecontroler)
- [8.4.4. 其它类](#844-%e5%85%b6%e5%ae%83%e7%b1%bb)
- [8.4.4.1. RSAPIGetHttpEnv](#8441-rsapigethttpenv)
- [8.4.4.2. RSAPISetUserData](#8442-rsapisetuserdata)
- [8.4.4.3. RSAPIGetUserData](#8443-rsapigetuserdata)
- [8.5. 综合示例](#85-%e7%bb%bc%e5%90%88%e7%a4%ba%e4%be%8b)
- [9. 性能压测](#9-%e6%80%a7%e8%83%bd%e5%8e%8b%e6%b5%8b)
- [10. 最后](#10-%e6%9c%80%e5%90%8e)
# 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_hello`。`Hello`服务处理逻辑为填充临时缓冲区,最后构造HTTP响应缓冲区。
运行过程:
1. 先打开第一个窗口,启动restserver
```
$ cd $HOME
$ restserver src/restserver/example/hello/restserver.conf
```
注意:指定的配置文件路径根据你放源码的目录按需调整。
注意:生产运行需要用nohup放置后台跑。
2. 再打开第二个窗口,用curl发起HTTP请求
测试示例`Hello`的脚本是`src/restserver/example/hello/hello.sh`,也可以直接执行,里面就一行
```
$ 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应用逻辑层)。
第三方库/工具说明表:
| 库/工具项目名及版本 | 简介 | 说明 | 备注 |
| --- | --- | --- | --- |
| list | 链表函数库 | 负责链表数据结构实现 | 从Linux Kernel里挖出来的 |
| LOGC v1.3.2 | 日志函数库 | 负责输出日志;已复制到restserver里 | 我的开源项目,传送门 [开源中国](https://gitee.com/calvinwilliams/iLOG3)、[github](https://github.com/calvinwilliams/iLOG3) |
| fasterjson v1.1.8 | JSON解析函数库 | 和DirectStruct配合,负责解析配置文件(JSON格式);需要自行安装 | 我的开源项目,传送门 [开源中国](https://gitee.com/calvinwilliams/fasterjson)、[github](https://github.com/calvinwilliams/fasterjson) |
| DirectStruct v1.14.0 | 报文序列化/反序列化代码自动生成器 | 根据定义文件,自动生成调用fasterjson的源代码;改造开发时才需要安装 | 我的开源项目,传送门 [开源中国](https://gitee.com/calvinwilliams/DirectStruct)、[github](https://github.com/calvinwilliams/DirectStruct) |
| tcpdaemon v1.5.0 | TCP通讯服务端框架函数库 | 负责指定通讯服务端模型的进程、通讯管理;需要自行安装 | 我的开源项目,传送门 [开源中国](https://gitee.com/calvinwilliams/tcpdaemon)、[github](https://github.com/calvinwilliams/tcpdaemon) |
| fasterhttp v1.5.0 | HTTP解析函数库 | 负责接收、解析和发送HTTP通讯数据;需要自行安装 | 我的开源项目,传送门 [开源中国](https://gitee.com/calvinwilliams/fasterhttp)、[github](https://github.com/calvinwilliams/fasterhttp) |
模块/层说明表:
| 模块/层名 | 简介 | 说明 |
| --- | --- | -- |
| 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. 过程架构

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
从[开源中国](https://gitee.com/calvinwilliams/fasterjson)或[github](https://github.com/calvinwilliams/fasterjson)下载解压源码或clone项目。
进入src目录编译链接
```
$ cd src
$ make -f makefile.Linux install
```
如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterjson/,库文件安装在$HOME/lib/libfasterjson.so。
#### 5.1.1.2. tcpdaemon
从[开源中国](https://gitee.com/calvinwilliams/tcpdaemon)或[github](https://github.com/calvinwilliams/tcpdaemon)下载解压源码或clone项目。
进入src目录编译链接
```
$ cd src
$ make -f makefile.Linux install
```
如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/tcpdaemon/,库文件安装在$HOME/lib/libtcpdaemon.a。
#### 5.1.1.3. fasterhttp
从[开源中国](https://gitee.com/calvinwilliams/fasterhttp)或[github](https://github.com/calvinwilliams/fasterhttp)下载解压源码或clone项目。
进入src目录编译链接
```
$ cd src
$ make -f makefile.Linux install
```
如果没有报错则说明源码编译安装成功,头文件安装在$HOME/include/fasterhttp/,库文件安装在$HOME/lib/libfasterhttp.so。
### 5.1.2. 源码编译安装restserver
从[开源中国](https://gitee.com/calvinwilliams/restserver)或[github](https://github.com/calvinwilliams/restserver)下载解压源码或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 "