├── .gitignore
├── 1.xml
├── wbxml.lua
├── wbxml.h
├── main.go
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | .idea
3 |
--------------------------------------------------------------------------------
/1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MIX 2
6 | 888833336669999
7 | MIX 2
8 | Android 8.0.0
9 | +8618599999999
10 | Android/8.0.0-EAS-1.3
11 | 中国联通 (46001)
12 |
13 |
14 |
15 |
16 | MS-EAS-Provisioning-WBXML
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/wbxml.lua:
--------------------------------------------------------------------------------
1 | -- ensure the lua2go lib is on the LUA_PATH so it will load
2 | -- normally, you'd just put it on the LUA_PATH
3 | package.path = package.path .. ';../lua/?.lua'
4 |
5 | -- load lua2go
6 | local lua2go = require('lua2go')
7 |
8 | -- load my Go library
9 | local example = lua2go.Load('/data/code/golang/src/dewbxml/wbxml.so')
10 |
11 | -- copy just the extern functions from benchmark.h into ffi.cdef structure below
12 | -- (the boilerplate cgo prologue is already defined for you in lua2go)
13 | -- this registers your Go functions to the ffi library..
14 | lua2go.Externs[[
15 | extern char* parse(GoString data);
16 | ]]
17 |
18 | local filename = "/data/code/golang/src/dewbxml/file.bin"
19 | local file = io.open(filename,"rb")
20 | local data = file:read("*a")
21 | local goResult = example.parse(lua2go.ToGo(data))
22 |
23 | local Result = lua2go.ToLua(goResult)
24 |
25 | print('Result: ' .. Result)
26 |
--------------------------------------------------------------------------------
/wbxml.h:
--------------------------------------------------------------------------------
1 | /* Created by "go tool cgo" - DO NOT EDIT. */
2 |
3 | /* package command-line-arguments */
4 |
5 | /* Start of preamble from import "C" comments. */
6 |
7 |
8 |
9 |
10 | /* End of preamble from import "C" comments. */
11 |
12 |
13 | /* Start of boilerplate cgo prologue. */
14 |
15 | #ifndef GO_CGO_PROLOGUE_H
16 | #define GO_CGO_PROLOGUE_H
17 |
18 | typedef signed char GoInt8;
19 | typedef unsigned char GoUint8;
20 | typedef short GoInt16;
21 | typedef unsigned short GoUint16;
22 | typedef int GoInt32;
23 | typedef unsigned int GoUint32;
24 | typedef long long GoInt64;
25 | typedef unsigned long long GoUint64;
26 | typedef GoInt64 GoInt;
27 | typedef GoUint64 GoUint;
28 | typedef __SIZE_TYPE__ GoUintptr;
29 | typedef float GoFloat32;
30 | typedef double GoFloat64;
31 | typedef float _Complex GoComplex64;
32 | typedef double _Complex GoComplex128;
33 |
34 | /*
35 | static assertion to make sure the file is being used on architecture
36 | at least with matching size of GoInt.
37 | */
38 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
39 |
40 | typedef struct { const char *p; GoInt n; } GoString;
41 | typedef void *GoMap;
42 | typedef void *GoChan;
43 | typedef struct { void *t; void *v; } GoInterface;
44 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
45 |
46 | #endif
47 |
48 | /* End of boilerplate cgo prologue. */
49 |
50 | #ifdef __cplusplus
51 | extern "C" {
52 | #endif
53 |
54 | extern char* parse(GoString data);
55 |
56 | #ifdef __cplusplus
57 | }
58 | #endif
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Copyright (c) 2018 sec.lu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEq
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
23 | */
24 |
25 | package main
26 |
27 | import (
28 | "C"
29 | "bytes"
30 | "encoding/xml"
31 | "fmt"
32 | "os"
33 | "regexp"
34 | "strings"
35 |
36 | . "github.com/magicmonty/activesync-go/activesync"
37 | . "github.com/magicmonty/activesync-go/activesync/base"
38 | . "github.com/magicmonty/wbxml-go/wbxml"
39 | )
40 |
41 | /*
42 | ```
43 |
44 |
45 |
46 |
47 | MIX 2
48 | 888833336669999
49 | MIX 2
50 | Android 8.0.0
51 | +8618599999999
52 | Android/8.0.0-EAS-1.3
53 | 中国联通 (46001)
54 |
55 |
56 |
57 |
58 | MS-EAS-Provisioning-WBXML
59 |
60 |
61 |
62 |
63 | ```
64 | */
65 | type Provision struct {
66 | XMLName xml.Name `xml:"Provision"`
67 | Text string `xml:",chardata"`
68 | O string `xml:"O,attr"`
69 | S string `xml:"S,attr"`
70 | DeviceInformation struct {
71 | Text string `xml:",chardata"`
72 | Set struct {
73 | Text string `xml:",chardata"`
74 | Model string `xml:"Model"`
75 | IMEI string `xml:"IMEI"`
76 | FriendlyName string `xml:"FriendlyName"`
77 | OS string `xml:"OS"`
78 | PhoneNumber string `xml:"PhoneNumber"`
79 | UserAgent string `xml:"UserAgent"`
80 | MobileOperator string `xml:"MobileOperator"`
81 | } `xml:"Set"`
82 | } `xml:"DeviceInformation"`
83 | Policies struct {
84 | Text string `xml:",chardata"`
85 | Policy struct {
86 | Text string `xml:",chardata"`
87 | PolicyType string `xml:"PolicyType"`
88 | } `xml:"Policy"`
89 | } `xml:"Policies"`
90 | }
91 |
92 | func removeInvalidChars(b []byte) []byte {
93 | re := regexp.MustCompile("[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]")
94 | return re.ReplaceAll(b, []byte{})
95 | }
96 |
97 | //export encodeXML
98 | func encodeXML(xmlString []byte) {
99 | xmlString = removeInvalidChars([]byte(xmlString))
100 | w := bytes.NewBuffer(make([]byte, 0))
101 | e := NewEncoder(
102 | MakeCodeBook(PROTOCOL_VERSION_14_1),
103 | string(xmlString),
104 | w)
105 | err := e.Encode()
106 | if err != nil {
107 | fmt.Println(err.Error())
108 | } else {
109 | fmt.Println(w)
110 | }
111 | }
112 | func getDecodeResult(data ...byte) string {
113 | var result string
114 | result, _ = Decode(bytes.NewBuffer(data), MakeCodeBook(PROTOCOL_VERSION_14_1))
115 | return result
116 | }
117 |
118 | //export parse
119 | func parse(data string) (*C.char) {
120 | result := make([]string, 0)
121 | xmldata := getDecodeResult([]byte(data)...)
122 | // fmt.Println(xmldata)
123 |
124 | out := Provision{}
125 | xml.Unmarshal([]byte(xmldata), &out)
126 |
127 | //fmt.Printf("Model: %v\n", out.DeviceInformation.Set.Model)
128 | //fmt.Printf("Imie: %v\n", out.DeviceInformation.Set.IMEI)
129 | //fmt.Printf("FriendlyName: %v\n", out.DeviceInformation.Set.FriendlyName)
130 | //fmt.Printf("PhoneNumber: %v\n", out.DeviceInformation.Set.PhoneNumber)
131 | //fmt.Printf("MobileOperator: %v\n", out.DeviceInformation.Set.MobileOperator)
132 |
133 | result = append(result, out.DeviceInformation.Set.Model)
134 | result = append(result, out.DeviceInformation.Set.IMEI)
135 | result = append(result, out.DeviceInformation.Set.FriendlyName)
136 | result = append(result, out.DeviceInformation.Set.PhoneNumber)
137 | result = append(result, out.DeviceInformation.Set.MobileOperator)
138 |
139 | return C.CString(strings.Join(result, "||"))
140 | }
141 |
142 | func main() {
143 | if len(os.Args) == 3 {
144 | cmd := os.Args[1]
145 | file := os.Args[2]
146 | f, err := os.Open(file)
147 | if err == nil {
148 | data := make([]byte, 102400)
149 | f.Read(data)
150 |
151 | if strings.ToLower(cmd) == "encode" {
152 | encodeXML(data)
153 | } else {
154 | parse(string(data))
155 | }
156 | }
157 | } else {
158 | fmt.Printf("%s [encode|decode] filename\n", os.Args[0])
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```toml
2 | title = "用go语言给lua写扩展"
3 | slug = "extending-lua-with-go-language"
4 | desc = "extending-lua-with-go-language"
5 | date = "2018-11-27 00:14:21"
6 | update_date = "2018-11-27 00:14:21"
7 | author = ""
8 | thumb = ""
9 | draft = false
10 | tags = ["tag"]
11 | ```
12 |
13 | ## 背景
14 | 最近的一个lua项目中需要解析wbxml,WBXML是XML的二进制表示形式,Exchange与手机端之间的通讯采用的就是该协议,我需要解析到手机端提交过来的数据,但lua没有现成的Wbxml解析库,从头撸一个势必要花费大量造轮子的时间,在网上查找了半天,发现有一个go语言版本的[https://github.com/magicmonty/activesync-go](https://github.com/magicmonty/activesync-go),写了几行测试代码,确认该库可以正常解析出Exchange的wbxml数据内容,如下所示:
15 |
16 | 
17 |
18 |
19 | ## 微服务 VS lua 扩展
20 |
21 | 最初的方案打算用golang实现一个微服务,供openresty调用,该方案的特点是方便,能快速实现,但缺点也是非常明显的:
22 |
23 | - 性能损耗大:openresty每接收到一个请求都需要调用golang的restful api,然后等待golang把wbxml解析完并返回,这中间有非常大的性能损耗
24 | - 增加运维成本:golang微服务奔溃后,openresty将无法拿到想到的信息了,在运维时,除了要关注openresty本身外,还要时刻关注golang微服务的业务连续性、性能等指标
25 |
26 | 最佳的方案是提供一个lua的扩展,无缝集成到openresty中,这样可以完美地规避掉上述2个缺点。
27 |
28 | ## 用GO语言扩展lua
29 |
30 | ### 编写规范
31 | 关于用go语言扩展lua,github中已有现成的辅助库[https://github.com/theganyo/lua2go](https://github.com/theganyo/lua2go)可以使用,它的工作流程如下:
32 |
33 | 1. 编写go模块,并导出需要给lua使用的函数:
34 |
35 | ```golang
36 | //export add
37 | func add(operand1 int, operand2 int) int {
38 | return operand1 + operand2
39 | }
40 | ```
41 | 1. 将go模块编译为静态库:
42 |
43 | ```golang
44 | go build -buildmode=c-shared -o example.so example.go
45 | ```
46 | 1. 编写lua文件,加载自己的.so文件:
47 |
48 | ```lua
49 | local lua2go = require('lua2go')
50 | local example = lua2go.Load('./example.so')
51 | ```
52 | 1. 在lua文件与头文件模块中注册导出的函数:
53 |
54 | ```lua
55 | lua2go.Externs[[
56 | extern GoInt add(GoInt p0, GoInt p1);
57 | ]]
58 | ```
59 | 1. 在lua文件中调用导出的函数并将结果转化为lua格式的数据:
60 |
61 | ```lua
62 | local goAddResult = example.add(1, 1)
63 | local addResult = lua2go.ToLua(goAddResult)
64 | print('1 + 1 = ' .. addResult)
65 | ```
66 |
67 | 详细情况可以参考该项目的[example](https://github.com/theganyo/lua2go/tree/master/example)
68 |
69 | ### 编写自己的的wbxml解析库
70 |
71 | `getDecodeResult`函数可以将wbxml的二进制数据直接解析成xml格式的string
72 | ```golang
73 | func getDecodeResult(data ...byte) string {
74 | var result string
75 | result, _ = Decode(bytes.NewBuffer(data), MakeCodeBook(PROTOCOL_VERSION_14_1))
76 | return result
77 | }
78 | ```
79 |
80 | 但解析出来的xml的格式如下,多层嵌套且用了命名空间,虽然能看到明文的xml了,但是还是不能直接取到我们想要的数据
81 | ```xml
82 |
83 |
84 |
85 |
86 | MIX 2
87 | 888833336669999
88 | MIX 2
89 | Android 8.0.0
90 | +8618599999999
91 | Android/8.0.0-EAS-1.3
92 | 中国联通 (46001)
93 |
94 |
95 |
96 |
97 | MS-EAS-Provisioning-WBXML
98 |
99 |
100 |
101 | ```
102 |
103 | 我们需要再对xml进行一次解析,解析到对应的struct中,就可以方便地获取想要的数据了,但是这个xml格式比较复杂,笔者试着手工定义了几次都失败了,干脆找了个自动化工具自动生成了,自动化工具的地址为[https://github.com/miku/zek](https://github.com/miku/zek)。
104 |
105 | 作者还提供了个Web版的在线工具,使用起来非常方便,地址为:[https://www.onlinetool.io/xmltogo/](https://www.onlinetool.io/xmltogo/)
106 |
107 | 最后生成的Struct如下:
108 | ```golang
109 | type Provision struct {
110 | XMLName xml.Name `xml:"Provision"`
111 | Text string `xml:",chardata"`
112 | O string `xml:"O,attr"`
113 | S string `xml:"S,attr"`
114 | DeviceInformation struct {
115 | Text string `xml:",chardata"`
116 | Set struct {
117 | Text string `xml:",chardata"`
118 | Model string `xml:"Model"`
119 | IMEI string `xml:"IMEI"`
120 | FriendlyName string `xml:"FriendlyName"`
121 | OS string `xml:"OS"`
122 | PhoneNumber string `xml:"PhoneNumber"`
123 | UserAgent string `xml:"UserAgent"`
124 | MobileOperator string `xml:"MobileOperator"`
125 | } `xml:"Set"`
126 | } `xml:"DeviceInformation"`
127 | Policies struct {
128 | Text string `xml:",chardata"`
129 | Policy struct {
130 | Text string `xml:",chardata"`
131 | PolicyType string `xml:"PolicyType"`
132 | } `xml:"Policy"`
133 | } `xml:"Policies"`
134 | }
135 |
136 | ```
137 |
138 | 最终我们自己导出的处理wbxml的函数如下(将需要关注的信息放到一个用`||`分割的字符串中返回):
139 |
140 | ```golang
141 | //export parse
142 | func parse(data []byte) (*C.char) {
143 | result := make([]string, 0)
144 | xmldata := getDecodeResult(data...)
145 | fmt.Println(xmldata)
146 |
147 | out := Provision{}
148 | xml.Unmarshal([]byte(xmldata), &out)
149 |
150 | //fmt.Printf("Model: %v\n", out.DeviceInformation.Set.Model)
151 | //fmt.Printf("Imie: %v\n", out.DeviceInformation.Set.IMEI)
152 | //fmt.Printf("FriendlyName: %v\n", out.DeviceInformation.Set.FriendlyName)
153 | //fmt.Printf("PhoneNumber: %v\n", out.DeviceInformation.Set.PhoneNumber)
154 | //fmt.Printf("MobileOperator: %v\n", out.DeviceInformation.Set.MobileOperator)
155 |
156 | result = append(result, out.DeviceInformation.Set.Model)
157 | result = append(result, out.DeviceInformation.Set.IMEI)
158 | result = append(result, out.DeviceInformation.Set.FriendlyName)
159 | result = append(result, out.DeviceInformation.Set.PhoneNumber)
160 | result = append(result, out.DeviceInformation.Set.MobileOperator)
161 |
162 | return C.CString(strings.Join(result, "||"))
163 | }
164 | ```
165 |
166 | 接下来分别在`wbxml.h`和`xbxml/lua`中导出这个函数,如下所示:
167 |
168 | `wbxml.h`的内容:
169 | ```c
170 | #ifndef GO_CGO_PROLOGUE_H
171 | #define GO_CGO_PROLOGUE_H
172 |
173 | typedef signed char GoInt8;
174 | typedef unsigned char GoUint8;
175 | typedef short GoInt16;
176 | typedef unsigned short GoUint16;
177 | typedef int GoInt32;
178 | typedef unsigned int GoUint32;
179 | typedef long long GoInt64;
180 | typedef unsigned long long GoUint64;
181 | typedef GoInt64 GoInt;
182 | typedef GoUint64 GoUint;
183 | typedef __SIZE_TYPE__ GoUintptr;
184 | typedef float GoFloat32;
185 | typedef double GoFloat64;
186 | typedef float _Complex GoComplex64;
187 | typedef double _Complex GoComplex128;
188 |
189 | /*
190 | static assertion to make sure the file is being used on architecture
191 | at least with matching size of GoInt.
192 | */
193 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
194 |
195 | typedef struct { const char *p; GoInt n; } GoString;
196 | typedef void *GoMap;
197 | typedef void *GoChan;
198 | typedef struct { void *t; void *v; } GoInterface;
199 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
200 |
201 | #endif
202 |
203 | /* End of boilerplate cgo prologue. */
204 |
205 | #ifdef __cplusplus
206 | extern "C" {
207 | #endif
208 |
209 | extern char* parse(GoString data);
210 |
211 | #ifdef __cplusplus
212 | }
213 | #endif
214 | ```
215 |
216 | `wbxml`的内容:
217 |
218 | ```lua
219 | -- ensure the lua2go lib is on the LUA_PATH so it will load
220 | -- normally, you'd just put it on the LUA_PATH
221 | package.path = package.path .. ';../lua/?.lua'
222 |
223 | -- load lua2go
224 | local lua2go = require('lua2go')
225 |
226 | -- load my Go library
227 | local example = lua2go.Load('/data/code/golang/src/dewbxml/wbxml.so')
228 |
229 | -- copy just the extern functions from benchmark.h into ffi.cdef structure below
230 | -- (the boilerplate cgo prologue is already defined for you in lua2go)
231 | -- this registers your Go functions to the ffi library..
232 | lua2go.Externs[[
233 | extern char* parse(GoString data);
234 | ]]
235 |
236 | local filename = "/data/code/golang/src/dewbxml/file.bin"
237 | local file = io.open(filename,"rb")
238 | local data = file:read("*a")
239 | local goResult = example.parse(lua2go.ToGo(data))
240 |
241 | local Result = lua2go.ToLua(goResult)
242 |
243 | print('Result: ' .. Result)
244 | ```
245 | 最终的结果如下图所示:
246 | 
247 |
248 | 造化弄人,在不经意间,还是造了一个轮子,项目地址为:[https://github.com/netxfly/dewbxml](https://github.com/netxfly/dewbxml)
249 |
--------------------------------------------------------------------------------