diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0606a94 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,57 @@ +--- +title: Defold websocket extension API documentation +brief: This manual covers how to use websockets with Defold +--- + +# Defold websocket extension API documentation + +This extension supports both secure (`wss://`) and non secure (`ws://`) websocket connections. +All platforms should support this extension. + + +Here is how you connect to a websocket and listen to events: + +```lua +local function websocket_callback(self, conn, data) + if data.event == websocket.EVENT_DISCONNECTED then + print("disconnected " .. conn) + self.connection = nil + elseif data.event == websocket.EVENT_CONNECTED then + print("Connected " .. conn) + -- self.connection = conn + elseif data.event == websocket.EVENT_ERROR then + print("Error:", data.error) + elseif data.event == websocket.EVENT_MESSAGE then + print("Receiving: '" .. tostring(data.message) .. "'") + end +end + +function init(self) + self.url = "ws://echo.websocket.org" + local params = {} + self.connection = websocket.connect(self.url, params, websocket_callback) +end + +function finalize(self) + if self.connection ~= nil then + websocket.disconnect(self.connection) + end +end +``` + + +## Installation +To use this library in your Defold project, add the following URL to your `game.project` dependencies: + +https://github.com/defold/extension-websocket/archive/master.zip + +We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-websocket/releases). + + +## Source code + +The source code is available on [GitHub](https://github.com/defold/extension-websocket) + +## API reference + +https://defold.com/extension-websocket/api/ \ No newline at end of file diff --git a/examples/websocket.gui b/examples/websocket.gui index 81d25c0..aecb3b6 100644 --- a/examples/websocket.gui +++ b/examples/websocket.gui @@ -16,7 +16,7 @@ background_color { nodes { position { x: 10.0 - y: 1113.0 + y: 949.0 z: 0.0 w: 1.0 } @@ -78,7 +78,7 @@ nodes { nodes { position { x: 214.0 - y: 568.0 + y: 314.0 z: 0.0 w: 1.0 } @@ -236,7 +236,7 @@ nodes { nodes { position { x: 320.0 - y: 438.0 + y: 184.0 z: 0.0 w: 1.0 } @@ -394,7 +394,7 @@ nodes { nodes { position { x: 320.0 - y: 503.0 + y: 249.0 z: 0.0 w: 1.0 } @@ -552,7 +552,7 @@ nodes { nodes { position { x: 429.0 - y: 568.0 + y: 314.0 z: 0.0 w: 1.0 } @@ -710,7 +710,7 @@ nodes { nodes { position { x: 320.0 - y: 568.0 + y: 314.0 z: 0.0 w: 1.0 } diff --git a/examples/websocket.gui_script b/examples/websocket.gui_script index 2dd49f8..abbaabc 100644 --- a/examples/websocket.gui_script +++ b/examples/websocket.gui_script @@ -57,22 +57,14 @@ end local function websocket_callback(self, conn, data) if data.event == websocket.EVENT_DISCONNECTED then + log("Disconnected: " .. tostring(conn)) self.connection = nil update_gui(self) - if data.error then - log("Diconnect error:", data.error) - self.connection = nil - end elseif data.event == websocket.EVENT_CONNECTED then - if data.error then - log("Connection error:", data.error) - self.connection = nil - end update_gui(self) + log("Connected: " .. tostring(conn)) elseif data.event == websocket.EVENT_ERROR then - if data.error then - log("Error:", data.error) - end + log("Error: '" .. data.error .. "'") elseif data.event == websocket.EVENT_MESSAGE then log("Receiving: '" .. tostring(data.message) .. "'") end @@ -84,9 +76,6 @@ local function connect(self, scheme) self.url = scheme .. URL log("Connecting to " .. self.url) self.connection = websocket.connect(self.url, params, websocket_callback) - if self.connection == nil then - print("Failed to connect to " .. self.url .. ": " .. err) - end end local function disconnect(self) diff --git a/game.project b/game.project index 47263b0..bbf4266 100755 --- a/game.project +++ b/game.project @@ -5,8 +5,8 @@ main_collection = /examples/websocket.collectionc shared_state = 1 [display] -width = 960 -height = 640 +width = 640 +height = 960 [project] title = extension-websocket diff --git a/websocket/api/api.script_api b/websocket/api/api.script_api new file mode 100644 index 0000000..b2c8b86 --- /dev/null +++ b/websocket/api/api.script_api @@ -0,0 +1,119 @@ +- name: websocket + type: table + desc: Functions and constants for using websockets. Supported on all platforms. + members: + +#***************************************************************************************************** + + - name: connect + type: function + desc: Connects to a remote address + parameters: + - name: url + type: string + desc: url of the remote connection + + - name: params + type: table + desc: optional parameters as properties. The following parameters can be set + members: + + - name: callback + type: function + desc: callback that receives all messages from the connection + parameters: + - name: self + type: object + desc: The script instance that was used to register the callback + + - name: connection + type: object + desc: the connection + + - name: data + type: table + desc: the event payload + members: + - name: event + type: number + desc: The current event. One of the following + + - `websocket.EVENT_CONNECTED` + + - `websocket.EVENT_DISCONNECTED` + + - `websocket.EVENT_ERROR` + + - `websocket.EVENT_MESSAGE` + + - name: message + type: string + desc: The received data. Only valid if event is `websocket.EVENT_MESSAGE` + + - name: error + type: string + desc: The error string. Only valid if event is `websocket.EVENT_ERROR` + + + returns: + - name: connection + type: object + desc: the connection + + examples: + - desc: |- + ```lua + local function websocket_callback(self, conn, data) + if data.event == websocket.EVENT_DISCONNECTED then + print("disconnected " .. conn) + self.connection = nil + elseif data.event == websocket.EVENT_CONNECTED then + print("Connected " .. conn) + -- self.connection = conn + elseif data.event == websocket.EVENT_ERROR then + print("Error:", data.error) + elseif data.event == websocket.EVENT_MESSAGE then + print("Receiving: '" .. tostring(data.message) .. "'") + end + end + + function init(self) + self.url = "ws://echo.websocket.org" + local params = {} + self.connection = websocket.connect(self.url, params, websocket_callback) + end + + function finalize(self) + if self.connection ~= nil then + websocket.disconnect(self.connection) + end + end + ``` + +#***************************************************************************************************** + + - name: disconnect + type: function + desc: Explicitly close a websocket + parameters: + - name: connection + type: object + desc: the websocket connection + +#***************************************************************************************************** + + - name: EVENT_CONNECTED + type: number + desc: The websocket was connected + + - name: EVENT_DISCONNECTED + type: number + desc: The websocket disconnected + + - name: EVENT_MESSAGE + type: number + desc: The websocket received data + + - name: EVENT_ERROR + type: number + desc: The websocket encountered an error diff --git a/websocket/src/websocket.cpp b/websocket/src/websocket.cpp index 996d9f6..ce8ff00 100644 --- a/websocket/src/websocket.cpp +++ b/websocket/src/websocket.cpp @@ -24,21 +24,7 @@ struct WebsocketContext } g_Websocket; -Result SetStatus(WebsocketConnection* conn, Result status, const char* format, ...) -{ - if (conn->m_Status == RESULT_OK) - { - va_list lst; - va_start(lst, format); - - conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst); - va_end(lst); - conn->m_Status = status; - } - return status; -} - -static void HandleCallback(WebsocketConnection* conn, int event, const uint8_t* msg, size_t msg_len); +static void HandleCallback(WebsocketConnection* conn, int event); #define STRING_CASE(_X) case _X: return #_X; @@ -71,7 +57,37 @@ const char* StateToString(State err) #undef STRING_CASE #define WS_DEBUG(...) -//#define WS_DEBUG dmLogWarning +//#define WS_DEBUG(...) dmLogWarning(__VA_ARGS__); + +#define CLOSE_CONN(...) \ + SetStatus(conn, RESULT_ERROR, __VA_ARGS__); \ + CloseConnection(conn); + + +static void SetState(WebsocketConnection* conn, State state) +{ + State prev_state = conn->m_State; + if (prev_state != state) + { + conn->m_State = state; + WS_DEBUG("%s -> %s", StateToString(prev_state), StateToString(conn->m_State)); + } +} + + +Result SetStatus(WebsocketConnection* conn, Result status, const char* format, ...) +{ + if (conn->m_Status == RESULT_OK) + { + va_list lst; + va_start(lst, format); + + conn->m_BufferSize = vsnprintf(conn->m_Buffer, conn->m_BufferCapacity, format, lst); + va_end(lst); + conn->m_Status = status; + } + return status; +} // *************************************************************************************************** // LUA functions @@ -100,7 +116,7 @@ static WebsocketConnection* CreateConnection(const char* url) static void DestroyConnection(WebsocketConnection* conn) { #if defined(HAVE_WSLAY) - if (conn->m_State == STATE_CONNECTED) + if (conn->m_Ctx) WSL_Exit(conn->m_Ctx); #endif @@ -126,9 +142,7 @@ static void CloseConnection(WebsocketConnection* conn) #endif } - conn->m_State = STATE_DISCONNECTED; - - WS_DEBUG("%s -> %s", StateToString(prev_state), StateToString(conn->m_State)); + SetState(conn, STATE_DISCONNECTED); } static int FindConnection(WebsocketConnection* conn) @@ -226,17 +240,14 @@ static int LuaSend(lua_State* L) dmSocket::Result sr = Send(conn, string, string_length, 0); if (dmSocket::RESULT_OK != sr) { - conn->m_Status = RESULT_ERROR; - dmSnPrintf(conn->m_Buffer, conn->m_BufferCapacity, "Failed to send on websocket"); - HandleCallback(conn, EVENT_ERROR, 0, 0); - CloseConnection(conn); + CLOSE_CONN("Failed to send on websocket"); } #endif return 0; } -static void HandleCallback(WebsocketConnection* conn, int event, const uint8_t* msg, size_t msg_len) +static void HandleCallback(WebsocketConnection* conn, int event) { if (!dmScript::IsCallbackValid(conn->m_Callback)) return; @@ -257,17 +268,12 @@ static void HandleCallback(WebsocketConnection* conn, int event, const uint8_t* lua_pushinteger(L, event); lua_setfield(L, -2, "event"); - lua_pushinteger(L, conn->m_Status); - lua_setfield(L, -2, "status"); - - if (conn->m_Status != RESULT_OK) - { - lua_pushstring(L, conn->m_Buffer); + if (EVENT_ERROR == event) { + lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize); lua_setfield(L, -2, "error"); } - - if (msg != 0) { - lua_pushlstring(L, (const char*)msg, msg_len); + else if (EVENT_MESSAGE == event) { + lua_pushlstring(L, conn->m_Buffer, conn->m_BufferSize); lua_setfield(L, -2, "message"); } @@ -381,17 +387,18 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) { uint32_t size = g_Websocket.m_Connections.Size(); -#define CLOSE_CONN(MSG, ...) \ - dmLogError(MSG, __VA_ARGS__); \ - CloseConnection(conn); - for (uint32_t i = 0; i < size; ++i) { WebsocketConnection* conn = g_Websocket.m_Connections[i]; if (STATE_DISCONNECTED == conn->m_State) { - HandleCallback(conn, EVENT_DISCONNECTED, 0, 0); + if (RESULT_OK != conn->m_Status) + { + HandleCallback(conn, EVENT_ERROR); + } + + HandleCallback(conn, EVENT_DISCONNECTED); g_Websocket.m_Connections.EraseSwap(i); --i; @@ -415,7 +422,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) } #else int recv_bytes = 0; - dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity, &recv_bytes); + dmSocket::Result sr = Receive(conn, conn->m_Buffer, conn->m_BufferCapacity-1, &recv_bytes); if( sr == dmSocket::RESULT_WOULDBLOCK ) { continue; @@ -424,19 +431,19 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) if (dmSocket::RESULT_OK == sr) { conn->m_BufferSize += recv_bytes; + conn->m_Buffer[conn->m_BufferCapacity-1] = 0; conn->m_HasMessage = 1; } else { CLOSE_CONN("Websocket failed to receive data %s", dmSocket::ResultToString(sr)); - conn->m_State = STATE_DISCONNECTED; continue; } #endif if (conn->m_HasMessage) { - HandleCallback(conn, EVENT_MESSAGE, (uint8_t*)conn->m_Buffer, conn->m_BufferSize); + HandleCallback(conn, EVENT_MESSAGE); conn->m_HasMessage = 0; conn->m_BufferSize = 0; } @@ -466,7 +473,6 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) int r = WSL_Init(&conn->m_Ctx, g_Websocket.m_BufferSize, (void*)conn); if (0 != r) { - SetStatus(conn, RESULT_FAIL_WSLAY_INIT, "Failed initializing wslay: %s", WSL_ResultToString(r)); CLOSE_CONN("Failed initializing wslay: %s", WSL_ResultToString(r)); continue; } @@ -481,11 +487,9 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) conn->m_Buffer[0] = 0; conn->m_BufferSize = 0; - conn->m_State = STATE_CONNECTED; - WS_DEBUG("STATE_HANDSHAKE -> STATE_CONNECTED"); - - HandleCallback(conn, EVENT_CONNECTED, 0, 0); + SetState(conn, STATE_CONNECTED); + HandleCallback(conn, EVENT_CONNECTED); } else if (STATE_HANDSHAKE_WRITE == conn->m_State) { @@ -500,8 +504,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) continue; } - conn->m_State = STATE_HANDSHAKE_READ; - WS_DEBUG("STATE_HANDSHAKE_WRITE -> STATE_HANDSHAKE_READ"); + SetState(conn, STATE_HANDSHAKE_READ); } else if (STATE_CONNECTING == conn->m_State) { @@ -519,9 +522,7 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) conn->m_Socket = dmConnectionPool::GetSocket(g_Websocket.m_Pool, conn->m_Connection); conn->m_SSLSocket = dmConnectionPool::GetSSLSocket(g_Websocket.m_Pool, conn->m_Connection); - conn->m_State = STATE_HANDSHAKE_WRITE; - - WS_DEBUG("STATE_CONNECTING -> STATE_HANDSHAKE"); + SetState(conn, STATE_HANDSHAKE_WRITE); } } @@ -532,3 +533,4 @@ static dmExtension::Result WebsocketOnUpdate(dmExtension::Params* params) DM_DECLARE_EXTENSION(Websocket, LIB_NAME, dmWebsocket::WebsocketAppInitialize, dmWebsocket::WebsocketAppFinalize, dmWebsocket::WebsocketInitialize, dmWebsocket::WebsocketOnUpdate, 0, dmWebsocket::WebsocketFinalize) +#undef CLOSE_CONN diff --git a/websocket/src/websocket.h b/websocket/src/websocket.h index fe29684..81f8673 100644 --- a/websocket/src/websocket.h +++ b/websocket/src/websocket.h @@ -79,7 +79,7 @@ namespace dmWebsocket #ifdef __GNUC__ Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...) __attribute__ ((format (printf, 3, 4))); #else - Result SetStatus(WebsocketCOnnection* conn, Result status, const char* fmt, ...); + Result SetStatus(WebsocketConnection* conn, Result status, const char* fmt, ...); #endif // Communication diff --git a/websocket/src/wslay_callbacks.cpp b/websocket/src/wslay_callbacks.cpp index fd7739f..a2ffc25 100644 --- a/websocket/src/wslay_callbacks.cpp +++ b/websocket/src/wslay_callbacks.cpp @@ -80,22 +80,6 @@ ssize_t WSL_RecvCallback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, { WebsocketConnection* conn = (WebsocketConnection*)user_data; - // struct Session *session = (struct Session*)user_data; - // ssize_t r; - // while((r = recv(session->fd, buf, len, 0)) == -1 && errno == EINTR); - // if(r == -1) { - // if(errno == EAGAIN || errno == EWOULDBLOCK) { - // wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); - // } else { - // wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); - // } - // } else if(r == 0) { - // /* Unexpected EOF is also treated as an error */ - // wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); - // r = -1; - // } - // return r; - int r = -1; // received bytes if >=0, error if < 0 dmSocket::Result socket_result = Receive(conn, buf, len, &r); @@ -119,36 +103,9 @@ ssize_t WSL_SendCallback(wslay_event_context_ptr ctx, const uint8_t *data, size_ { WebsocketConnection* conn = (WebsocketConnection*)user_data; - // struct Session *session = (struct Session*)user_data; - // ssize_t r; - - // int sflags = 0; - // // #ifdef MSG_MORE - // // if(flags & WSLAY_MSG_MORE) { - // // sflags |= MSG_MORE; - // // } - // // #endif // MSG_MORE - // while((r = send(session->fd, data, len, sflags)) == -1 && errno == EINTR); - // if(r == -1) { - // if(errno == EAGAIN || errno == EWOULDBLOCK) { - // wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); - // } else { - // wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); - // } - // } - // return r; - int sent_bytes = 0; dmSocket::Result socket_result = Send(conn, (const char*)data, len, &sent_bytes); - // dmSocket::Result socket_result; - // int r = -1; // sent bytes if >=0, error if < 0 - - // do { - // socket_result = dmSocket::Send(conn->m_Socket, data, len, &r); - // } - // while (r == -1 && socket_result == dmSocket::RESULT_INTR); - if (socket_result != dmSocket::RESULT_OK) { if (socket_result == dmSocket::RESULT_WOULDBLOCK || socket_result == dmSocket::RESULT_TRY_AGAIN) @@ -174,24 +131,6 @@ void WSL_OnMsgRecvCallback(wslay_event_context_ptr ctx, const struct wslay_event } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) { // TODO: Store the reason - - // close_code = arg->status_code; -// size_t len = arg->msg_length; -// close_reason = ""; -// if (len > 2 /* first 2 bytes = close code */) { -// close_reason.parse_utf8((char *)arg->msg + 2, len - 2); -// } -// if (!wslay_event_get_close_sent(_data->ctx)) { -// if (_data->is_server) { -// WSLServer *helper = (WSLServer *)_data->obj; -// helper->_on_close_request(_data->id, close_code, close_reason); -// } else { -// WSLClient *helper = (WSLClient *)_data->obj; -// helper->_on_close_request(close_code, close_reason); -// } -// } - - //SetStatus(conn, RESULT_NOT_CONNECTED, "Websocket received close event for %s", conn->m_Url.m_Hostname); } }