diff --git a/changelog b/changelog index d66c7fad6..134747c83 100644 --- a/changelog +++ b/changelog @@ -66,6 +66,39 @@ lwsts[15714]: 3: 0x79 lwsts[15714]: 4: 0x65 lwsts[15714]: 5: 0x21 +3) There is a new API to allow the user code to control the content of the +close frame sent when about to return nonzero from the user callback to +indicate the connection should close. + +/** + * lws_close_reason - Set reason and aux data to send with Close packet + * If you are going to return nonzero from the callback + * requesting the connection to close, you can optionally + * call this to set the reason the peer will be told if + * possible. + * + * @wsi: The websocket connection to set the close reason on + * @status: A valid close status from websocket standard + * @buf: NULL or buffer containing up to 124 bytes of auxiliary data + * @len: Length of data in @buf to send + */ +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len); + +An extra button is added to the "open and close" test server page that requests +that the test server close the connection from his end. + +The test server code will do so by + + lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, + (unsigned char *)"seeya", 5); + return -1; + +The browser shows the close code and reason he received + +websocket connection CLOSED, code: 1001, reason: seeya + User api changes ---------------- @@ -84,6 +117,11 @@ extra 2 bytes space at the end of your buffer. This ONLY applies to LWS_WRITE_CLOSE, which you normally don't send directly, but cause by returning nonzero from a callback letting the library actually send it. +2) Because of lws_close_reason() formalizing handling close frames, +LWS_WRITE_CLOSE is removed from libwebsockets.h. It was only of use to send +close frames...close frame content should be managed using lws_close_reason() +now. + v1.6.0-chrome48-firefox42 diff --git a/lib/client-parser.c b/lib/client-parser.c index 76f69f06f..90f9107a0 100644 --- a/lib/client-parser.c +++ b/lib/client-parser.c @@ -270,6 +270,14 @@ spill: } lwsl_parser("client sees server close len = %d\n", wsi->u.ws.rx_user_buffer_head); + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, + &wsi->u.ws.rx_user_buffer[ + LWS_SEND_BUFFER_PRE_PADDING], + wsi->u.ws.rx_user_buffer_head)) + return -1; /* * parrot the close packet payload back * we do not care about how it went, we are closing @@ -287,6 +295,10 @@ spill: lwsl_info("received %d byte ping, sending pong\n", wsi->u.ws.rx_user_buffer_head); + /* he set a close reason on this guy, ignore PING */ + if (wsi->u.ws.close_in_ping_buffer_len) + goto ping_drop; + if (wsi->u.ws.ping_pending_flag) { /* * there is already a pending ping payload diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6b04d810a..6e4d23095 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -74,7 +74,6 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) struct lws_context *context = wsi->context; int n, m, ret, old_state; struct lws_tokens eff_buf; - unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4]; if (!wsi) return; @@ -118,8 +117,6 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) break; } - wsi->u.ws.close_reason = reason; - if (wsi->mode == LWSCM_WSCL_WAITING_CONNECT || wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE) goto just_kill_connection; @@ -188,13 +185,24 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) */ if (old_state == LWSS_ESTABLISHED && - reason != LWS_CLOSE_STATUS_NOSTATUS && - reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY) { + (wsi->u.ws.close_in_ping_buffer_len || /* already a reason */ + (reason != LWS_CLOSE_STATUS_NOSTATUS && + (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) { lwsl_debug("sending close indication...\n"); - /* make valgrind happy */ - memset(buf, 0, sizeof(buf)); - n = lws_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING + 2], - 0, LWS_WRITE_CLOSE); + + /* if no prepared close reason, use 1000 and no aux data */ + if (!wsi->u.ws.close_in_ping_buffer_len) { + wsi->u.ws.close_in_ping_buffer_len = 2; + wsi->u.ws.ping_payload_buf[LWS_SEND_BUFFER_PRE_PADDING] = + (reason >> 16) & 0xff; + wsi->u.ws.ping_payload_buf[LWS_SEND_BUFFER_PRE_PADDING + 1] = + reason & 0xff; + } + + n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[ + LWS_SEND_BUFFER_PRE_PADDING], + wsi->u.ws.close_in_ping_buffer_len, + LWS_WRITE_CLOSE); if (n >= 0) { /* * we have sent a nice protocol level indication we @@ -944,3 +952,25 @@ lws_wsi_user(struct lws *wsi) { return wsi->user_space; } + +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len) +{ + unsigned char *p, *start; + int budget = sizeof(wsi->u.ws.ping_payload_buf) - + LWS_SEND_BUFFER_PRE_PADDING; + + assert(wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT); + + start = p = &wsi->u.ws.ping_payload_buf[LWS_SEND_BUFFER_PRE_PADDING]; + + *p++ = (((int)status) >> 8) & 0xff; + *p++ = ((int)status) & 0xff; + + if (buf) + while (len-- && p < start + budget) + *p++ = *buf++; + + wsi->u.ws.close_in_ping_buffer_len = p - start; +} diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 26587b66e..830fc4074 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -453,7 +453,7 @@ enum lws_write_protocol { /* special 04+ opcodes */ - LWS_WRITE_CLOSE = 4, + /* LWS_WRITE_CLOSE is handled by lws_close_reason() */ LWS_WRITE_PING = 5, LWS_WRITE_PONG = 6, @@ -1409,7 +1409,6 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); * LWS_WRITE_TEXT, * LWS_WRITE_BINARY, * LWS_WRITE_CONTINUATION, - * LWS_WRITE_CLOSE, * LWS_WRITE_PING, * LWS_WRITE_PONG * @@ -1428,13 +1427,6 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); * memset(&buf[LWS_SEND_BUFFER_PRE_PADDING], 0, 128); * * lws_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], 128, LWS_WRITE_TEXT); - * - * When sending - * - * LWS_WRITE_CLOSE - * - * only, you must allow your buffer to be 2 bytes longer than otherwise - * needed. * * When sending HTTP, with * @@ -1476,6 +1468,22 @@ LWS_VISIBLE LWS_EXTERN int lws_write(struct lws *wsi, unsigned char *buf, size_t len, enum lws_write_protocol protocol); +/** + * lws_close_reason - Set reason and aux data to send with Close packet + * If you are going to return nonzero from the callback + * requesting the connection to close, you can optionally + * call this to set the reason the peer will be told if + * possible. + * + * @wsi: The websocket connection to set the close reason on + * @status: A valid close status from websocket standard + * @buf: NULL or buffer containing up to 124 bytes of auxiliary data + * @len: Length of data in @buf to send + */ +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len); + /* helper for case where buffer may be const */ #define lws_write_http(wsi, buf, len) \ lws_write(wsi, (unsigned char *)(buf), len, LWS_WRITE_HTTP) diff --git a/lib/output.c b/lib/output.c index b25487ceb..795cc8a6f 100644 --- a/lib/output.c +++ b/lib/output.c @@ -278,7 +278,7 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, eff_buf.token = (char *)buf; eff_buf.token_len = len; - switch (protocol) { + switch ((int)protocol) { case LWS_WRITE_PING: case LWS_WRITE_PONG: case LWS_WRITE_CLOSE: @@ -325,18 +325,6 @@ LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, case LWS_WRITE_CLOSE: n = LWSWSOPC_CLOSE; - - /* - * 06+ has a 2-byte status code in network order - * we can do this because we demand post-buf - */ - - if (wsi->u.ws.close_reason) { - /* reason codes count as data bytes */ - buf[0] = (unsigned char)(wsi->u.ws.close_reason >> 8); - buf[1] = (unsigned char)wsi->u.ws.close_reason; - len += 2; - } break; case LWS_WRITE_PING: n = LWSWSOPC_PING; @@ -416,7 +404,7 @@ do_more_inside_frame: } send_raw: - switch (protocol) { + switch ((int)protocol) { case LWS_WRITE_CLOSE: /* lwsl_hexdump(&buf[-pre], len); */ case LWS_WRITE_HTTP: diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index bb1fbd2a7..8647919da 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -431,6 +431,9 @@ enum { LWS_RXFLOW_PENDING_CHANGE = (1 << 1), }; +/* this is not usable directly by user code any more, lws_close_reason() */ +#define LWS_WRITE_CLOSE 4 + struct lws_protocols; struct lws; @@ -835,12 +838,15 @@ struct _lws_websocket_related { size_t rx_packet_length; unsigned int rx_user_buffer_head; unsigned char mask_nonce[4]; - unsigned char ping_payload_buf[128 - 4 + LWS_SEND_BUFFER_PRE_PADDING]; /* control opc == < 124 */ - short close_reason; /* enum lws_close_status */ + /* Also used for close content... control opcode == < 128 */ + unsigned char ping_payload_buf[128 - 4 + LWS_SEND_BUFFER_PRE_PADDING]; + unsigned char ping_payload_len; unsigned char frame_mask_index; unsigned char opcode; unsigned char rsv; + /* zero if no info, or length including 2-byte close code */ + unsigned char close_in_ping_buffer_len; unsigned int final:1; unsigned int frame_is_binary:1; diff --git a/test-server/test-server-dumb-increment.c b/test-server/test-server-dumb-increment.c index ef24d5028..e8bb9c9c2 100644 --- a/test-server/test-server-dumb-increment.c +++ b/test-server/test-server-dumb-increment.c @@ -56,6 +56,12 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, break; if (strcmp((const char *)in, "reset\n") == 0) pss->number = 0; + if (strcmp((const char *)in, "closeme\n") == 0) { + lwsl_notice("dumb_inc: closing as requested\n"); + lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, + (unsigned char *)"seeya", 5); + return -1; + } break; /* * this just demonstrates how to use the protocol filter. If you won't diff --git a/test-server/test.html b/test-server/test.html index f773d1546..ee5986cd1 100644 --- a/test-server/test.html +++ b/test-server/test.html @@ -84,13 +84,15 @@ run. + -
Not initialized
+
Not initialized
To help with open and close testing, you can open and close a connection by hand using - the buttons. + the buttons. "Request Server Close" sends a message asking the server to +initiate the close.
@@ -310,24 +312,32 @@ function ot_open() { document.getElementById("ot_status").textContent = " websocket connection opened "; document.getElementById("ot_open_btn").disabled = true; document.getElementById("ot_close_btn").disabled = false; + document.getElementById("ot_req_close_btn").disabled = false; } - socket_ot.onclose = function(){ + socket_ot.onclose = function(e){ document.getElementById("ot_statustd").style.backgroundColor = "#ff4040"; - document.getElementById("ot_status").textContent = " websocket connection CLOSED "; + document.getElementById("ot_status").textContent = " websocket connection CLOSED, code: " + e.code + + ", reason: " + e.reason; document.getElementById("ot_open_btn").disabled = false; document.getElementById("ot_close_btn").disabled = true; - + document.getElementById("ot_req_close_btn").disabled = true; } } catch(exception) { alert('

Error' + exception); } } +/* browser will close the ws in a controlled way */ function ot_close() { socket_ot.close(3000, "Bye!"); } +/* we ask the server to close the ws in a controlled way */ +function ot_req_close() { + socket_ot.send("closeme\n"); +} + /* lws-mirror protocol */ var down = 0;