codecamp

HTTPD

http parser哪家强?

首先介绍下目前市面上比较知名的2种C实现的http parser :

  1. h2o的picohttp;

  1. nodejs实现http_parser;

在cf框架未成形之初! 作者为了测试简单与方便, 自己用string库封装了基础的http server. 这可以在最初的提交代码找到.

当开发进展到底层代码封装完毕后, 所以就想到去开发者交友网站进行咨询了解.

目前需求为对线性字符串匹配与header头部start与end poosition定位, 然后创建指定大小table组装http content.

然后针对性的进行数据解析, 判断方法类型、打上数据类型标签, 最后返回给使用者.

在明确上述需求后, 经过一些简单调研与测试便选择了h2o的http parser实现.

为什么选择pico?

原因:

  1. pico的头文件与实现代码就2个文件, 使用也非常方便, 编译完成后就可以开始使用.

  1. 实现简单并且通俗易懂, 非常容易上手;

  1. 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).

注意:

  1. 无关乎handle的类型, handle的返回值必须为string类型作为body返回给客户端;

  1. api的content-type为Applicaton/json; use则为:text/html;

  1. 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.USEapp.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```类型时, 框架仅会判断类型做好标识, 为了不依赖相关库所以不会主动解析.
API 使用手册
HTTPC
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

API 使用手册

HTTP API

Mail API

DB API

DB

Cache API

class API

MQ API

MQ

Crypt API

cf API

cf

System API

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }