diff --git a/example/example.lua b/example/example.lua new file mode 100755 index 0000000..ddf569a --- /dev/null +++ b/example/example.lua @@ -0,0 +1,83 @@ +#!/usr/bin/lua + +local ev = require "ev" +local uwsc = require "uwsc" +local loop = ev.Loop.default + +local url = "ws://localhost:8082/echo" +local PING_INTERVAL = 5 +local auto_reconnect = true +local RECONNECT_INTERVAL = 5 +local do_connect = nil +local sender = nil + +local function stop_send() + if sender then + sender:stop(loop) + sender = nil + end +end + +local function on_open(c) + print(loop:now(), "open ok") + + sender = ev.Timer.new(function(loop, timer, revents) + c:send_text("Text Message - " .. os.time()) + c:send_binary("Binary Message - " .. os.time()) + end, 0.5, 1) + + sender:start(loop) +end + +local function start_reconnect() + if not auto_reconnect then + loop:unloop() + return + end + + ev.Timer.new(function() + do_connect() + end, RECONNECT_INTERVAL):start(loop) +end + +do_connect = function() + local c, err = uwsc.new(url, PING_INTERVAL) + if not c then + print(loop:now(), err) + return + end + + c:on("open", function() + on_open(c) + end) + + c:on("message", function(data, is_binary) + print(loop:now(), "Received message:", data, "is binary:", is_binary) + end) + + c:on("close", function(code, reason) + print(loop:now(), "Closed by peer:", code, reason) + stop_send() + start_reconnect() + end) + + c:on("error", function(err, msg) + print(loop:now(), "Error occurred:", err, msg) + stop_send() + start_reconnect() + end) +end + +ev.Signal.new(function() + loop:unloop() +end, ev.SIGINT):start(loop) + +do_connect() + +-- major minor patch +local version = string.format("%d.%d.%d", uwsc.version()) +print(loop:now(), "Version:", version) + +loop:loop() + +print(loop:now(), "Normal quit") \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4753aaf..9eb0956 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,7 +9,7 @@ set(UWSC_VERSION_PATCH 2) # Check the third party Libraries find_package(Libev REQUIRED) -include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBEV_INCLUDE_DIR}) +include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} ${LIBEV_INCLUDE_DIR}) set(EXTRA_LIBS ${LIBEV_LIBRARY} dl) set(SOURCE_FILES uwsc.c log.c utils.c buffer.c sha1.c base64.c ssl.c) @@ -97,6 +97,8 @@ target_link_libraries(uwsc_s ${EXTRA_LIBS}) # configure a header file to pass some of the CMake settings to the source code configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +add_subdirectory(lua) + install( FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt new file mode 100644 index 0000000..d9d28f3 --- /dev/null +++ b/src/lua/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(Lua) + +set(UWSC_LUA_SUPPORT_DEFAULT "ON") + +if (NOT LUA_FOUND) + set(BUILD_LUA_DEFAULT "OFF") +endif (NOT LUA_FOUND) + +option(UMQTT_LUA_SUPPORT "Enable build of Lua module" ${UWSC_LUA_SUPPORT_DEFAULT}) + +if(UMQTT_LUA_SUPPORT) + include_directories(${LUA_INCLUDE_DIR}) + + add_library(uwsc-lua MODULE uwsc_lua.c) + set_target_properties(uwsc-lua PROPERTIES OUTPUT_NAME uwsc PREFIX "") + + target_link_libraries(uwsc-lua uwsc) + + install(TARGETS uwsc-lua + LIBRARY DESTINATION lib/lua/${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR} + ) +endif(UMQTT_LUA_SUPPORT) diff --git a/src/lua/uwsc_lua.c b/src/lua/uwsc_lua.c new file mode 100755 index 0000000..32a6c07 --- /dev/null +++ b/src/lua/uwsc_lua.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2019 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#include +#include +#include + +#include "utils.h" +#include "uwsc_lua.h" + +#define UWSC_MT "uqmtt" + +/* https://github.com/brimworks/lua-ev/blob/master/lua_ev.h#L33 */ +#define EV_LOOP_MT "ev{loop}" +#define EV_UNINITIALIZED_DEFAULT_LOOP (struct ev_loop *)1 + +static int uwsc_lua_version(lua_State *L) +{ + lua_pushinteger(L, UWSC_VERSION_MAJOR); + lua_pushinteger(L, UWSC_VERSION_MINOR); + lua_pushinteger(L, UWSC_VERSION_PATCH); + + return 3; +} + +static void uwsc_onmessage(struct uwsc_client *cli, void *data, size_t len, bool binary) +{ + struct uwsc_client_lua *cl = container_of(cli, struct uwsc_client_lua, cli); + lua_State *L = cl->L; + + lua_rawgeti(L, LUA_REGISTRYINDEX, cl->onmessage_ref); + if (!lua_isfunction(L, -1)) + return; + + lua_pushlstring(L, data, len); + lua_pushboolean(L, binary); + + lua_call(L, 2, 0); +} + +static void uwsc_onerror(struct uwsc_client *cli, int err, const char *msg) +{ + struct uwsc_client_lua *cl = container_of(cli, struct uwsc_client_lua, cli); + lua_State *L = cl->L; + + cl->connected = false; + + lua_rawgeti(L, LUA_REGISTRYINDEX, cl->onerror_ref); + if (!lua_isfunction(L, -1)) + return; + + lua_pushinteger(L, err); + lua_pushstring(L, msg); + + lua_call(L, 2, 0); +} + +static void uwsc_onclose(struct uwsc_client *cli, int code, const char *reason) +{ + struct uwsc_client_lua *cl = container_of(cli, struct uwsc_client_lua, cli); + lua_State *L = cl->L; + + cl->connected = false; + + lua_rawgeti(L, LUA_REGISTRYINDEX, cl->onclose_ref); + if (!lua_isfunction(L, -1)) + return; + + lua_pushinteger(L, code); + lua_pushstring(L, reason); + + lua_call(L, 2, 0); +} + +static void uwsc_onopen(struct uwsc_client *cli) +{ + struct uwsc_client_lua *cl = container_of(cli, struct uwsc_client_lua, cli); + lua_State *L = cl->L; + + cl->connected = true; + + lua_rawgeti(L, LUA_REGISTRYINDEX, cl->onopen_ref); + if (!lua_isfunction(L, -1)) + return; + + lua_call(L, 0, 0); +} + +static int uwsc_lua_new(lua_State *L) +{ + struct ev_loop *loop = NULL; + struct uwsc_client_lua *cl; + const char *url = lua_tostring(L, 1); + int ping_interval = lua_tointeger(L, 2); + const char *extra_header = NULL; + + if (lua_istable(L, 3)) { + struct ev_loop **tmp; + + lua_getfield(L, 3, "loop"); + if (!lua_isnil(L, -1)) { + tmp = luaL_checkudata(L, 2, EV_LOOP_MT); + if (*tmp != EV_UNINITIALIZED_DEFAULT_LOOP) + loop = *tmp; + } + + lua_getfield(L, 3, "extra_header"); + extra_header = lua_tostring(L, -1); + } + + cl = lua_newuserdata(L, sizeof(struct uwsc_client_lua)); + if (!cl) { + lua_pushnil(L); + lua_pushstring(L, "lua_newuserdata() failed"); + return 2; + } + + memset(cl, 0, sizeof(struct uwsc_client_lua)); + + luaL_getmetatable(L, UWSC_MT); + lua_setmetatable(L, -2); + + if (uwsc_init(&cl->cli, loop, url, ping_interval, extra_header) < 0) { + lua_pushnil(L); + lua_pushfstring(L, "uwsc_init() failed"); + return 2; + } + + cl->L = L; + cl->cli.onopen = uwsc_onopen; + cl->cli.onmessage = uwsc_onmessage; + cl->cli.onerror = uwsc_onerror; + cl->cli.onclose = uwsc_onclose; + + return 1; +} + +static int uwsc_lua_on(lua_State *L) +{ + struct uwsc_client_lua *cl = luaL_checkudata(L, 1, UWSC_MT); + const char *name = luaL_checkstring(L, 2); + int ref; + + luaL_checktype(L, 3, LUA_TFUNCTION); + + ref = luaL_ref(L, LUA_REGISTRYINDEX); + + if (!strcmp(name, "open")) + cl->onopen_ref = ref; + else if (!strcmp(name, "message")) + cl->onmessage_ref = ref; + else if (!strcmp(name, "error")) + cl->onerror_ref = ref; + else if (!strcmp(name, "close")) + cl->onclose_ref = ref; + else + luaL_argcheck(L, false, 2, "available event name: open message error close"); + + return 0; +} + +static int __uwsc_lua_send(lua_State *L, int op) +{ + struct uwsc_client_lua *cl = luaL_checkudata(L, 1, UWSC_MT); + size_t len; + const void *data = luaL_checklstring(L, 2, &len); + + cl->cli.send(&cl->cli, data, len, op); + + return 0; +} + +static int uwsc_lua_send(lua_State *L) +{ + int op = lua_tointeger(L, 3); + + return __uwsc_lua_send(L, op); +} + +static int uwsc_lua_send_text(lua_State *L) +{ + return __uwsc_lua_send(L, UWSC_OP_TEXT); +} + +static int uwsc_lua_send_binary(lua_State *L) +{ + return __uwsc_lua_send(L, UWSC_OP_BINARY); +} + +static int uwsc_lua_gc(lua_State *L) +{ + struct uwsc_client_lua *cl = luaL_checkudata(L, 1, UWSC_MT); + + if (cl->connected) { + cl->cli.send_close(&cl->cli, UWSC_CLOSE_STATUS_NORMAL, ""); + cl->cli.free(&cl->cli); + cl->connected = false; + } + + return 0; +} + +static const luaL_Reg uwsc_meta[] = { + {"on", uwsc_lua_on}, + {"send", uwsc_lua_send}, + {"send_text", uwsc_lua_send_text}, + {"send_binary", uwsc_lua_send_binary}, + {"__gc", uwsc_lua_gc}, + {NULL, NULL} +}; + +static const luaL_Reg uwsc_fun[] = { + {"new", uwsc_lua_new}, + {"version", uwsc_lua_version}, + {NULL, NULL} +}; + +int luaopen_uwsc(lua_State *L) +{ + /* metatable.__index = metatable */ + luaL_newmetatable(L, UWSC_MT); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, uwsc_meta, 0); + + lua_newtable(L); + luaL_setfuncs(L, uwsc_fun, 0); + + return 1; +} diff --git a/src/lua/uwsc_lua.h b/src/lua/uwsc_lua.h new file mode 100644 index 0000000..5382084 --- /dev/null +++ b/src/lua/uwsc_lua.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 Jianhui Zhao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +#ifndef __UWSC_LUA_H +#define __UWSC_LUA_H + +#include +#include + +#include "uwsc.h" + +/* Compatibility defines */ +#if LUA_VERSION_NUM <= 501 + +/* NOTE: this only works if nups == 0! */ +#define luaL_setfuncs(L, fns, nups) luaL_register((L), NULL, (fns)) + +#endif + +struct uwsc_client_lua { + lua_State *L; + + struct uwsc_client cli; + bool connected; + + int onopen_ref; + int onmessage_ref; + int onerror_ref; + int onclose_ref; +}; + +#endif \ No newline at end of file