HTTPD
http parser哪家强?
首先介绍下目前市面上比较知名的2种C实现的http parser :
- h2o的picohttp;
- nodejs实现http_parser;
在cf框架未成形之初! 作者为了测试简单与方便, 自己用string库封装了基础的http server. 这可以在最初的提交代码找到.
当开发进展到底层代码封装完毕后, 所以就想到去开发者交友网站进行咨询了解.
目前需求为对线性字符串匹配与header头部start与end poosition定位, 然后创建指定大小table组装http content.
然后针对性的进行数据解析, 判断方法类型、打上数据类型标签, 最后返回给使用者.
在明确上述需求后, 经过一些简单调研与测试便选择了h2o的http parser实现.
为什么选择pico?
原因:
- pico的头文件与实现代码就2个文件, 使用也非常方便, 编译完成后就可以开始使用.
- 实现简单并且通俗易懂, 非常容易上手;
- pico性能确实高;
当初简单对pico与http_parser进行过对比, 在不开启sse4的情况下至少快2倍左右。有兴趣可以自行测试.
pico的代码我放在luaclib内, 封装后使用makefile编译lua可用动态库. 默认没开启sse4, 为了照顾一些同学机器不支持sse4指令集的情况, 省却了改makefile的麻烦 :)
中间件设计
众所周知, 中间件设计的目的是为了授权验证、请求准入等需求设计而来的. 在大多数http server框架中都会增加类似设计.
cf为此分离除了before函数专门用来处理httpd service使用, 避免冗余无用的参数验证等代码与业务逻辑耦合在一起.
同时, 提供了http库来体现before回调函数应该如何工作. 在使用者熟练之后可以使用cf很轻松的写出高性能api service.
httpd 使用
下面着重为大家介绍httpd的使用与相关函数原型, 有能力的同学也可以直接阅读源码进行学习:
local httpd = require "httpd"
使用之前先导入httpd库
httpd:new()
使用new方法创建一个httpd对象.(这没什么好说的)
此方法返回一个httpd对象;
httpd:timeout(number)
timeout方法设置每个http请求的connection最大保持时间, 默认为15秒;
此方法返回值为nil;
httpd:server_name(string)
server_name方法的参数是一个字符串类型的参数, 这个方法设置cf的http response头部Server字段的值;
此方法返回值为nil;
httpd:max_path_size(number)
max_path_size方法设置最大path路径长度, 超过number大小的path都会被禁止; 默认长度为1024;
此方法返回值为nil;
httpd:max_body_size(number)
max_body_size方法设置最大body长度, 超过number大小的body都会被禁止; 默认长度为1024 * 1024;
此方法返回值为nil;
httpd:max_header_size(number)
max_header_size方法设置最大body长度, 超过number大小的header都会被禁止; 默认长度为65535;
此方法返回值为nil;
httpd:before(function)
before方法设置每个请求在被业务函数处理之前, 优先被function处理.
此方法需要配合http库进行使用, http可以为此设计了专门的API来语义化中间件设计. 提升代码可读性.
这个方法一般可用于设计业务中间件、准入认证、行为收集等等; 具体使用方法参见main.lua示例.
local http = require "http"
app:before(function(content)
if content.METHOD == 'GET' then
return http.ok()
end
return http.throw(401, '<h1>未授权</h1>')
end)
需要注意的是: 只要你注入了funtion, 即使function内部什么都不做也会返回401.
此方法返回值为nil;
httpd:ws(route, handle)
ws方法为使用者注入http -> websocket类型的路由升级.
注册此方法后, httpd在检测到当前路由类型为ws类型后将会为检查请求头部(header), 错误的头部将会被判断并返回错误码.
首先被回调的是注册ws路由的class ctor方法; 这个方法将会有一个opt参数(table类型). ws对象将会被注入其中.
具体使用示例, 请参考script/ws.lua
.
httpd:use(route, handle)
httpd:api(route, handle)
use/api方法为route注入路由处理函数, handle即可以是一个函数, 也可以是一个lua table(class).
注意:
- 无关乎handle的类型, handle的返回值必须为string类型作为body返回给客户端;
- api的content-type为Applicaton/json; use则为:text/html;
- http库不可在此handle内部使用, 否则会出现异常;
如果注入的handle为table:
① 必须使用cf框架内置的lua class语法进行设计;
② new方法会传入http request content(注意: 构造函数异常将会引起路由对应的方法无法被正确调用);
③ get/post/put方法会根据用户请求类型(method)被回调(无参数), 如未实现对应方法将会有框架返回对应状态码;
如果注入的handle为function:
cf会为此function注入当前的http request content, 内部包含所有请求上下文与参数;
handle返回值必须为一个string类型的值, 否则将可能出现警告或返回空值;
方法返回值为nil
httpd:group(type, prefix, handles)
group方法提供了一种批量注册路由的方式, 为一组同一组路由提供简单便方便在注册方法.
第一个参数type为需要批量注册的路由类型; 初始化httpd对象后, 使用app.USE
或app.API
进行传值;
第二个参数prefix为string类型的头部; 例如:/api
、/admin
;
第三个参数为一组路由处理函数或处理类数组; 类型为: {route = '/login', class = class};
注意: 此方法主要为批量注册api类型或use类型路由对象准备, 不可同时注册不同类型路由, 不可同时注册websocket路由;
为了模块化view类型路由与api类型路由(区分admin/api类型), 提供方便项目管理与使用, 减少潜在口头的约定;
此方法返回值为nil
httpd:listen(ip, port)
listen方法用于告诉httpd对象监听指定端口; 第一个参数ip暂未被httpd使用, 默认监听所有网卡的ip地址与port端口号;
此方法需要在run方法之前被调用.
此方法返回值为nil
httpd:run(ip, port)
run方法用于开始监听模式.
注意: run方法被调用后, 写在run方法之后的代码将直到程序结束后都可能(maybe)不会被执行.
此方法返回值为nil
httpd示例:
一个最简单的cf web service使用示例:
local httpd = require "httpd"
local app = httpd:new('httpd')
app:use('/view', function(content)
return '<h1>CF Web Service</h1>'
end)
app:api('/api', function(content)
return '{"code":200,"message":"ok"}' -- json string
end)
app:listen("0.0.0.0", 8080)
app:run()
httpd 请求日志格式:
[年/月/日 时:分:秒] - [ip] - [x-real-ip] - [path] - [http code] - [request handle timeline]
httpd request的 content 内容
args : 支持标准get或者post的参数, 对a[1]=1&a[2]=2将会不会解析为数组类型; 支持multipart-form传参;
body : body支持三中类型, multipart-form file/post json/post xml/;
header: 原始header数组, 不会进行header解析;
file : 进行multipart-form上传一个或多个文件时, 才会有这个属性;
cf会将后两种数据交换格式根据post content-type类型在content内注入类型标识; 如:
app:use('/admin', function(content)
if conten.json then
return 'json', print('json')
elseif conten.xml then
return 'xml', print('xml')
end
return '未知类型'
end)
注意
body为```xml```或```json```类型时, 框架仅会判断类型做好标识, 为了不依赖相关库所以不会主动解析.
更多建议: