├── .gitignore ├── examples ├── wss_server │ ├── .gitignore │ ├── package.json │ ├── package-lock.json │ └── index.js ├── ws.dll ├── ws_server.lua ├── ws_client.lua └── wss_client.lua ├── .gitmodules ├── src ├── lua_ws.cpp ├── lua_ws_server.cpp ├── lua_ws_client.cpp ├── lua_wss_client.cpp └── lua_ws_server_channel.cpp ├── include ├── lua_ws_server.hpp ├── lua_ws_client.hpp ├── lua_wss_client.hpp └── lua_ws_server_channel.hpp ├── CMakeLists.txt ├── .vscode └── settings.json ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /examples/wss_server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | keys -------------------------------------------------------------------------------- /examples/ws.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holywyvern/love-ws/HEAD/examples/ws.dll -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/simple-websocket-server"] 2 | path = vendor/simple-websocket-server 3 | url = https://gitlab.com/eidheim/Simple-WebSocket-Server.git 4 | -------------------------------------------------------------------------------- /examples/wss_server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wss_server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ws": "^5.1.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/ws_server.lua: -------------------------------------------------------------------------------- 1 | local ws = require("ws") 2 | 3 | local server = ws.newServer(8080) 4 | 5 | local channel = server:getChannel("^/test/?$") 6 | 7 | server:start() 8 | 9 | while true do 10 | local ev = channel:checkQueue() 11 | if ev then 12 | print("connection '" .. ev.connection .. "' " .. ev.type .. " " .. ev.message) 13 | if ev.type == "message" then 14 | channel:send(ev.connection, ev.message); 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /examples/ws_client.lua: -------------------------------------------------------------------------------- 1 | local ws = require("ws") 2 | 3 | local client = ws.newClient("localhost:8080/test") 4 | 5 | client:connect() 6 | 7 | local connected = true 8 | 9 | while connected do 10 | local ev = client:checkQueue() 11 | if ev then 12 | print(ev.type .. ": " .. ev.message) 13 | if ev.type == "error" or ev.type == "close" then 14 | connected = false 15 | end 16 | if ev.type == "open" then 17 | client:sendMessage("hello") 18 | end 19 | end 20 | end 21 | 22 | print("Connection closed") -------------------------------------------------------------------------------- /examples/wss_client.lua: -------------------------------------------------------------------------------- 1 | local ws = require("ws") 2 | 3 | local client = ws.newTlsClient("localhost:8080/test", false) 4 | 5 | client:connect() 6 | 7 | local connected = true 8 | 9 | while connected do 10 | local ev = client:checkQueue() 11 | if ev then 12 | print(ev.type .. ": " .. ev.message) 13 | if ev.type == "error" or ev.type == "close" then 14 | connected = false 15 | end 16 | if ev.type == "open" then 17 | client:sendMessage("hello") 18 | end 19 | end 20 | end 21 | 22 | print("Connection closed") -------------------------------------------------------------------------------- /src/lua_ws.cpp: -------------------------------------------------------------------------------- 1 | #include "lua.hpp" 2 | #include "lua_ws_client.hpp" 3 | #include "lua_wss_client.hpp" 4 | #include "lua_ws_server.hpp" 5 | #include "lua_ws_server_channel.hpp" 6 | 7 | #if defined(_WIN32) || defined(WIN32) 8 | #define LOVE_WS_EXPORT __declspec(dllexport) 9 | #else 10 | #define LOVE_WS_EXPORT 11 | #endif 12 | 13 | extern "C" LUALIB_API LOVE_WS_EXPORT int 14 | luaopen_ws(lua_State *L) 15 | { 16 | lua_newtable(L); 17 | // Plain websockets 18 | LuaWsClient::setup(L); 19 | LuaWsServer::setup(L); 20 | LuaWsServerChannel::setup(L); 21 | // Secure websockets 22 | LuaWssClient::setup(L); 23 | return 1; 24 | } -------------------------------------------------------------------------------- /examples/wss_server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wss_server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async-limiter": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 10 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 11 | }, 12 | "ws": { 13 | "version": "5.1.1", 14 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", 15 | "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", 16 | "requires": { 17 | "async-limiter": "1.0.0" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/wss_server/index.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const fs = require('fs'); 3 | const path = require("path"); 4 | 5 | const WebSocket = require('ws'); 6 | 7 | // Remember to generate your keys and put it inside a keys folder. 8 | // For this example, they can be self silgned. 9 | // As an example, you may use this using OpenSSL: 10 | // openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout private.pem -out public.pem 11 | const server = https.createServer({ 12 | cert: fs.readFileSync(path.join(__dirname, 'keys', 'public.pem')), 13 | key: fs.readFileSync(path.join(__dirname, 'keys', 'private.pem')) 14 | }); 15 | 16 | const wss = new WebSocket.Server({ server }); 17 | 18 | wss.on('connection', function connection (ws) { 19 | console.log("User connected"); 20 | ws.on('message', function message (msg) { 21 | console.log(`Message from user: ${msg}.`); 22 | ws.send("I'll desconnect you in a while"); 23 | setTimeout(() => { 24 | ws.close(); 25 | }, 10000); 26 | }); 27 | }); 28 | 29 | server.listen(8080); -------------------------------------------------------------------------------- /include/lua_ws_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUA_WS_SERVER 2 | #define LUA_WS_SERVER 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "lua.hpp" 9 | #include "server_ws.hpp" 10 | 11 | extern "C" { 12 | typedef struct lua_State lua_State; 13 | } 14 | 15 | class WsServer : public SimpleWeb::SocketServer 16 | { 17 | private: 18 | boost::mutex _idMutex; 19 | std::map, std::string> _connectionIds; 20 | std::map> _idMap; 21 | public: 22 | std::shared_ptr thread; 23 | WsServer(int port); 24 | std::string getId(std::shared_ptr connection); 25 | }; 26 | 27 | class LuaWsServer 28 | { 29 | public: 30 | static WsServer *check(lua_State *L, int narg); 31 | static const char className[]; 32 | static const luaL_reg methods[]; 33 | static void setup(lua_State *L); 34 | static int create(lua_State *L); 35 | static int gc(lua_State *L); 36 | 37 | static int start(lua_State *L); 38 | static int stop(lua_State *L); 39 | }; 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/lua_ws_client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUA_WS_CLIENT 2 | #define LUA_WS_CLIENT 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "lua.hpp" 9 | #include "client_ws.hpp" 10 | 11 | class WsClientMessage { 12 | public: 13 | int type; 14 | std::string message; 15 | WsClientMessage(int type, std::string message); 16 | std::string getTypeName(); 17 | }; 18 | 19 | class WsClient : public SimpleWeb::SocketClient 20 | { 21 | public: 22 | WsClient(const std::string &server_port_path) noexcept; 23 | bool _serverOpen; 24 | boost::mutex _queueMutex; 25 | std::queue _messageQueue; 26 | std::shared_ptr thread; 27 | void popMessage(lua_State *L); 28 | void pushMessage(int type, std::string message); 29 | void sendMessage(std::string message); 30 | }; 31 | 32 | class LuaWsClient 33 | { 34 | public: 35 | static const char className[]; 36 | static const luaL_reg methods[]; 37 | // Lua VM functions 38 | static WsClient *check(lua_State *L, int narg); 39 | static void setup(lua_State *L); 40 | static int create(lua_State *L); 41 | static int gc(lua_State *L); 42 | // Lua Methods 43 | static int connect(lua_State *L); 44 | static int close(lua_State *L); 45 | static int checkQueue(lua_State *L); 46 | static int sendMessage(lua_State *L); 47 | static int isOpen(lua_State *L); 48 | }; 49 | 50 | #endif -------------------------------------------------------------------------------- /include/lua_wss_client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUA_WSS_CLIENT 2 | #define LUA_WSS_CLIENT 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "lua.hpp" 9 | #include "client_wss.hpp" 10 | 11 | class WssClientMessage { 12 | public: 13 | int type; 14 | std::string message; 15 | WssClientMessage(int type, std::string message); 16 | std::string getTypeName(); 17 | }; 18 | 19 | class WssClient : public SimpleWeb::SocketClient 20 | { 21 | public: 22 | WssClient(const std::string &server_port_path, bool certificate) noexcept; 23 | bool _serverOpen; 24 | boost::mutex _queueMutex; 25 | std::queue _messageQueue; 26 | std::shared_ptr thread; 27 | void popMessage(lua_State *L); 28 | void pushMessage(int type, std::string message); 29 | void sendMessage(std::string message); 30 | }; 31 | 32 | class LuaWssClient 33 | { 34 | public: 35 | static const char className[]; 36 | static const luaL_reg methods[]; 37 | // Lua VM functions 38 | static WssClient *check(lua_State *L, int narg); 39 | static void setup(lua_State *L); 40 | static int create(lua_State *L); 41 | static int gc(lua_State *L); 42 | // Lua Methods 43 | static int connect(lua_State *L); 44 | static int close(lua_State *L); 45 | static int checkQueue(lua_State *L); 46 | static int sendMessage(lua_State *L); 47 | static int isOpen(lua_State *L); 48 | }; 49 | 50 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | project (lua-ws) 4 | 5 | if(NOT MSVC) 6 | add_compile_options(-std=c++11 -Wall -Wextra -Wsign-conversion) 7 | else() 8 | add_compile_options(/W1) 9 | endif() 10 | 11 | add_library(ws MODULE 12 | src/lua_ws_client.cpp 13 | src/lua_ws_server.cpp 14 | src/lua_ws_server_channel.cpp 15 | src/lua_wss_client.cpp 16 | src/lua_ws.cpp 17 | ) 18 | 19 | target_include_directories(ws 20 | PUBLIC 21 | ${PROJECT_SOURCE_DIR}/include 22 | ) 23 | 24 | if(APPLE) 25 | set(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") 26 | endif() 27 | 28 | add_definitions( -DBOOST_ALL_NO_LIB ) 29 | set(Boost_USE_STATIC_LIBS ON) # only find static libs 30 | set(Boost_USE_STATIC_LIBS ON) 31 | set(Boost_USE_MULTITHREADED ON) 32 | set(Boost_USE_STATIC_RUNTIME OFF) 33 | 34 | find_package(OpenSSL REQUIRED) 35 | find_package(Lua REQUIRED) 36 | find_package(Boost 1.54.0 COMPONENTS system thread filesystem REQUIRED) 37 | 38 | target_include_directories(ws PUBLIC ${OPENSSL_INCLUDE_DIR}) 39 | target_include_directories(ws PUBLIC ${LUA_INCLUDE_DIR}) 40 | target_include_directories(ws PRIVATE ${Boost_INCLUDE_DIR}) 41 | target_include_directories(ws PRIVATE ${PROJECT_SOURCE_DIR}/vendor/simple-websocket-server) 42 | 43 | target_link_libraries(ws PRIVATE ${OPENSSL_LIBRARIES}) 44 | target_link_libraries(ws PRIVATE ${LUA_LIBRARIES}) 45 | target_link_libraries(ws PRIVATE ${Boost_LIBRARIES}) 46 | 47 | 48 | if(WIN32) 49 | target_link_libraries(ws PRIVATE ws2_32 wsock32) 50 | endif() -------------------------------------------------------------------------------- /include/lua_ws_server_channel.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LUA_WS_SERVER_CHANNEL 2 | #define LUA_WS_SERVER_CHANNEL 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "lua.hpp" 9 | #include "server_ws.hpp" 10 | #include "lua_ws_server.hpp" 11 | 12 | class WsServerMessage { 13 | public: 14 | std::string connectionId; 15 | int type; 16 | std::string message; 17 | WsServerMessage(std::string id, int type, std::string message); 18 | std::string getTypeName(); 19 | }; 20 | 21 | class WsServerChannel { 22 | private: 23 | WsServer *_server; 24 | boost::mutex _queueMutex; 25 | boost::mutex _idMutex; 26 | std::queue _messageQueue; 27 | std::string _endpoint; 28 | std::map, std::string> _connectionIds; 29 | std::map> _idMap; 30 | public: 31 | WsServerChannel(WsServer *server, const char *channel, size_t len); 32 | void popMessage(lua_State *L); 33 | void pushMessage(std::string connection, int type); 34 | void pushMessage(std::string connection, int type, std::string message); 35 | void removeConnection(std::string id); 36 | void removeConnection(std::shared_ptr connection); 37 | void sendMessage(std::string id, std::string message); 38 | void broadcast(std::string message); 39 | void disconnect(std::string id); 40 | std::string getId(std::shared_ptr connection); 41 | }; 42 | 43 | class LuaWsServerChannel { 44 | public: 45 | static WsServerChannel *check(lua_State *L, int narg); 46 | static const char className[]; 47 | static const luaL_reg methods[]; 48 | // Lua VM methods 49 | static void setup(lua_State *L); 50 | static int create(lua_State *L); 51 | static int gc(lua_State *L); 52 | // Lua methods 53 | static int disconnect(lua_State *L); 54 | static int send(lua_State *L); 55 | static int broadcast(lua_State *L); 56 | static int checkQueue(lua_State *L); 57 | }; 58 | 59 | #endif -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": false, 3 | "files.associations": { 4 | "system_error": "cpp", 5 | "xlocale": "cpp", 6 | "random": "cpp", 7 | "xlocmes": "cpp", 8 | "memory": "cpp", 9 | "regex": "cpp", 10 | "sstream": "cpp", 11 | "queue": "cpp", 12 | "xstring": "cpp", 13 | "iostream": "cpp", 14 | "mutex": "cpp", 15 | "algorithm": "cpp", 16 | "array": "cpp", 17 | "atomic": "cpp", 18 | "chrono": "cpp", 19 | "cmath": "cpp", 20 | "cstddef": "cpp", 21 | "cstdint": "cpp", 22 | "cstdio": "cpp", 23 | "cstdlib": "cpp", 24 | "cstring": "cpp", 25 | "ctime": "cpp", 26 | "cwchar": "cpp", 27 | "deque": "cpp", 28 | "exception": "cpp", 29 | "functional": "cpp", 30 | "initializer_list": "cpp", 31 | "iomanip": "cpp", 32 | "ios": "cpp", 33 | "iosfwd": "cpp", 34 | "istream": "cpp", 35 | "iterator": "cpp", 36 | "limits": "cpp", 37 | "list": "cpp", 38 | "locale": "cpp", 39 | "map": "cpp", 40 | "new": "cpp", 41 | "ostream": "cpp", 42 | "ratio": "cpp", 43 | "stdexcept": "cpp", 44 | "streambuf": "cpp", 45 | "string": "cpp", 46 | "thread": "cpp", 47 | "tuple": "cpp", 48 | "type_traits": "cpp", 49 | "typeinfo": "cpp", 50 | "unordered_map": "cpp", 51 | "unordered_set": "cpp", 52 | "utility": "cpp", 53 | "vector": "cpp", 54 | "xfacet": "cpp", 55 | "xfunctional": "cpp", 56 | "xhash": "cpp", 57 | "xiosbase": "cpp", 58 | "xlocbuf": "cpp", 59 | "xlocinfo": "cpp", 60 | "xlocmon": "cpp", 61 | "xlocnum": "cpp", 62 | "xloctime": "cpp", 63 | "xmemory": "cpp", 64 | "xmemory0": "cpp", 65 | "xstddef": "cpp", 66 | "xtr1common": "cpp", 67 | "xtree": "cpp", 68 | "xutility": "cpp", 69 | "xthread": "cpp" 70 | } 71 | } -------------------------------------------------------------------------------- /src/lua_ws_server.cpp: -------------------------------------------------------------------------------- 1 | #include "lua_ws_server.hpp" 2 | #include "lua_ws_server_channel.hpp" 3 | 4 | #include "lua.hpp" 5 | 6 | #ifndef lua_boxpointer 7 | #define lua_boxpointer(L,u) \ 8 | (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u)) 9 | #endif 10 | 11 | #ifndef lua_unboxpointer 12 | #define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i))) 13 | #endif 14 | 15 | #define method(class, name) \ 16 | { #name, class::name } 17 | 18 | static const std::string CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 19 | 20 | const luaL_reg LuaWsServer::methods[] = { 21 | {"getChannel", LuaWsServerChannel::create}, 22 | method(LuaWsServer, start), 23 | method(LuaWsServer, stop), 24 | {0, 0}}; 25 | 26 | const char LuaWsServer::className[] = "LuaWsServer"; 27 | 28 | static std::string 29 | generateUUID() { 30 | std::string uuid = std::string(36, ' '); 31 | int rnd = 0; 32 | int r = 0; 33 | 34 | uuid[8] = '-'; 35 | uuid[13] = '-'; 36 | uuid[18] = '-'; 37 | uuid[23] = '-'; 38 | 39 | uuid[14] = '4'; 40 | 41 | for (int i = 0; i < 36; i++) { 42 | if (i != 8 && i != 13 && i != 18 && i != 14 && i != 23) { 43 | if (rnd <= 0x02) { 44 | rnd = 0x2000000 + (std::rand() * 0x1000000) | 0; 45 | } 46 | rnd >>= 4; 47 | uuid[i] = CHARS[(i == 19) ? ((rnd & 0xf) & 0x3) | 0x8 : rnd & 0xf]; 48 | } 49 | } 50 | return uuid; 51 | } 52 | 53 | WsServer::WsServer(int port) : SimpleWeb::SocketServer() { 54 | this->config.port = port; 55 | this->thread = nullptr; 56 | } 57 | 58 | std::string 59 | WsServer::getId(std::shared_ptr connection) 60 | { 61 | boost::lock_guard lock{_idMutex}; 62 | if (_connectionIds.count(connection) < 1) { 63 | std::string uuid = generateUUID(); 64 | while (_idMap.count(uuid) > 0) { 65 | uuid = generateUUID(); 66 | } 67 | _connectionIds[connection] = uuid; 68 | _idMap[uuid] = connection; 69 | } 70 | return _connectionIds[connection]; 71 | } 72 | 73 | WsServer * 74 | LuaWsServer::check(lua_State *L, int narg) 75 | { 76 | luaL_checktype(L, narg, LUA_TUSERDATA); 77 | void *ud = luaL_checkudata(L, narg, className); 78 | if(!ud) luaL_typerror(L, narg, className); 79 | return *(WsServer**)ud; // unbox pointer 80 | } 81 | 82 | void LuaWsServer::setup(lua_State *L) 83 | { 84 | lua_newtable(L); 85 | int methodtable = lua_gettop(L); 86 | luaL_newmetatable(L, className); 87 | int metatable = lua_gettop(L); 88 | 89 | lua_pushliteral(L, "__metatable"); 90 | lua_pushvalue(L, methodtable); 91 | lua_settable(L, metatable); // hide metatable from Lua getmetatable() 92 | 93 | lua_pushliteral(L, "__index"); 94 | lua_pushvalue(L, methodtable); 95 | lua_settable(L, metatable); 96 | 97 | lua_pushliteral(L, "__gc"); 98 | lua_pushcfunction(L, LuaWsServer::gc); 99 | lua_settable(L, metatable); 100 | 101 | lua_pop(L, 1); // drop metatable 102 | 103 | luaL_openlib(L, 0, methods, 0); // fill methodtable 104 | lua_pop(L, 1); // drop methodtable 105 | 106 | lua_pushcfunction(L, LuaWsServer::create); 107 | lua_setfield(L, -2, "newServer"); 108 | } 109 | 110 | int LuaWsServer::create(lua_State *L) 111 | { 112 | lua_settop(L, 1); 113 | int port = luaL_checkinteger(L, 1); 114 | WsServer *server = new WsServer(port); 115 | lua_boxpointer(L, server); 116 | luaL_getmetatable(L, className); 117 | lua_setmetatable(L, -2); 118 | return 1; 119 | } 120 | 121 | int LuaWsServer::gc(lua_State *L) 122 | { 123 | WsServer *server = (WsServer*)lua_unboxpointer(L, 1); 124 | if (server->thread) { 125 | server->stop(); 126 | server->thread->join(); 127 | server->thread = nullptr; 128 | } 129 | delete server; 130 | return 0; 131 | } 132 | 133 | int LuaWsServer::start(lua_State *L) 134 | { 135 | WsServer * server = LuaWsServer::check(L, 1); 136 | server->thread = std::make_shared([=]() { 137 | server->start(); 138 | }); 139 | return 0; 140 | } 141 | 142 | int LuaWsServer::stop(lua_State *L) 143 | { 144 | WsServer * server = LuaWsServer::check(L, 1); 145 | server->stop(); 146 | if (server->thread) { 147 | server->thread->join(); 148 | server->thread = nullptr; 149 | } 150 | return 0; 151 | } -------------------------------------------------------------------------------- /src/lua_ws_client.cpp: -------------------------------------------------------------------------------- 1 | #include "lua_ws_client.hpp" 2 | 3 | #include "client_ws.hpp" 4 | #include "lua.hpp" 5 | 6 | #ifndef lua_boxpointer 7 | #define lua_boxpointer(L,u) \ 8 | (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u)) 9 | #endif 10 | 11 | #ifndef lua_unboxpointer 12 | #define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i))) 13 | #endif 14 | 15 | #define method(class, name) \ 16 | { #name, class::name } 17 | 18 | const luaL_reg LuaWsClient::methods[] = { 19 | method(LuaWsClient, connect), 20 | method(LuaWsClient, close), 21 | method(LuaWsClient, checkQueue), 22 | method(LuaWsClient, sendMessage), 23 | {0, 0}}; 24 | 25 | const char LuaWsClient::className[] = "LuaWsClient"; 26 | 27 | WsClientMessage::WsClientMessage(int type, std::string message) 28 | { 29 | this->type = type; 30 | this->message = message; 31 | } 32 | 33 | std::string WsClientMessage::getTypeName() 34 | { 35 | switch (this->type) 36 | { 37 | case 1: return std::string("message"); 38 | case 2: return std::string("open"); 39 | case 3: return std::string("close"); 40 | case 4: return std::string("error"); 41 | case 5: return std::string("connecting"); 42 | default: break; 43 | } 44 | return std::string("unknown"); 45 | } 46 | 47 | WsClient::WsClient(const std::string &server_port_path) noexcept : 48 | SimpleWeb::SocketClient(server_port_path) { 49 | this->thread = nullptr; 50 | } 51 | 52 | void 53 | WsClient::pushMessage(int type, std::string message) 54 | { 55 | boost::lock_guard lock{_queueMutex}; 56 | WsClientMessage msg(type, message); 57 | _messageQueue.push(msg); 58 | } 59 | 60 | void 61 | WsClient::popMessage(lua_State *L) 62 | { 63 | boost::lock_guard lock{_queueMutex}; 64 | if (_messageQueue.empty()) { 65 | lua_pushnil(L); 66 | } else { 67 | auto msg(_messageQueue.front()); 68 | lua_createtable(L, 0, 2); 69 | lua_pushstring(L, "type"); 70 | auto type = msg.getTypeName(); 71 | lua_pushlstring(L, type.data(), type.size()); 72 | lua_settable(L, -3); 73 | lua_pushstring(L, "message"); 74 | lua_pushlstring(L, msg.message.data(), msg.message.size()); 75 | lua_settable(L, -3); 76 | _messageQueue.pop(); 77 | } 78 | } 79 | 80 | void 81 | WsClient::sendMessage(std::string message) 82 | { 83 | this->connection->send(message); 84 | } 85 | 86 | WsClient * 87 | LuaWsClient::check(lua_State *L, int narg) 88 | { 89 | luaL_checktype(L, narg, LUA_TUSERDATA); 90 | void *ud = luaL_checkudata(L, narg, className); 91 | if(!ud) luaL_typerror(L, narg, className); 92 | return *(WsClient**)ud; // unbox pointer 93 | } 94 | 95 | void LuaWsClient::setup(lua_State *L) 96 | { 97 | lua_newtable(L); 98 | int methodtable = lua_gettop(L); 99 | luaL_newmetatable(L, className); 100 | int metatable = lua_gettop(L); 101 | 102 | lua_pushliteral(L, "__metatable"); 103 | lua_pushvalue(L, methodtable); 104 | lua_settable(L, metatable); // hide metatable from Lua getmetatable() 105 | 106 | lua_pushliteral(L, "__index"); 107 | lua_pushvalue(L, methodtable); 108 | lua_settable(L, metatable); 109 | 110 | lua_pushliteral(L, "__gc"); 111 | lua_pushcfunction(L, LuaWsClient::gc); 112 | lua_settable(L, metatable); 113 | 114 | lua_pop(L, 1); // drop metatable 115 | 116 | luaL_openlib(L, 0, methods, 0); // fill methodtable 117 | lua_pop(L, 1); // drop methodtable 118 | 119 | lua_pushcfunction(L, LuaWsClient::create); 120 | lua_setfield(L, -2, "newClient"); 121 | } 122 | 123 | int LuaWsClient::create(lua_State *L) 124 | { 125 | lua_settop(L, 1); 126 | size_t len; 127 | const char *url = luaL_checklstring(L, 1, &len); 128 | std::string ref(url, len); 129 | WsClient *client = new WsClient(url); 130 | client->_serverOpen = false; 131 | client->pushMessage(5, std::string("")); 132 | client->on_message = [=](std::shared_ptr connection, std::shared_ptr message) { 133 | client->pushMessage(1, message->string()); 134 | }; 135 | client->on_open = [=](std::shared_ptr connection) { 136 | client->_serverOpen = true; 137 | client->pushMessage(2, std::string("")); 138 | }; 139 | client->on_close = [=](std::shared_ptr connection, int status, const std::string & reason) { 140 | client->_serverOpen = false; 141 | client->pushMessage(3, std::string("")); 142 | }; 143 | client->on_error = [=](std::shared_ptr connection, const SimpleWeb::error_code &ec) { 144 | client->pushMessage(4, ec.message()); 145 | }; 146 | lua_boxpointer(L, client); 147 | luaL_getmetatable(L, className); 148 | lua_setmetatable(L, -2); 149 | return 1; 150 | } 151 | 152 | int LuaWsClient::gc(lua_State *L) 153 | { 154 | WsClient *ws = (WsClient*)lua_unboxpointer(L, 1); 155 | if (ws->_serverOpen) { 156 | ws->stop(); 157 | if (ws->thread) { 158 | ws->thread->join(); 159 | ws->thread = nullptr; 160 | } 161 | } 162 | delete ws; 163 | return 0; 164 | } 165 | 166 | int LuaWsClient::connect(lua_State *L) 167 | { 168 | WsClient *ws = LuaWsClient::check(L, 1); 169 | ws->thread = std::make_shared([=]() { 170 | ws->start(); 171 | }); 172 | return 0; 173 | } 174 | 175 | int LuaWsClient::close(lua_State *L) 176 | { 177 | WsClient *ws = LuaWsClient::check(L, 1); 178 | if (!ws->_serverOpen) { 179 | return 0; 180 | } 181 | ws->stop(); 182 | if (ws->thread) { 183 | ws->thread->join(); 184 | ws->thread = nullptr; 185 | } 186 | return 0; 187 | } 188 | 189 | int LuaWsClient::checkQueue(lua_State *L) 190 | { 191 | WsClient *ws = LuaWsClient::check(L, 1); 192 | ws->popMessage(L); 193 | return 1; 194 | } 195 | 196 | int LuaWsClient::sendMessage(lua_State *L) 197 | { 198 | WsClient *ws = LuaWsClient::check(L, 1); 199 | size_t len; 200 | const char *str = luaL_checklstring(L, 2, &len); 201 | std::string message(str, len); 202 | ws->sendMessage(message); 203 | return 0; 204 | } 205 | 206 | int LuaWsClient::isOpen(lua_State *L) 207 | { 208 | WsClient *ws = LuaWsClient::check(L, 1); 209 | lua_pushboolean(L, ws->_serverOpen ? 1 : 0); 210 | return 1; 211 | } -------------------------------------------------------------------------------- /src/lua_wss_client.cpp: -------------------------------------------------------------------------------- 1 | #include "lua_wss_client.hpp" 2 | 3 | #include "client_wss.hpp" 4 | #include "lua.hpp" 5 | 6 | #ifndef lua_boxpointer 7 | #define lua_boxpointer(L,u) \ 8 | (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u)) 9 | #endif 10 | 11 | #ifndef lua_unboxpointer 12 | #define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i))) 13 | #endif 14 | 15 | #define method(class, name) \ 16 | { #name, class::name } 17 | 18 | const luaL_reg LuaWssClient::methods[] = { 19 | method(LuaWssClient, connect), 20 | method(LuaWssClient, close), 21 | method(LuaWssClient, checkQueue), 22 | method(LuaWssClient, sendMessage), 23 | {0, 0}}; 24 | 25 | const char LuaWssClient::className[] = "LuaWssClient"; 26 | 27 | WssClientMessage::WssClientMessage(int type, std::string message) 28 | { 29 | this->type = type; 30 | this->message = message; 31 | } 32 | 33 | 34 | std::string WssClientMessage::getTypeName() 35 | { 36 | switch (this->type) 37 | { 38 | case 1: return std::string("message"); 39 | case 2: return std::string("open"); 40 | case 3: return std::string("close"); 41 | case 4: return std::string("error"); 42 | case 5: return std::string("connecting"); 43 | default: break; 44 | } 45 | return std::string("unknown"); 46 | } 47 | 48 | WssClient::WssClient(const std::string &server_port_path, bool validate_cert) noexcept : 49 | SimpleWeb::SocketClient(server_port_path, validate_cert) { 50 | this->thread = nullptr; 51 | } 52 | 53 | void 54 | WssClient::pushMessage(int type, std::string message) 55 | { 56 | boost::lock_guard lock{_queueMutex}; 57 | WssClientMessage msg(type, message); 58 | _messageQueue.push(msg); 59 | } 60 | 61 | void 62 | WssClient::popMessage(lua_State *L) 63 | { 64 | boost::lock_guard lock{_queueMutex}; 65 | if (_messageQueue.empty()) { 66 | lua_pushnil(L); 67 | } 68 | else { 69 | auto msg(_messageQueue.front()); 70 | lua_createtable(L, 0, 2); 71 | lua_pushstring(L, "type"); 72 | auto type = msg.getTypeName(); 73 | lua_pushlstring(L, type.data(), type.size()); 74 | lua_settable(L, -3); 75 | lua_pushstring(L, "message"); 76 | lua_pushlstring(L, msg.message.data(), msg.message.size()); 77 | lua_settable(L, -3); 78 | _messageQueue.pop(); 79 | } 80 | } 81 | 82 | void 83 | WssClient::sendMessage(std::string message) 84 | { 85 | this->connection->send(message); 86 | } 87 | 88 | 89 | WssClient * 90 | LuaWssClient::check(lua_State *L, int narg) 91 | { 92 | luaL_checktype(L, narg, LUA_TUSERDATA); 93 | void *ud = luaL_checkudata(L, narg, className); 94 | if (!ud) luaL_typerror(L, narg, className); 95 | return *(WssClient**)ud; // unbox pointer 96 | } 97 | 98 | void 99 | LuaWssClient::setup(lua_State *L) 100 | { 101 | lua_newtable(L); 102 | int methodtable = lua_gettop(L); 103 | luaL_newmetatable(L, className); 104 | int metatable = lua_gettop(L); 105 | 106 | lua_pushliteral(L, "__metatable"); 107 | lua_pushvalue(L, methodtable); 108 | lua_settable(L, metatable); // hide metatable from Lua getmetatable() 109 | 110 | lua_pushliteral(L, "__index"); 111 | lua_pushvalue(L, methodtable); 112 | lua_settable(L, metatable); 113 | 114 | lua_pushliteral(L, "__gc"); 115 | lua_pushcfunction(L, LuaWssClient::gc); 116 | lua_settable(L, metatable); 117 | 118 | lua_pop(L, 1); // drop metatable 119 | 120 | luaL_openlib(L, 0, methods, 0); // fill methodtable 121 | lua_pop(L, 1); // drop methodtable 122 | 123 | lua_pushcfunction(L, LuaWssClient::create); 124 | lua_setfield(L, -2, "newTlsClient"); 125 | } 126 | 127 | int 128 | LuaWssClient::create(lua_State *L) 129 | { 130 | lua_settop(L, 1); 131 | size_t len; 132 | const char *url = luaL_checklstring(L, 1, &len); 133 | std::string ref(url, len); 134 | int validate_cert = lua_toboolean(L, 2); 135 | WssClient *client = new WssClient(url, validate_cert ? true : false); 136 | client->_serverOpen = false; 137 | client->pushMessage(5, std::string("")); 138 | client->on_message = [=](std::shared_ptr connection, std::shared_ptr message) { 139 | client->pushMessage(1, message->string()); 140 | }; 141 | client->on_open = [=](std::shared_ptr connection) { 142 | client->_serverOpen = true; 143 | client->pushMessage(2, std::string("")); 144 | }; 145 | client->on_close = [=](std::shared_ptr connection, int status, const std::string & reason) { 146 | client->_serverOpen = false; 147 | client->pushMessage(3, std::string("")); 148 | }; 149 | client->on_error = [=](std::shared_ptr connection, const SimpleWeb::error_code &ec) { 150 | client->pushMessage(4, ec.message()); 151 | }; 152 | lua_boxpointer(L, client); 153 | luaL_getmetatable(L, className); 154 | lua_setmetatable(L, -2); 155 | return 1; 156 | } 157 | 158 | int 159 | LuaWssClient::gc(lua_State *L) 160 | { 161 | WssClient *ws = (WssClient*)lua_unboxpointer(L, 1); 162 | if (ws->_serverOpen) { 163 | ws->stop(); 164 | if (ws->thread) { 165 | ws->thread->join(); 166 | ws->thread = nullptr; 167 | } 168 | } 169 | delete ws; 170 | return 0; 171 | } 172 | 173 | int 174 | LuaWssClient::connect(lua_State *L) 175 | { 176 | WssClient *ws = LuaWssClient::check(L, 1); 177 | ws->thread = std::make_shared([=]() { 178 | ws->start(); 179 | }); 180 | return 0; 181 | } 182 | 183 | int 184 | LuaWssClient::close(lua_State *L) 185 | { 186 | WssClient *ws = LuaWssClient::check(L, 1); 187 | if (!ws->_serverOpen) { 188 | return 0; 189 | } 190 | ws->stop(); 191 | if (ws->thread) { 192 | ws->thread->join(); 193 | ws->thread = nullptr; 194 | } 195 | return 0; 196 | } 197 | 198 | int 199 | LuaWssClient::checkQueue(lua_State *L) 200 | { 201 | WssClient *ws = LuaWssClient::check(L, 1); 202 | ws->popMessage(L); 203 | return 1; 204 | } 205 | 206 | int 207 | LuaWssClient::sendMessage(lua_State *L) 208 | { 209 | WssClient *ws = LuaWssClient::check(L, 1); 210 | size_t len; 211 | const char *str = luaL_checklstring(L, 2, &len); 212 | std::string message(str, len); 213 | ws->sendMessage(message); 214 | return 0; 215 | } 216 | 217 | int 218 | LuaWssClient::isOpen(lua_State *L) 219 | { 220 | WssClient *ws = LuaWssClient::check(L, 1); 221 | lua_pushboolean(L, ws->_serverOpen ? 1 : 0); 222 | return 1; 223 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # love-ws 2 | 3 | A löve lua library to create websocket clients and servers. 4 | 5 | ## Dependencies 6 | 7 | - Boost 8 | - OpenSSL 9 | - CMake 10 | - Lua 11 | 12 | ## Building 13 | 14 | ``` 15 | mkdir build 16 | cd build 17 | cmake .. 18 | ``` 19 | 20 | ### Notes for windows users 21 | 22 | Remeber than cmake's find package sometimes requires you to config your 23 | environment variables. 24 | 25 | I personally had to add the following: 26 | 27 | - BOOST_INCLUDEDIR 28 | - BOOST_LIBRARYDIR 29 | - BOOST_ROOT 30 | - OPENSSL_INCLUDE_DIR 31 | - OPENSSL_LIBRARIES 32 | - OPENSSL_ROOT_DIR 33 | 34 | You will also need a visual studio version from 2013, 2015 or 2017. 35 | I tested on both 2015 and 2017. 36 | 37 | If you build this way, you may end up having only a 32 bits version of the dll. 38 | I'll have to investigate on how to get a x64 version easily. 39 | 40 | #### Building for Windows 64 bits 41 | 42 | You may need a bit of tools to compile, I'm testing it with Visual Studio 2015, but it should 43 | work on other Visual Studio versions. 44 | 45 | So first go ahead and install Visual Studio 2015 express. 46 | 47 | The CMake command I use for generating 64 bit builds is the following: 48 | 49 | ``` 50 | cmake -G "Visual Studio 14 2015 Win64" . 51 | ``` 52 | 53 | ##### For building lua 5.1 for 64 bits 54 | 55 | - Got the lua sources (https://www.lua.org/source/) 56 | - Added the CMake file from rude/megasource for lua 57 | - Make with Cmake 58 | 59 | ##### For building openssl for 64 bits 60 | 61 | - I got the openssl sources (https://www.openssl.org/source/) 62 | - Installed ActivePerl 63 | - Installed NASM (https://www.nasm.us/pub/nasm/releasebuilds/2.13.03/win64/) 64 | 65 | Followed instructions on INSTALL file: 66 | 67 | With the visual studio developer command line tools open: 68 | 69 | - perl Configure VC-WIN64A 70 | - nmake 71 | 72 | ##### Building all together 73 | 74 | Build it using cmake again... 75 | Remember to configure your env variables to work properly. 76 | 77 | In this case, I linked opensll dynamically, so those dlls are also 78 | needed to use it on x64 platforms. 79 | 80 | ### Notes on non windows users 81 | 82 | I didn't try to build this library on other platforms right now. 83 | But it's using CMake and I'm only using the standard library, so it may work easily. 84 | 85 | ## Usage 86 | 87 | You just require this dll like any other module: 88 | 89 | ```lua 90 | local ws = require("ws") 91 | ``` 92 | 93 | Then you must create a server or a client: 94 | 95 | ### Client 96 | 97 | ```lua 98 | local client = ws.newClient("url_or_ip:port/ns") 99 | ``` 100 | 101 | ws.newClient only takes 1 argument: the connection url. 102 | The url may contain a host or ip address, a port and a namespace. 103 | 104 | For example: 105 | 106 | ```lua 107 | local client = ws.newClient("localhost:8080/game") 108 | ``` 109 | 110 | 111 | Creates a client than will connect to localhost (the local machine) at port 8080 on the "game" 112 | namespace. 113 | 114 | If you need to connect using TLS for security reasons, 115 | you can also create a secure version of the client socket. 116 | 117 | ```lua 118 | local client = ws.newTlsClient("localhost:8080/game", true) 119 | ``` 120 | 121 | The second argument (false by default) is if you should validate 122 | the certificate of the server. 123 | 124 | On development you may want to use self signed certificates, 125 | if that's the case, put it to false, However, you should 126 | always put it as true in production. 127 | 128 | Then you should start the server: 129 | 130 | ```lua 131 | client:connect() 132 | ``` 133 | 134 | After that, the client will listen on a separate thread, you may do it in a loop: 135 | 136 | ```lua 137 | while true do 138 | local ev = client:checkQueue() 139 | if ev then 140 | --- an event happened 141 | end 142 | end 143 | 144 | ``` 145 | An event is a table with 2 parameters: 146 | 147 | - **type**: One of "message", "open", "close", "error", "connecting" 148 | - **message**: A string, with the message attached. On error, this is the error message. 149 | 150 | Type indicates wich action is taking place: 151 | 152 | - **open**: The client is connected to the server 153 | - **close**: The client closed a connection to the server 154 | - **error**: An error happened 155 | - **connecting**: The client is trying to connect to the server 156 | - **message**: The server sent a message to the client 157 | 158 | You can also send a message to the server, using `client:sendMessage("my message")` 159 | 160 | ### Server 161 | 162 | ```lua 163 | local server = ws.newServer(port) 164 | ``` 165 | 166 | The server works almost like the client, but unlike the client it can listen into different channels: 167 | You can open a channel by doing the following: 168 | 169 | ```lua 170 | local channel = server:getChannel("^/test/?$") 171 | ``` 172 | 173 | server:getChannel(pattern) takes a pattern as a parameter, this parameter is a regular expression. 174 | So if you want to have a channel listening to multiple sources, you can do that. 175 | 176 | After selecting your channels, you need to start the server: 177 | 178 | ```lua 179 | server:start() 180 | ``` 181 | 182 | Then, like the clients, you listen to messages on the queue: 183 | 184 | ```lua 185 | while true do 186 | local ev = channel:checkQueue() 187 | if ev then 188 | --- an event happened 189 | end 190 | end 191 | ``` 192 | 193 | An event, is a table with 3 fields: 194 | 195 | - **connection**: A string, indicating an id used to identify the connection who send the message. 196 | - **type**: One of "open", "close", "message", "connecting", "error" 197 | - **message**: A string, with the message attached. On error, this is the error message. 198 | 199 | Type indicates wich action is taking place: 200 | 201 | - **open**: The client just opened the connection with the server 202 | - **close**: The client closed the connection with the server 203 | - **error**: An error happened on the client 204 | - **connecting**: The client is trying to connect to the server 205 | - **message**: The client send a message to the server 206 | 207 | You can send a message to a client from the server, using `channel:send(connectionId, message)` 208 | or you can broadcast to all users, using `channel:broadcast(message)` 209 | 210 | ## License 211 | 212 | Apache 2.0 213 | 214 | Thanks to Ole Christian Eidheim for the awesome simple-websocket-library. 215 | 216 | ## Features and TODO list 217 | 218 | - [x] plain websocket server 219 | - [x] plain websocket client 220 | - [X] secure websocket client 221 | - [X] provide simple examples 222 | - [ ] TLS Server (Not sure) 223 | - [ ] Cleanup of code 224 | - [ ] Optimize methods 225 | - [ ] Better documentation -------------------------------------------------------------------------------- /src/lua_ws_server_channel.cpp: -------------------------------------------------------------------------------- 1 | #include "lua_ws_server.hpp" 2 | #include "lua_ws_server_channel.hpp" 3 | #include 4 | #include 5 | 6 | #ifndef lua_boxpointer 7 | #define lua_boxpointer(L,u) \ 8 | (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u)) 9 | #endif 10 | 11 | #ifndef lua_unboxpointer 12 | #define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i))) 13 | #endif 14 | 15 | #define method(class, name) \ 16 | { #name, class::name } 17 | 18 | const char LuaWsServerChannel::className[] = "LuaWsServerChannel"; 19 | 20 | const luaL_reg LuaWsServerChannel::methods[] = { 21 | method(LuaWsServerChannel, disconnect), 22 | method(LuaWsServerChannel, send), 23 | method(LuaWsServerChannel, broadcast), 24 | method(LuaWsServerChannel, checkQueue), 25 | { 0, 0 } }; 26 | 27 | WsServerMessage::WsServerMessage(std::string id, int type, std::string message) 28 | { 29 | this->connectionId = id; 30 | this->type = type; 31 | this->message = message; 32 | } 33 | 34 | std::string WsServerMessage::getTypeName() 35 | { 36 | switch (this->type) 37 | { 38 | case 0: return std::string("open"); 39 | case 1: return std::string("close"); 40 | case 2: return std::string("message"); 41 | case 3: return std::string("connecting"); 42 | case 4: return std::string("error"); 43 | default: break; 44 | } 45 | return std::string("unknown"); 46 | } 47 | 48 | WsServerChannel::WsServerChannel(WsServer *server, const char *channel, size_t len) 49 | { 50 | _endpoint = std::string(channel, len); 51 | _server = server; 52 | auto& endpoint = _server->endpoint[_endpoint]; 53 | auto channelPtr = this; 54 | endpoint.on_message = [=](std::shared_ptr connection, std::shared_ptr message) { 55 | channelPtr->pushMessage(this->getId(connection), 2, message->string()); 56 | }; 57 | endpoint.on_open = [=](std::shared_ptr connection) { 58 | auto id = channelPtr->getId(connection); 59 | channelPtr->pushMessage(this->getId(connection), 0); 60 | }; 61 | 62 | // See RFC 6455 7.4.1. for status codes 63 | endpoint.on_close = [=](std::shared_ptr connection, int status, const std::string & reason) { 64 | channelPtr->pushMessage(this->getId(connection), 1); 65 | channelPtr->removeConnection(connection); 66 | }; 67 | endpoint.on_error = [=](std::shared_ptr connection, const SimpleWeb::error_code &ec) { 68 | channelPtr->pushMessage(channelPtr->getId(connection), 4, ec.message()); 69 | }; 70 | } 71 | 72 | void 73 | WsServerChannel::removeConnection(std::string id) 74 | { 75 | boost::lock_guard lock{_idMutex}; 76 | if (_idMap.count(id) > 0) { 77 | auto connection = _idMap[id]; 78 | if (_connectionIds.count(connection) > 0) { 79 | _connectionIds.erase(connection); 80 | } 81 | _idMap.erase(id); 82 | } 83 | } 84 | 85 | void 86 | WsServerChannel::removeConnection(std::shared_ptr connection) 87 | { 88 | boost::lock_guard lock{_idMutex}; 89 | if (_connectionIds.count(connection) > 0) { 90 | std::string id = _connectionIds[connection]; 91 | if (_idMap.count(id) > 0) { 92 | _idMap.erase(id); 93 | } 94 | _connectionIds.erase(connection); 95 | } 96 | } 97 | 98 | std::string 99 | WsServerChannel::getId(std::shared_ptr connection) 100 | { 101 | boost::lock_guard lock{_idMutex}; 102 | if (_connectionIds.count(connection) < 1) { 103 | std::string uuid = this->_server->getId(connection); 104 | _connectionIds[connection] = uuid; 105 | _idMap[uuid] = connection; 106 | } 107 | return _connectionIds[connection]; 108 | } 109 | 110 | void WsServerChannel::popMessage(lua_State *L) 111 | { 112 | boost::lock_guard lock{_idMutex}; 113 | if (_messageQueue.empty()) { 114 | lua_pushnil(L); 115 | } 116 | else { 117 | auto msg(_messageQueue.front()); 118 | lua_createtable(L, 0, 3); 119 | lua_pushstring(L, "connection"); 120 | lua_pushlstring(L, msg.connectionId.data(), msg.connectionId.size()); 121 | lua_settable(L, -3); 122 | lua_pushstring(L, "type"); 123 | auto type = msg.getTypeName(); 124 | lua_pushlstring(L, type.data(), type.size()); 125 | lua_settable(L, -3); 126 | lua_pushstring(L, "message"); 127 | lua_pushlstring(L, msg.message.data(), msg.message.size()); 128 | lua_settable(L, -3); 129 | _messageQueue.pop(); 130 | } 131 | } 132 | 133 | void WsServerChannel::pushMessage(std::string connection, int type) 134 | { 135 | std::string m(""); 136 | this->pushMessage(connection, type, m); 137 | } 138 | 139 | void WsServerChannel::pushMessage(std::string connection, int type, std::string message) 140 | { 141 | boost::lock_guard lock{_idMutex}; 142 | WsServerMessage msg(connection, type, message); 143 | this->_messageQueue.push(msg); 144 | } 145 | 146 | void WsServerChannel::sendMessage(std::string id, std::string message) 147 | { 148 | boost::lock_guard lock{_idMutex}; 149 | if (_idMap.count(id) > 0) { 150 | auto connection = _idMap[id]; 151 | connection->send(message, [](const SimpleWeb::error_code &ec) { 152 | if (ec) { 153 | // Error 154 | } 155 | }); 156 | } 157 | 158 | } 159 | 160 | void WsServerChannel::broadcast(std::string message) 161 | { 162 | for (auto &pair : this->_connectionIds) { 163 | pair.first->send(message); 164 | } 165 | } 166 | 167 | void WsServerChannel::disconnect(std::string id) 168 | { 169 | this->removeConnection(id); 170 | boost::lock_guard lock{_idMutex}; 171 | if (_idMap.count(id) > 0) { 172 | auto connection = _idMap[id]; 173 | connection->send_close(100); 174 | } 175 | } 176 | 177 | WsServerChannel * 178 | LuaWsServerChannel::check(lua_State *L, int narg) 179 | { 180 | luaL_checktype(L, narg, LUA_TUSERDATA); 181 | void *ud = luaL_checkudata(L, narg, className); 182 | if (!ud) luaL_typerror(L, narg, className); 183 | return *(WsServerChannel**)ud; // unbox pointer 184 | } 185 | 186 | void 187 | LuaWsServerChannel::setup(lua_State *L) 188 | { 189 | lua_newtable(L); 190 | int methodtable = lua_gettop(L); 191 | luaL_newmetatable(L, className); 192 | int metatable = lua_gettop(L); 193 | 194 | lua_pushliteral(L, "__metatable"); 195 | lua_pushvalue(L, methodtable); 196 | lua_settable(L, metatable); // hide metatable from Lua getmetatable() 197 | 198 | lua_pushliteral(L, "__index"); 199 | lua_pushvalue(L, methodtable); 200 | lua_settable(L, metatable); 201 | 202 | lua_pushliteral(L, "__gc"); 203 | lua_pushcfunction(L, LuaWsServerChannel::gc); 204 | lua_settable(L, metatable); 205 | 206 | lua_pop(L, 1); // drop metatable 207 | 208 | luaL_openlib(L, 0, methods, 0); // fill methodtable 209 | lua_pop(L, 1); // drop methodtable 210 | } 211 | 212 | int 213 | LuaWsServerChannel::create(lua_State *L) 214 | { 215 | WsServer * server = LuaWsServer::check(L, 1); 216 | size_t len; 217 | const char *endpoint = luaL_checklstring(L, 2, &len); 218 | WsServerChannel *channel = new WsServerChannel(server, endpoint, len); 219 | lua_boxpointer(L, channel); 220 | luaL_getmetatable(L, className); 221 | lua_setmetatable(L, -2); 222 | return 1; 223 | } 224 | 225 | int 226 | LuaWsServerChannel::gc(lua_State *L) 227 | { 228 | WsServerChannel *channel = (WsServerChannel*)lua_unboxpointer(L, 1); 229 | delete channel; 230 | return 0; 231 | } 232 | 233 | int 234 | LuaWsServerChannel::disconnect(lua_State *L) 235 | { 236 | WsServerChannel * server = LuaWsServerChannel::check(L, 1); 237 | size_t len; 238 | const char *id = luaL_checklstring(L, 2, &len); 239 | server->disconnect(std::string(id, len)); 240 | return 0; 241 | } 242 | 243 | int 244 | LuaWsServerChannel::send(lua_State *L) 245 | { 246 | WsServerChannel * server = LuaWsServerChannel::check(L, 1); 247 | size_t id_len, msg_len; 248 | const char *id = luaL_checklstring(L, 2, &id_len); 249 | const char *msg = luaL_checklstring(L, 3, &msg_len); 250 | server->sendMessage(std::string(id, id_len), std::string(msg, msg_len)); 251 | return 0; 252 | } 253 | 254 | int 255 | LuaWsServerChannel::broadcast(lua_State *L) 256 | { 257 | WsServerChannel * server = LuaWsServerChannel::check(L, 1); 258 | size_t len; 259 | const char *msg = luaL_checklstring(L, 3, &len); 260 | server->broadcast(std::string(msg, len)); 261 | return 0; 262 | } 263 | 264 | 265 | int 266 | LuaWsServerChannel::checkQueue(lua_State *L) 267 | { 268 | WsServerChannel * server = LuaWsServerChannel::check(L, 1); 269 | server->popMessage(L); 270 | return 1; 271 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------