FS-11676 add JSON support to mod_lua
This commit is contained in:
parent
78bbe8f5d9
commit
813771f737
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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>
|
|
@ -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")
|
|
@ -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()
|
Loading…
Reference in New Issue