FS-11676 add JSON support to mod_lua

This commit is contained in:
Seven Du 2018-12-15 19:03:44 +08:00 committed by Andrey Volk
parent 78bbe8f5d9
commit 813771f737
9 changed files with 512 additions and 0 deletions

View File

@ -1978,6 +1978,7 @@ AC_CONFIG_FILES([Makefile
src/mod/formats/mod_portaudio_stream/Makefile
src/mod/languages/mod_java/Makefile
src/mod/languages/mod_lua/Makefile
src/mod/languages/mod_lua/test/Makefile
src/mod/languages/mod_managed/Makefile
src/mod/languages/mod_perl/Makefile
src/mod/languages/mod_python/Makefile

View File

@ -23,3 +23,4 @@ mod_lua_wrap.cpp: mod_lua_extra.c
echo "#include \"mod_lua_extra.c\"" >> mod_lua_wrap.cpp
patch -s -p0 -i hack.diff
SUBDIRS=. test

View File

@ -19,7 +19,19 @@
%}
%typemap(in, checkfn="lua_istable") SWIGLUA_TABLE {
$1.L = L;
$1.idx = $input;
}
%typemap(typecheck) SWIGLUA_TABLE {
$1 = lua_istable(L, $input);
}
%typemap(out) cJSON * {
SWIG_arg += LUA::JSON::cJSON2LuaTable(L, result);
cJSON_Delete(result);
}
/* Lua function typemap */
%typemap(in, checkfn = "lua_isfunction") SWIGLUA_FN {
@ -43,6 +55,11 @@
%newobject API::execute;
%newobject API::executeString;
%newobject CoreSession::playAndDetectSpeech;
%newobject JSON;
%newobject JSON::encode;
%newobject JSON::decode;
%newobject JSON::execute;
%newobject JSON::execute2;
%include "typemaps.i"
%apply int *OUTPUT { int *len };
@ -107,5 +124,20 @@ class Dbh {
int load_extension(const char *extension);
};
class JSON {
private:
public:
JSON();
~JSON();
cJSON *decode(const char *str);
char *encode(SWIGLUA_TABLE lua_table);
cJSON *execute(const char *);
cJSON *execute(SWIGLUA_TABLE table);
char *execute2(const char *);
char *execute2(SWIGLUA_TABLE table);
void encode_empty_table_as_object(bool flag);
void return_unformatted_json(bool flag);
};
}

View File

@ -506,3 +506,234 @@ int Dbh::load_extension(const char *extension)
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "DBH NOT Connected.\n");
return 0;
}
JSON::JSON()
{
_encode_empty_table_as_object = true;
}
JSON::~JSON()
{
}
void JSON::encode_empty_table_as_object(bool flag)
{
_encode_empty_table_as_object = flag;
}
void JSON::return_unformatted_json(bool flag)
{
_return_unformatted_json = flag;
}
cJSON *JSON::decode(const char *str)
{
cJSON *json = cJSON_Parse(str);
return json;
}
#define ADDITEM(json, k, v) do { \
if (return_array > 0) { cJSON_AddItemToArray(json, v);} else { cJSON_AddItemToObject(json, k, v); } \
} while (0)
void JSON::LuaTable2cJSON(lua_State *L, int index, cJSON **json)
{
int return_array = -1;
// Push another reference to the table on top of the stack (so we know
// where it is, and this function can work for negative, positive and
// pseudo indices
lua_pushvalue(L, index);
// stack now contains: -1 => table
lua_pushnil(L);
// stack now contains: -1 => nil; -2 => table
while (lua_next(L, -2)) {
// stack now contains: -1 => value; -2 => key; -3 => table
// copy the key so that lua_tostring does not modify the original
lua_pushvalue(L, -2);
// stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
const char *key = lua_tostring(L, -1);
// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "key: %s\n", key);
if (return_array < 0) {
if (lua_isnumber(L, -1) && lua_tonumber(L, -1) == 1) {
return_array = 1;
*json = cJSON_CreateArray();
} else {
return_array = 0;
*json = cJSON_CreateObject();
}
}
switch_assert(*json);
if (lua_isnumber(L, -2)) {
ADDITEM(*json, key, cJSON_CreateNumber(lua_tonumber(L, -2)));
} else if (lua_isstring(L, -2)) {
ADDITEM(*json, key, cJSON_CreateString(lua_tostring(L, -2)));
} else if (lua_isboolean(L, -2)) {
ADDITEM(*json, key, cJSON_CreateBool(lua_toboolean(L, -2)));
} else if (lua_isnil(L, -2)) {
ADDITEM(*json, key, cJSON_CreateNull());
} else if (lua_isnone(L, -2)) {
// ADDITEM(*json, key, cJSON_CreateNone());
} else if (lua_istable(L, -2)) {
cJSON *child = NULL;
LuaTable2cJSON(L, -2, &child);
if (child) {
ADDITEM(*json, key, child);
} else { // empty table?
ADDITEM(*json, key, _encode_empty_table_as_object ? cJSON_CreateObject() : cJSON_CreateArray());
}
}
// pop value + copy of key, leaving original key
lua_pop(L, 2);
// stack now contains: -1 => key; -2 => table
}
// stack now contains: -1 => table (when lua_next returns 0 it pops the key
// but does not push anything.)
// Pop table
lua_pop(L, 1);
// Stack is now the same as it was on entry to this function
}
char *JSON::encode(SWIGLUA_TABLE lua_table)
{
lua_State *L = lua_table.L;
cJSON *json = NULL;
luaL_checktype(L, lua_table.idx, LUA_TTABLE);
LuaTable2cJSON(L, -1, &json);
if (!json) {
json = _encode_empty_table_as_object ? cJSON_CreateObject() : cJSON_CreateArray();
}
char *s = _return_unformatted_json ? cJSON_PrintUnformatted(json) : cJSON_Print(json);
cJSON_Delete(json);
return s;
}
int JSON::cJSON2LuaTable(lua_State *L, cJSON *json) {
cJSON *current = NULL;
if (!json) return 0;
lua_newtable(L);
if (json->type == cJSON_Object) {
for (current = json->child; current; current = current->next) {
// printf("type: %d %s\n", current->type, current->string);
switch (current->type) {
case cJSON_String:
lua_pushstring(L, current->valuestring);
lua_setfield(L, -2, current->string);
break;
case cJSON_Number:
lua_pushnumber(L, current->valuedouble);
lua_setfield(L, -2, current->string);
break;
case cJSON_True:
lua_pushboolean(L, 1);
lua_setfield(L, -2, current->string);
break;
case cJSON_False:
lua_pushboolean(L, 0);
lua_setfield(L, -2, current->string);
break;
case cJSON_Object:
JSON::cJSON2LuaTable(L, current);
lua_setfield(L, -2, current->string);
break;
case cJSON_Array:
JSON::cJSON2LuaTable(L, current);
lua_setfield(L, -2, current->string);
break;
default:
break;
}
}
} else if (json->type == cJSON_Array) {
int i = 1;
for (current = json->child; current; current = current->next) {
// printf("array type: %d %s\n", current->type, current->valuestring);
switch (current->type) {
case cJSON_String:
lua_pushinteger(L, i++);
lua_pushstring(L, current->valuestring);
lua_settable(L, -3);
break;
case cJSON_Number:
lua_pushinteger(L, i++);
lua_pushnumber(L, current->valuedouble);
lua_settable(L, -3);
break;
case cJSON_True:
lua_pushinteger(L, i++);
lua_pushboolean(L, 1);
lua_settable(L, -3);
break;
case cJSON_False:
lua_pushinteger(L, i++);
lua_pushboolean(L, 0);
lua_settable(L, -3);
break;
case cJSON_Object:
lua_pushinteger(L, i++);
JSON::cJSON2LuaTable(L, current);
lua_settable(L, -3);
break;
default:
break;
}
}
}
return 1;
}
cJSON *JSON::execute(const char *str)
{
cJSON *cmd = cJSON_Parse(str);
cJSON *reply = NULL;
if (cmd) {
switch_json_api_execute(cmd, NULL, &reply);
}
cJSON_Delete(cmd);
return reply;
}
cJSON *JSON::execute(SWIGLUA_TABLE table)
{
lua_State *L = table.L;
cJSON *json = NULL;
cJSON *reply = NULL;
luaL_checktype(L, table.idx, LUA_TTABLE);
LuaTable2cJSON(L, -1, &json);
switch_json_api_execute(json, NULL, &reply);
cJSON_Delete(json);
return reply;
}
char *JSON::execute2(const char *str)
{
cJSON *reply = execute(str);
return _return_unformatted_json ? cJSON_PrintUnformatted(reply) : cJSON_Print(reply);
}
char *JSON::execute2(SWIGLUA_TABLE table)
{
cJSON *reply = execute(table);
return _return_unformatted_json ? cJSON_PrintUnformatted(reply) : cJSON_Print(reply);
}

View File

@ -20,6 +20,13 @@ typedef struct{
#define SWIGLUA_FN_GET(fn) {lua_pushvalue(fn.L,fn.idx);}
typedef struct{
lua_State* L;
int idx;
}SWIGLUA_TABLE;
#define SWIGLUA_TABLE_GET(fn) {lua_pushvalue(fn.L,fn.idx);}
namespace LUA {
class Session:public CoreSession {
@ -73,5 +80,25 @@ namespace LUA {
void clear_error();
int load_extension(const char *extension);
};
class JSON {
private:
bool _encode_empty_table_as_object;
bool _return_unformatted_json;
public:
JSON();
~JSON();
cJSON *decode(const char *);
char *encode(SWIGLUA_TABLE table);
cJSON *execute(const char *);
cJSON *execute(SWIGLUA_TABLE table);
char *execute2(const char *);
char *execute2(SWIGLUA_TABLE table);
void encode_empty_table_as_object(bool flag);
void return_unformatted_json(bool flag);
static int cJSON2LuaTable(lua_State *L, cJSON *json);
void LuaTable2cJSON(lua_State *L, int index, cJSON **json);
};
}
#endif

View File

@ -0,0 +1,4 @@
bin_PROGRAMS = test_mod_lua
AM_CFLAGS = $(SWITCH_AM_CFLAGS)
AM_LDFLAGS = $(switch_builddir)/libfreeswitch.la -avoid-version -no-undefined $(SWITCH_AM_LDFLAGS)
TESTS = $(bin_PROGRAMS)

View File

@ -0,0 +1,38 @@
<?xml version="1.0"?>
<document type="freeswitch/xml">
<section name="configuration" description="Various Configuration">
<configuration name="modules.conf" description="Modules">
<modules>
<load module="mod_console"/>
<load module="mod_commands"/>
</modules>
</configuration>
<configuration name="console.conf" description="Console Logger">
<mappings>
<map name="all" value="console,debug,info,notice,warning,err,crit,alert"/>
</mappings>
<settings>
<param name="colorize" value="true"/>
<param name="loglevel" value="debug"/>
</settings>
</configuration>
<configuration name="timezones.conf" description="Timezones">
<timezones>
<zone name="GMT" value="GMT0" />
</timezones>
</configuration>
</section>
<section name="dialplan" description="Regex/XML Dialplan">
<context name="default">
<extension name="sample">
<condition>
<action application="info"/>
</condition>
</extension>
</context>
</section>
</document>

View File

@ -0,0 +1,112 @@
function serialize(o)
s = ""
if type(o) == "number" then
s = s .. o
elseif type(o) == "string" then
s = s .. string.format("%q", o)
elseif type(o) == "table" then
s = s .. "{\n"
for k, v in pairs(o) do
s = s .. ' ' .. k .. ' = '
s = s .. serialize(v)
s = s .. ",\n"
end
s = s .. "}"
elseif type(o) == "boolean" then
if o then
s = s .. "true"
else
s = s .. "false"
end
else
s = s .. " [" .. type(o) .. "]"
end
return s
end
json = freeswitch.JSON()
str = '{"a": "中文"}'
x = json:decode(str)
assert(x.a == '中文')
str = '{"a": "1", "b": 2, "c": true, "d": false, "e": [], "f": {}, "g": [1, 2, "3"], "h": {"a": 1, "b": 2}}'
x = json:decode(str)
freeswitch.consoleLog("INFO", serialize(x) .. "\n")
freeswitch.consoleLog("INFO", json:encode(x) .. '\n')
assert(x.a == "1")
assert(x.b == 2)
x = json:decode('["a", "b", true, false, null]')
freeswitch.consoleLog("INFO", serialize(x) .. "\n")
assert(x[1] == "a")
x = json:decode('[]')
assert(x)
x = json:decode('{}')
assert(x)
x = json:decode('blah')
assert(x == nil)
s = json:encode({hello = "blah", seven="7", aa = {bb = "cc", ee="ff", more = {deep = "yes"}}, last="last", empty={}})
freeswitch.consoleLog("INFO", s .. "\n")
s = json:encode({"a", "b", "c"})
freeswitch.consoleLog("INFO", s .. "\n")
s = json:encode({a = 1, b = 2, c = 3, d=true, e=false, f=nil})
freeswitch.consoleLog("INFO", s .. "\n")
json:return_unformatted_json(true);
s = json:encode({})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == "{}")
json:encode_empty_table_as_object(false);
s = json:encode({})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == "[]")
s = json:encode({[1] = "a"})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == '["a"]')
s = json:encode({"a", "b", "c"})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == '["a","b","c"]')
-- sparse
s = json:encode({[3] = "c"})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == '{"3":"c"}')
s = json:encode({{name = "seven"}, {name="nine"}})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == '[{"name":"seven"},{"name":"nine"}]')
s = json:encode({{name = "中文"}, {["中文"]="也行"}})
freeswitch.consoleLog("INFO", s .. "\n")
assert(s == '[{"name":"中文"},{"中文":"也行"}]')
json:encode_empty_table_as_object(true);
cmd = {command="status", data={}}
ret = json:execute(cmd)
freeswitch.consoleLog("INFO", serialize(ret) .. "\n")
ret = json:execute(json:encode(cmd))
freeswitch.consoleLog("INFO", serialize(ret) .. "\n")
ret = json:execute2(cmd)
freeswitch.consoleLog("INFO", ret .. "\n")
ret = json:execute2(json:encode(cmd))
freeswitch.consoleLog("INFO", ret .. "\n")
-- assert(false)
stream:write("+OK")

View File

@ -0,0 +1,66 @@
/*
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2005-2018, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is
* Seven Du <dujinfang@gmail.com>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
*
* test_mod_lua -- mod_lua test
*
*/
#include <test/switch_test.h>
FST_CORE_BEGIN("conf")
{
FST_MODULE_BEGIN(mod_lua, mod_lua_test)
{
FST_SETUP_BEGIN()
{
fst_requires_module("mod_lua");
}
FST_SETUP_END()
FST_TEST_BEGIN(json_test)
{
switch_stream_handle_t stream = { 0 };
SWITCH_STANDARD_STREAM(stream);
switch_api_execute("lua", "test_json.lua", NULL, &stream);
if (stream.data) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "LUA DATA: %s\n", stream.data);
fst_check(strstr(stream.data, "+OK") == stream.data);
free(stream.data);
}
}
FST_TEST_END()
FST_TEARDOWN_BEGIN()
{
}
FST_TEARDOWN_END()
}
FST_MODULE_END()
}
FST_CORE_END()