-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement initial version for websocket module
- Loading branch information
1 parent
8a7007e
commit 73a01ce
Showing
9 changed files
with
1,448 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
// Module for websockets | ||
|
||
// Example usage: | ||
// ws = websocket.createClient() | ||
// ws:onConnection(function() ws:send('hi') end) | ||
// ws:onReceive(function(_, data, opcode) print(data) end) | ||
// ws:onClose(function(_, reasonCode) print('ws closed', reasonCode) end) | ||
// ws:connect('ws://example.com') | ||
|
||
#include "lmem.h" | ||
#include "lualib.h" | ||
#include "lauxlib.h" | ||
#include "platform.h" | ||
#include "module.h" | ||
|
||
#include "c_types.h" | ||
#include "c_string.h" | ||
|
||
#include "websocketclient.h" | ||
|
||
#define METATABLE_WSCLIENT "websocket.client" | ||
|
||
typedef struct ws_data { | ||
int self_ref; | ||
int onConnection; | ||
int onReceive; | ||
int onClose; | ||
} ws_data; | ||
|
||
static void websocketclient_onConnectionCallback(ws_info *ws) { | ||
NODE_DBG("websocketclient_onConnectionCallback\n"); | ||
|
||
lua_State *L = lua_getstate(); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
luaL_error(L, "Client websocket is nil.\n"); | ||
return; | ||
} | ||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (data->onConnection != LUA_NOREF) { | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onConnection); // load the callback function | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument | ||
lua_call(L, 1, 0); | ||
} | ||
} | ||
|
||
static void websocketclient_onReceiveCallback(ws_info *ws, char *message, int opCode) { | ||
NODE_DBG("websocketclient_onReceiveCallback\n"); | ||
|
||
lua_State *L = lua_getstate(); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
luaL_error(L, "Client websocket is nil.\n"); | ||
return; | ||
} | ||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (data->onReceive != LUA_NOREF) { | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onReceive); // load the callback function | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument | ||
lua_pushstring(L, message); // #2 callback argument | ||
lua_pushnumber(L, opCode); // #3 callback argument | ||
lua_call(L, 3, 0); | ||
} | ||
} | ||
|
||
static void websocketclient_onCloseCallback(ws_info *ws, int errorCode) { | ||
NODE_DBG("websocketclient_onCloseCallback\n"); | ||
|
||
lua_State *L = lua_getstate(); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
luaL_error(L, "Client websocket is nil.\n"); | ||
return; | ||
} | ||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (data->onClose != LUA_NOREF) { | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onClose); // load the callback function | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->self_ref); // pass itself, #1 callback argument | ||
lua_pushnumber(L, errorCode); // pass the error code, #2 callback argument | ||
lua_call(L, 2, 0); | ||
} | ||
|
||
// free self-reference to allow gc (no futher callback will be called until next ws:connect()) | ||
lua_gc(L, LUA_GCSTOP, 0); // required to avoid freeing ws_data | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->self_ref); | ||
data->self_ref = LUA_NOREF; | ||
lua_gc(L, LUA_GCRESTART, 0); | ||
} | ||
|
||
static int websocket_createClient(lua_State *L) { | ||
NODE_DBG("websocket_createClient\n"); | ||
|
||
// create user data | ||
ws_data *data = (ws_data *) luaM_malloc(L, sizeof(ws_data)); | ||
data->onConnection = LUA_NOREF; | ||
data->onReceive = LUA_NOREF; | ||
data->onClose = LUA_NOREF; | ||
data->self_ref = LUA_NOREF; // only set when ws:connect is called | ||
|
||
ws_info *ws = (ws_info *) lua_newuserdata(L, sizeof(ws_info)); | ||
ws->connectionState = 0; | ||
ws->onConnection = &websocketclient_onConnectionCallback; | ||
ws->onReceive = &websocketclient_onReceiveCallback; | ||
ws->onFailure = &websocketclient_onCloseCallback; | ||
ws->reservedData = data; | ||
|
||
// set its metatable | ||
luaL_getmetatable(L, METATABLE_WSCLIENT); | ||
lua_setmetatable(L, -2); | ||
|
||
return 1; | ||
} | ||
|
||
static int websocketclient_onConnection(lua_State *L) { | ||
NODE_DBG("websocketclient_onConnection\n"); | ||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) { | ||
return luaL_typerror(L, 2, "function or nil"); | ||
} | ||
|
||
if (data->onConnection != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection); | ||
data->onConnection = LUA_NOREF; | ||
} | ||
|
||
if (lua_type(L, 2) != LUA_TNIL) { | ||
lua_pushvalue(L, 2); // copy argument (func) to the top of stack | ||
data->onConnection = luaL_ref(L, LUA_REGISTRYINDEX); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int websocketclient_onReceive(lua_State *L) { | ||
NODE_DBG("websocketclient_onReceive\n"); | ||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) { | ||
return luaL_typerror(L, 2, "function or nil"); | ||
} | ||
|
||
if (data->onReceive != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onReceive); | ||
data->onReceive = LUA_NOREF; | ||
} | ||
|
||
if (lua_type(L, 2) != LUA_TNIL) { | ||
lua_pushvalue(L, 2); // copy argument (func) to the top of stack | ||
data->onReceive = luaL_ref(L, LUA_REGISTRYINDEX); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int websocketclient_onClose(lua_State *L) { | ||
NODE_DBG("websocketclient_onClose\n"); | ||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (lua_type(L, 2) != LUA_TNIL && lua_type(L, 2) != LUA_TFUNCTION && lua_type(L, 2) != LUA_TLIGHTFUNCTION) { | ||
return luaL_typerror(L, 2, "function or nil"); | ||
} | ||
|
||
if (data->onClose != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onClose); | ||
data->onClose = LUA_NOREF; | ||
} | ||
|
||
if (lua_type(L, 2) != LUA_TNIL) { | ||
lua_pushvalue(L, 2); // copy argument (func) to the top of stack | ||
data->onClose = luaL_ref(L, LUA_REGISTRYINDEX); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static int websocketclient_connect(lua_State *L) { | ||
NODE_DBG("websocketclient_connect is called.\n"); | ||
|
||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (ws->connectionState != 0 && ws->connectionState != 4) { | ||
return luaL_error(L, "Websocket already connecting or connected.\n"); | ||
} | ||
ws->connectionState = 0; | ||
|
||
lua_pushvalue(L, 1); // copy userdata to the top of stack to allow ref | ||
data->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); | ||
|
||
const char *url = luaL_checkstring(L, 2); | ||
ws_connect(ws, url); | ||
|
||
return 0; | ||
} | ||
|
||
static int websocketclient_send(lua_State *L) { | ||
NODE_DBG("websocketclient_send is called.\n"); | ||
|
||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (ws->connectionState != 3) { | ||
// should this be an onFailure callback instead? | ||
return luaL_error(L, "Websocket isn't connected.\n"); | ||
} | ||
|
||
int msgLength; | ||
const char *msg = luaL_checklstring(L, 2, &msgLength); | ||
|
||
int opCode = 1; // default: text message | ||
if (lua_gettop(L) == 3) { | ||
opCode = luaL_checkint(L, 3); | ||
} | ||
|
||
ws_send(ws, opCode, msg, (unsigned short) msgLength); | ||
return 0; | ||
} | ||
|
||
static int websocketclient_close(lua_State *L) { | ||
NODE_DBG("websocketclient_close.\n"); | ||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
|
||
ws_close(ws); | ||
return 0; | ||
} | ||
|
||
static int websocketclient_gc(lua_State *L) { | ||
NODE_DBG("websocketclient_gc\n"); | ||
|
||
ws_info *ws = (ws_info *) luaL_checkudata(L, 1, METATABLE_WSCLIENT); | ||
luaL_argcheck(L, ws, 1, "Client websocket expected"); | ||
|
||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection); | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onReceive); | ||
|
||
if (data->onClose != LUA_NOREF) { | ||
if (ws->connectionState != 4) { // only call if connection open | ||
lua_rawgeti(L, LUA_REGISTRYINDEX, data->onClose); | ||
|
||
lua_pushnumber(L, -100); | ||
lua_call(L, 1, 0); | ||
} | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onClose); | ||
} | ||
|
||
if (data->self_ref != LUA_NOREF) { | ||
lua_gc(L, LUA_GCSTOP, 0); // required to avoid freeing ws_data | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->self_ref); | ||
data->self_ref = LUA_NOREF; | ||
lua_gc(L, LUA_GCRESTART, 0); | ||
} | ||
|
||
NODE_DBG("freeing lua data\n"); | ||
luaM_free(L, data); | ||
NODE_DBG("done freeing lua data\n"); | ||
|
||
return 0; | ||
} | ||
|
||
static const LUA_REG_TYPE websocket_map[] = | ||
{ | ||
{ LSTRKEY("createClient"), LFUNCVAL(websocket_createClient) }, | ||
{ LNILKEY, LNILVAL } | ||
}; | ||
|
||
static const LUA_REG_TYPE websocketclient_map[] = | ||
{ | ||
{ LSTRKEY("onConnection"), LFUNCVAL(websocketclient_onConnection) }, | ||
{ LSTRKEY("onReceive"), LFUNCVAL(websocketclient_onReceive) }, | ||
{ LSTRKEY("onClose"), LFUNCVAL(websocketclient_onClose) }, | ||
{ LSTRKEY("connect"), LFUNCVAL(websocketclient_connect) }, | ||
{ LSTRKEY("send"), LFUNCVAL(websocketclient_send) }, | ||
{ LSTRKEY("close"), LFUNCVAL(websocketclient_close) }, | ||
{ LSTRKEY("__gc" ), LFUNCVAL(websocketclient_gc) }, | ||
{ LSTRKEY("__index"), LROVAL(websocketclient_map) }, | ||
{ LNILKEY, LNILVAL } | ||
}; | ||
|
||
int loadWebsocketModule(lua_State *L) { | ||
luaL_rometatable(L, METATABLE_WSCLIENT, (void *) websocketclient_map); | ||
|
||
return 0; | ||
} | ||
|
||
NODEMCU_MODULE(WEBSOCKET, "websocket", websocket_map, loadWebsocketModule); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
|
||
############################################################# | ||
# Required variables for each makefile | ||
# Discard this section from all parent makefiles | ||
# Expected variables (with automatic defaults): | ||
# CSRCS (all "C" files in the dir) | ||
# SUBDIRS (all subdirs with a Makefile) | ||
# GEN_LIBS - list of libs to be generated () | ||
# GEN_IMAGES - list of images to be generated () | ||
# COMPONENTS_xxx - a list of libs/objs in the form | ||
# subdir/lib to be extracted and rolled up into | ||
# a generated lib/image xxx.a () | ||
# | ||
ifndef PDIR | ||
GEN_LIBS = libwebsocket.a | ||
endif | ||
|
||
STD_CFLAGS=-std=gnu11 -Wimplicit | ||
|
||
############################################################# | ||
# Configuration i.e. compile options etc. | ||
# Target specific stuff (defines etc.) goes in here! | ||
# Generally values applying to a tree are captured in the | ||
# makefile at its root level - these are then overridden | ||
# for a subtree within the makefile rooted therein | ||
# | ||
#DEFINES += | ||
|
||
############################################################# | ||
# Recursion Magic - Don't touch this!! | ||
# | ||
# Each subtree potentially has an include directory | ||
# corresponding to the common APIs applicable to modules | ||
# rooted at that subtree. Accordingly, the INCLUDE PATH | ||
# of a module can only contain the include directories up | ||
# its parent path, and not its siblings | ||
# | ||
# Required for each makefile to inherit from the parent | ||
# | ||
|
||
INCLUDES := $(INCLUDES) -I $(PDIR)include | ||
INCLUDES += -I ./ | ||
INCLUDES += -I ./include | ||
INCLUDES += -I ../include | ||
INCLUDES += -I ../libc | ||
INCLUDES += -I ../../include | ||
PDIR := ../$(PDIR) | ||
sinclude $(PDIR)Makefile | ||
|
Oops, something went wrong.