-
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 96687c4
Showing
9 changed files
with
1,488 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,332 @@ | ||
// 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) { | ||
NODE_DBG("got an onConnection func! !\n"); | ||
|
||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
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_error(L, "bad argument #2 to 'onConnection' (function or nil expected, got something else)"); | ||
} | ||
|
||
if (data->onConnection != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection); | ||
} | ||
|
||
if (lua_type(L, 2) == LUA_TNIL) { | ||
data->onConnection = LUA_NOREF; | ||
} else { | ||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
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_error(L, "bad argument #2 to 'onReceive' (function or nil expected, got something else)"); | ||
} | ||
|
||
if (data->onReceive != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onReceive); | ||
} | ||
|
||
if (lua_type(L, 2) == LUA_TNIL) { | ||
data->onReceive = LUA_NOREF; | ||
} else { | ||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
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_error(L, "bad argument #2 to 'onClose' (function or nil expected, got something else)"); | ||
} | ||
|
||
if (data->onClose != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onClose); | ||
} | ||
|
||
if (lua_type(L, 2) == LUA_TNIL) { | ||
data->onClose = LUA_NOREF; | ||
} else { | ||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
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"); | ||
|
||
if (ws == NULL || ws->reservedData == NULL) { | ||
return luaL_error(L, "Client websocket is nil.\n"); | ||
} | ||
ws_data *data = (ws_data *) ws->reservedData; | ||
|
||
if (data->onConnection != LUA_NOREF) { | ||
luaL_unref(L, LUA_REGISTRYINDEX, data->onConnection); | ||
} | ||
|
||
if (data->onReceive != LUA_NOREF) { | ||
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); |
Oops, something went wrong.