用 Go 語言給 Lua/OpenResty 寫擴展
https://www.lbbniu.com/3477.html
背景
最近的一個lua項目中需要解析wbxml,WBXML是XML的二進制表示形式,Exchange與手機端之間的通訊採用的就是該協議,我需要解析到手機端提交過來的數據,以提高用戶體驗。
但是lua沒有現成的Wbxml解析庫,從頭擼一個勢必要花費大量造輪子的時間,在網上查找了半天,發現有一個go語言版本的github.com/magicmonty/a,寫了幾行測試代碼,確認該庫可以正常解析出Exchange的wbxml數據內容,如下所示:
微服務 VS lua 擴展
最初的方案打算用golang實現一個微服務,供openresty調用,該方案的特點是方便,能快速實現,但缺點也是非常明顯的:
- 性能損耗大:openresty每接收到一個請求都需要調用golang的restful api,然後等待golang把wbxml解析完並返回,這中間有非常大的性能損耗
- 增加運維成本:golang微服務奔潰後,openresty將無法拿到想到的信息了,在運維時,除了要關注openresty本身外,還要時刻關注golang微服務的業務連續性、性能等指標
最佳的方案是提供一個lua的擴展,無縫集成到openresty中,這樣可以完美地規避掉上述2個缺點。
用GO語言擴展lua
編寫規範
關於用go語言擴展lua,github中已有現成的輔助庫github.com/theganyo/lua可以使用,它的工作流程如下:
1. 編寫go模塊,並導出需要給lua使用的函數: //export add func add(operand1 int, operand2 int) int { return operand1 + operand2 } 2. 將go模塊編譯爲靜態庫: go build -buildmode=c-shared -o example.so example.go 3. 編寫lua文件,加載自己的.so文件: local lua2go = require('lua2go') local example = lua2go.Load('./example.so') 4. 在lua文件與頭文件模塊中註冊導出的函數: lua2go.Externs[[ extern GoInt add(GoInt p0, GoInt p1); ]] 5. 在lua文件中調用導出的函數並將結果轉化爲lua格式的數據: local goAddResult = example.add(1, 1) local addResult = lua2go.ToLua(goAddResult) print('1 + 1 = ' .. addResult)
詳細情況可以參考該項目的example
編寫自己的的wbxml解析庫
getDecodeResult函數可以將wbxml的二進制數據直接解析成xml格式的string
func getDecodeResult(data ...byte) string { var result string result, _ = Decode(bytes.NewBuffer(data), MakeCodeBook(PROTOCOL_VERSION_14_1)) return result }
但解析出來的xml的格式如下,多層嵌套且用了命名空間,雖然能看到明文的xml了,但是還是不能直接取到我們想要的數據
<?xml version="1.0" encoding="utf-8"?> <O:Provision xmlns:O="Provision" xmlns:S="Settings"> <S:DeviceInformation> <S:Set> <S:Model>MIX 2</S:Model> <S:IMEI>888833336669999</S:IMEI> <S:FriendlyName>MIX 2</S:FriendlyName> <S:OS>Android 8.0.0</S:OS> <S:PhoneNumber>+8618599999999</S:PhoneNumber> <S:UserAgent>Android/8.0.0-EAS-1.3</S:UserAgent> <S:MobileOperator>中國聯通 (46001)</S:MobileOperator> </S:Set> </S:DeviceInformation> <O:Policies> <O:Policy> <O:PolicyType>MS-EAS-Provisioning-WBXML</O:PolicyType> </O:Policy> </O:Policies> </O:Provision>
我們需要再對xml進行一次解析,解析到對應的struct中,就可以方便地獲取想要的數據了,但是這個xml格式比較複雜,筆者試着手工定義了幾次都失敗了,乾脆找了個自動化工具自動生成了,自動化工具的地址爲github.com/miku/zek。
作者還提供了個Web版的在線工具,使用起來非常方便,地址爲:onlinetool.io/xmltogo/
最後生成的Struct如下:
type Provision struct { XMLName xml.Name `xml:"Provision"` Text string `xml:",chardata"` O string `xml:"O,attr"` S string `xml:"S,attr"` DeviceInformation struct { Text string `xml:",chardata"` Set struct { Text string `xml:",chardata"` Model string `xml:"Model"` IMEI string `xml:"IMEI"` FriendlyName string `xml:"FriendlyName"` OS string `xml:"OS"` PhoneNumber string `xml:"PhoneNumber"` UserAgent string `xml:"UserAgent"` MobileOperator string `xml:"MobileOperator"` } `xml:"Set"` } `xml:"DeviceInformation"` Policies struct { Text string `xml:",chardata"` Policy struct { Text string `xml:",chardata"` PolicyType string `xml:"PolicyType"` } `xml:"Policy"` } `xml:"Policies"` }
最終我們自己導出的處理wbxml的函數如下(將需要關注的信息放到一個用||分割的字符串中返回):
//export parse func parse(data []byte) (*C.char) { result := make([]string, 0) xmldata := getDecodeResult(data...) fmt.Println(xmldata) out := Provision{} xml.Unmarshal([]byte(xmldata), &out) //fmt.Printf("Model: %v\n", out.DeviceInformation.Set.Model) //fmt.Printf("Imie: %v\n", out.DeviceInformation.Set.IMEI) //fmt.Printf("FriendlyName: %v\n", out.DeviceInformation.Set.FriendlyName) //fmt.Printf("PhoneNumber: %v\n", out.DeviceInformation.Set.PhoneNumber) //fmt.Printf("MobileOperator: %v\n", out.DeviceInformation.Set.MobileOperator) result = append(result, out.DeviceInformation.Set.Model) result = append(result, out.DeviceInformation.Set.IMEI) result = append(result, out.DeviceInformation.Set.FriendlyName) result = append(result, out.DeviceInformation.Set.PhoneNumber) result = append(result, out.DeviceInformation.Set.MobileOperator) return C.CString(strings.Join(result, "||")) }
接下來分別在wbxml.h和xbxml/lua中導出這個函數,如下所示:
wbxml.h的內容:
#ifndef GO_CGO_PROLOGUE_H #define GO_CGO_PROLOGUE_H typedef signed char GoInt8; typedef unsigned char GoUint8; typedef short GoInt16; typedef unsigned short GoUint16; typedef int GoInt32; typedef unsigned int GoUint32; typedef long long GoInt64; typedef unsigned long long GoUint64; typedef GoInt64 GoInt; typedef GoUint64 GoUint; typedef __SIZE_TYPE__ GoUintptr; typedef float GoFloat32; typedef double GoFloat64; typedef float _Complex GoComplex64; typedef double _Complex GoComplex128; /* static assertion to make sure the file is being used on architecture at least with matching size of GoInt. */ typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; typedef struct { const char *p; GoInt n; } GoString; typedef void *GoMap; typedef void *GoChan; typedef struct { void *t; void *v; } GoInterface; typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; #endif /* End of boilerplate cgo prologue. */ #ifdef __cplusplus extern "C" { #endif extern char* parse(GoString data); #ifdef __cplusplus } #endif
wbxml的內容:
-- ensure the lua2go lib is on the LUA_PATH so it will load -- normally, you'd just put it on the LUA_PATH package.path = package.path .. ';../lua/?.lua' -- load lua2go local lua2go = require('lua2go') -- load my Go library local example = lua2go.Load('/data/code/golang/src/dewbxml/wbxml.so') -- copy just the extern functions from benchmark.h into ffi.cdef structure below -- (the boilerplate cgo prologue is already defined for you in lua2go) -- this registers your Go functions to the ffi library.. lua2go.Externs[[ extern char* parse(GoString data); ]] local filename = "/data/code/golang/src/dewbxml/file.bin" local file = io.open(filename,"rb") local data = file:read("*a") local goResult = example.parse(lua2go.ToGo(data)) local Result = lua2go.ToLua(goResult) print('Result: ' .. Result)
最終的結果如下圖所示:
造化弄人,在不經意間,還是造了一個輪子,項目地址爲:github.com/netxfly/dewb
轉載請註明:無限飛翔 » 用 Go 語言給 Lua/OpenResty 寫擴展