首先介紹下目前市面上比較知名的2種C實(shí)現(xiàn)的http parser :
在cf框架未成形之初! 作者為了測(cè)試簡(jiǎn)單與方便, 自己用string庫(kù)封裝了基礎(chǔ)的http server. 這可以在最初的提交代碼找到.
當(dāng)開(kāi)發(fā)進(jìn)展到底層代碼封裝完畢后, 所以就想到去開(kāi)發(fā)者交友網(wǎng)站進(jìn)行咨詢了解.
目前需求為對(duì)線性字符串匹配與header頭部start與end poosition定位, 然后創(chuàng)建指定大小table組裝http content.
然后針對(duì)性的進(jìn)行數(shù)據(jù)解析, 判斷方法類(lèi)型、打上數(shù)據(jù)類(lèi)型標(biāo)簽, 最后返回給使用者.
在明確上述需求后, 經(jīng)過(guò)一些簡(jiǎn)單調(diào)研與測(cè)試便選擇了h2o的http parser實(shí)現(xiàn).
原因:
當(dāng)初簡(jiǎn)單對(duì)pico與http_parser進(jìn)行過(guò)對(duì)比, 在不開(kāi)啟sse4的情況下至少快2倍左右。有興趣可以自行測(cè)試.
pico的代碼我放在luaclib內(nèi), 封裝后使用makefile編譯lua可用動(dòng)態(tài)庫(kù). 默認(rèn)沒(méi)開(kāi)啟sse4, 為了照顧一些同學(xué)機(jī)器不支持sse4指令集的情況, 省卻了改makefile的麻煩 :)
眾所周知, 中間件設(shè)計(jì)的目的是為了授權(quán)驗(yàn)證、請(qǐng)求準(zhǔn)入等需求設(shè)計(jì)而來(lái)的. 在大多數(shù)http server框架中都會(huì)增加類(lèi)似設(shè)計(jì).
cf為此分離除了before函數(shù)專(zhuān)門(mén)用來(lái)處理httpd service使用, 避免冗余無(wú)用的參數(shù)驗(yàn)證等代碼與業(yè)務(wù)邏輯耦合在一起.
同時(shí), 提供了http庫(kù)來(lái)體現(xiàn)before回調(diào)函數(shù)應(yīng)該如何工作. 在使用者熟練之后可以使用cf很輕松的寫(xiě)出高性能api service.
下面著重為大家介紹httpd的使用與相關(guān)函數(shù)原型, 有能力的同學(xué)也可以直接閱讀源碼進(jìn)行學(xué)習(xí):
使用之前先導(dǎo)入httpd庫(kù)
使用new方法創(chuàng)建一個(gè)httpd對(duì)象.(這沒(méi)什么好說(shuō)的)
此方法返回一個(gè)httpd對(duì)象;
timeout方法設(shè)置每個(gè)http請(qǐng)求的connection最大保持時(shí)間, 默認(rèn)為15秒;
此方法返回值為nil;
server_name方法的參數(shù)是一個(gè)字符串類(lèi)型的參數(shù), 這個(gè)方法設(shè)置cf的http response頭部Server字段的值;
此方法返回值為nil;
max_path_size方法設(shè)置最大path路徑長(zhǎng)度, 超過(guò)number大小的path都會(huì)被禁止; 默認(rèn)長(zhǎng)度為1024;
此方法返回值為nil;
max_body_size方法設(shè)置最大body長(zhǎng)度, 超過(guò)number大小的body都會(huì)被禁止; 默認(rèn)長(zhǎng)度為1024 * 1024;
此方法返回值為nil;
max_header_size方法設(shè)置最大body長(zhǎng)度, 超過(guò)number大小的header都會(huì)被禁止; 默認(rèn)長(zhǎng)度為65535;
此方法返回值為nil;
before方法設(shè)置每個(gè)請(qǐng)求在被業(yè)務(wù)函數(shù)處理之前, 優(yōu)先被function處理.
此方法需要配合http庫(kù)進(jìn)行使用, http可以為此設(shè)計(jì)了專(zhuān)門(mén)的API來(lái)語(yǔ)義化中間件設(shè)計(jì). 提升代碼可讀性.
這個(gè)方法一般可用于設(shè)計(jì)業(yè)務(wù)中間件、準(zhǔn)入認(rèn)證、行為收集等等; 具體使用方法參見(jiàn)main.lua示例.
local http = require "http"
app:before(function(content)
if content.METHOD == 'GET' then
return http.ok()
end
return http.throw(401, '<h1>未授權(quán)</h1>')
end)
需要注意的是: 只要你注入了funtion, 即使function內(nèi)部什么都不做也會(huì)返回401.
此方法返回值為nil;
ws方法為使用者注入http -> websocket類(lèi)型的路由升級(jí).
注冊(cè)此方法后, httpd在檢測(cè)到當(dāng)前路由類(lèi)型為ws類(lèi)型后將會(huì)為檢查請(qǐng)求頭部(header), 錯(cuò)誤的頭部將會(huì)被判斷并返回錯(cuò)誤碼.
首先被回調(diào)的是注冊(cè)ws路由的class ctor方法; 這個(gè)方法將會(huì)有一個(gè)opt參數(shù)(table類(lèi)型). ws對(duì)象將會(huì)被注入其中.
具體使用示例, 請(qǐng)參考script/ws.lua
.
use/api方法為route注入路由處理函數(shù), handle即可以是一個(gè)函數(shù), 也可以是一個(gè)lua table(class).
注意:
如果注入的handle為table:
① 必須使用cf框架內(nèi)置的lua class語(yǔ)法進(jìn)行設(shè)計(jì);
② new方法會(huì)傳入http request content(注意: 構(gòu)造函數(shù)異常將會(huì)引起路由對(duì)應(yīng)的方法無(wú)法被正確調(diào)用);
③ get/post/put方法會(huì)根據(jù)用戶請(qǐng)求類(lèi)型(method)被回調(diào)(無(wú)參數(shù)), 如未實(shí)現(xiàn)對(duì)應(yīng)方法將會(huì)有框架返回對(duì)應(yīng)狀態(tài)碼;
如果注入的handle為function:
cf會(huì)為此function注入當(dāng)前的http request content, 內(nèi)部包含所有請(qǐng)求上下文與參數(shù);
handle返回值必須為一個(gè)string類(lèi)型的值, 否則將可能出現(xiàn)警告或返回空值;
方法返回值為nil
group方法提供了一種批量注冊(cè)路由的方式, 為一組同一組路由提供簡(jiǎn)單便方便在注冊(cè)方法.
第一個(gè)參數(shù)type為需要批量注冊(cè)的路由類(lèi)型; 初始化httpd對(duì)象后, 使用app.USE
或app.API
進(jìn)行傳值;
第二個(gè)參數(shù)prefix為string類(lèi)型的頭部; 例如:/api
、/admin
;
第三個(gè)參數(shù)為一組路由處理函數(shù)或處理類(lèi)數(shù)組; 類(lèi)型為: {route = '/login', class = class};
注意: 此方法主要為批量注冊(cè)api類(lèi)型或use類(lèi)型路由對(duì)象準(zhǔn)備, 不可同時(shí)注冊(cè)不同類(lèi)型路由, 不可同時(shí)注冊(cè)websocket路由;
為了模塊化view類(lèi)型路由與api類(lèi)型路由(區(qū)分admin/api類(lèi)型), 提供方便項(xiàng)目管理與使用, 減少潛在口頭的約定;
此方法返回值為nil
listen方法用于告訴httpd對(duì)象監(jiān)聽(tīng)指定端口; 第一個(gè)參數(shù)ip暫未被httpd使用, 默認(rèn)監(jiān)聽(tīng)所有網(wǎng)卡的ip地址與port端口號(hào);
此方法需要在run方法之前被調(diào)用.
此方法返回值為nil
run方法用于開(kāi)始監(jiān)聽(tīng)模式.
注意: run方法被調(diào)用后, 寫(xiě)在run方法之后的代碼將直到程序結(jié)束后都可能(maybe)不會(huì)被執(zhí)行.
此方法返回值為nil
一個(gè)最簡(jiǎn)單的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()
[年/月/日 時(shí):分:秒] - [ip] - [x-real-ip] - [path] - [http code] - [request handle timeline]
args : 支持標(biāo)準(zhǔn)get或者post的參數(shù), 對(duì)a[1]=1&a[2]=2將會(huì)不會(huì)解析為數(shù)組類(lèi)型; 支持multipart-form傳參;
body : body支持三中類(lèi)型, multipart-form file/post json/post xml/;
header: 原始header數(shù)組, 不會(huì)進(jìn)行header解析;
file : 進(jìn)行multipart-form上傳一個(gè)或多個(gè)文件時(shí), 才會(huì)有這個(gè)屬性;
cf會(huì)將后兩種數(shù)據(jù)交換格式根據(jù)post content-type類(lèi)型在content內(nèi)注入類(lèi)型標(biāo)識(shí); 如:
app:use('/admin', function(content)
if conten.json then
return 'json', print('json')
elseif conten.xml then
return 'xml', print('xml')
end
return '未知類(lèi)型'
end)
body為```xml```或```json```類(lèi)型時(shí), 框架僅會(huì)判斷類(lèi)型做好標(biāo)識(shí), 為了不依賴(lài)相關(guān)庫(kù)所以不會(huì)主動(dòng)解析.
更多建議: