一个原来ASP的小项目,我想移植到OpenResty平台上。ASP平台虽然当年简单粗暴,但是现在要保持生命力还是得找个前景比较看好的,于是我相中了OpenResty这个平台。
首先遇到的问题当然是编码问题了……由于是ASP项目从前到后GBK,好歹大部分的提交操作都是AJAX POST,但是AJAX的提交都是UTF-8的,之前是在ASP做了转码操作的,而OpenResty平台使用nginx-iconv-module来转码,但是我看了看,首先OpenResty平台都不默认打包这个模块,其次,貌似不满足要求,我要的不是全部转码,我只需要把AJAX提交的部分请求转码就行……
于是我开始寻找LuaJIT、OpenResty相关的转码库,也许是我不会找,总之没找到……找到一个Lua Iconv,但是基于Lua标准平台的,LuaJIT估计不能用,我连试都没试……我感觉发挥我自己动手丰衣足食的特长的时候到了……
我用EveryThing搜了一下自己的操作系统(windows),发现到处都是iconv.dll,直接随便拿出一个来用就是了嘛……
要调用这个库成功着实费了一些功夫,感觉它的函数定义特别反人类……我花了一晚上的时间来调通三个主要函数……整整一晚上啊…… 总结一下它的反人类之处:
1、fromCode和toCode参数顺序是反着的;
2、调用时传入的不是char*,而是char**;
3、传入的size不是size_t,而是size_t *;
4、outSize需要传入最大输出buff,而输出的不是最终输出的字节数,而是最后剩下没用完的字节数……
5、明明是size_t类型,却要返回-1……
6、导出的函数名前面都加了个lib
所以,调了一晚上不能怪我太弱,只能怪敌人太强大……闲话少说,先上代码,最早调通的iconv.lua是这样的
(注:沿用iconv我的from、to也是反着的)
(居然没有lua的编辑器,拿Ruby冒充一下):
local ffi = require("ffi"); ffi.cdef[[ long libiconv_open(const char* tocode, const char* fromcode); long libiconv(int cd, char** inbuf, long *inbytesleft, char** outbuf, long *outbytesleft); long libiconv_close(int cd); ]]; local ICONV = ffi.load("iconv.dll"); local iconv = { _cd = nil, BUFFER_LENGTH = 4096, openHandle = function(self,tocode,fromcode) if self._cd ~= nil and self._cd ~= -1 then error("please close it first!", 2); return false; end local o = {_cd = nil}; --setmetatable(o, self) --self.__index = self setmetatable(o, {__index = self}); if type(tocode) ~= "string" or type(fromcode) ~= "string" then error("paramater error,please input string!", 2); return false; end o._cd = ICONV.libiconv_open(tocode,fromcode); if o._cd == -1 then o._cd = nil; return false; end return o; end, iconv = function(self,str) local inLen = string.len(str); local insize = ffi.new("long[1]",inLen); local instr = ffi.new("char[?]",inLen+1,str); local inptr = ffi.new("char*[1]",instr); local outstr = ffi.new("char[?]",self.BUFFER_LENGTH+1); local outptr = ffi.new("char*[1]",outstr); local outsize = ffi.new("long[1]",self.BUFFER_LENGTH); local err = ICONV.libiconv(self._cd,inptr,insize,outptr,outsize); if err == -1 then return false,nil; end local out = ffi.string(outstr,self.BUFFER_LENGTH - outsize[0]); return true,out; end, closeHandle = function(self) if self._cd == nil or self._cd == -1 then error("please open it first!", 2); return false; end ICONV.libiconv_close(self._cd); self._cd = nil; end }; return iconv;至于里面的long,32位系统size_t是unsigned int,64位是unsigned long,由于有时候返回-1,为了对比方便,我把unsigned去掉了……大家可以根据自己的平台改改试试……
然后写个testIconv.lua来测试一下~
local Iconv = require("iconv") local togbk = Iconv:openHandle("gbk", "utf-8"); if not togbk then print("create handle failed!"); return; end; local succ=nil; local value = "我爱汉字~我用UTF-8..." print("before iconv:"..value) succ,value = togbk:iconv(value); togbk:closeHandle(); if not succ then value = nil; end print("after iconv:"..value);把该文件保存为utf-8格式,然后用命令行执行
luajit testIconv.lua
或者写个bat耍帅的来运行一下:
@echo off echo -----Test iconv----- luajit iconvTest.lua pause执行结果:
before iconv:鎴戠埍姹夊瓧~鎴戠敤UTF-8... after iconv:我爱汉字~我用UTF-8...
事已至此,当天晚上就这么结束了……
第二天起来感觉好别扭……为啥open后面非要跟个close,多别扭啊……我看lua Iconv的示例里面不用close啊……
然后看它的源码,发现它用了带__gc函数的元表,然而我不记得lua table有这个元表函数啊……
查了一下lua文档,发现只有C语言中定义的C Type有这个元表函数……感觉是不是应该放弃?
当然不是……我感觉它能行我肯定也能行……我又去查了luajit文档,主要是ffi的函数,到最后终于找到了方法……
ctype = ffi.metatype(ct, metatable) cdata = ffi.gc(cdata, finalizer)这两个函数很关键,一个是给c type的复杂类型(结构体、共用体等)添加元表的,其中包括__gc元表函数;另一个是给C Data添加析构函数的,我仿佛看到了救星
写一段代码试一下呗~
local ffi = require("ffi"); local a = ffi.new("int[1]",1); ffi.gc(a,(function(self) print(self[0]); print("I'll over~goodbye~"); end)); print("wait a while~"); print("let's go die~");输出结果:
wait a while~ let's go die~ 1 I'll over~goodbye~看来效果拔群啊~
于是我今天晚上又花了一晚上的时间改写了上面的iconv.lua
local ffi = require("ffi"); ffi.cdef[[ long libiconv_open(const char* tocode, const char* fromcode); long libiconv(long cd, char** inbuf, long *inbytesleft, char** outbuf, long *outbytesleft); long libiconv_close(long cd); ]]; local ICONV = ffi.load("iconv.dll"); local iconv = { _cd = nil, BUFFER_LENGTH = 4096, openHandle = function(self,tocode,fromcode) if type(tocode) ~= "string" or type(fromcode) ~= "string" then error("paramater error,please input string!", 2); return false; end local o = nil; if self._cd ~= nil then if self._cd[0] ~= -1 then error("please close it first!", 2); return false; end o = self; else o = {_cd = ffi.new("long[1]",-1)}; setmetatable(o, {__index = self}); end o._cd[0] = ICONV.libiconv_open(tocode,fromcode); ffi.gc(o._cd,o.__gc); if o._cd[0] == -1 then return false; end return o; end, iconv = function(self,str) local inLen = string.len(str); local insize = ffi.new("long[1]",inLen); local instr = ffi.new("char[?]",inLen+1,str); local inptr = ffi.new("char*[1]",instr); local outstr = ffi.new("char[?]",self.BUFFER_LENGTH+1); local outptr = ffi.new("char*[1]",outstr); local outsize = ffi.new("long[1]",self.BUFFER_LENGTH); local err = ICONV.libiconv(self._cd[0],inptr,insize,outptr,outsize); if err == -1 then return false,nil; end local out = ffi.string(outstr,self.BUFFER_LENGTH - outsize[0]); return true,out; end, __gc = function(_cd) --print("gc running!"); if _cd ~= nil and _cd[0] ~= -1 then ICONV.libiconv_close(_cd[0]); end end, closeHandle = function(self) if self._cd == nil or self._cd[0] == -1 then error("please open it first!", 2); return false; end ICONV.libiconv_close(self._cd[0]); self._cd[0] = -1; end }; return iconv;于是上面的testIconv.lua变成这样了:
local Iconv = require("iconv") local togbk = Iconv:openHandle("gbk", "utf-8"); if not togbk then print("create handle failed!"); return; end; local succ=nil; local value = "我爱汉字~我用UTF-8..." print("before iconv:"..value) succ,value = togbk:iconv(value); --togbk:closeHandle(); togbk = nil;--当没有引用时自动释放,避免了内存泄漏,当然也可以手动调用togbk:closeHandle() if not succ then value = nil; end print("after iconv:"..tostring(value));这种情况下不用各种担心open之后必须要close什么的,也不用非得强迫症似的非要置nil,一切都是那么自然……写C语言或者java语言等等的程絮媛们估计都烦透了拖家带口的open函数……现在才体会到lua这种脚本语言的漂亮之处~