close add api to control sent close frame contents
authorAndy Green <andy.green@linaro.org>
Sat, 26 Dec 2015 09:20:34 +0000 (17:20 +0800)
committerAndy Green <andy.green@linaro.org>
Sat, 26 Dec 2015 09:20:34 +0000 (17:20 +0800)
This adds an api lws_close_reason() which lets you control what will
be sent in the close frame when the connection is closed by returning
nonzero from the user callback.

The test server demo is extended to prove it works in both directions.

With this, we should have nice close support.

https://github.com/warmcat/libwebsockets/issues/196

Signed-off-by: Andy Green <andy.green@linaro.org>
changelog
lib/client-parser.c
lib/libwebsockets.c
lib/libwebsockets.h
lib/output.c
lib/private-libwebsockets.h
test-server/test-server-dumb-increment.c
test-server/test.html

index d66c7fa..134747c 100644 (file)
--- 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
index 76f69f0..90f9107 100644 (file)
@@ -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
index 6b04d81..6e4d230 100644 (file)
@@ -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;
+}
index 26587b6..830fc40 100644 (file)
@@ -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)
index b25487c..795cc8a 100644 (file)
@@ -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:
index bb1fbd2..8647919 100644 (file)
@@ -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;
index ef24d50..e8bb9c9 100644 (file)
@@ -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
index f773d15..ee5986c 100644 (file)
@@ -84,13 +84,15 @@ run.
                <tr>
                <td align=center><input type=button id=ot_open_btn value="Open" onclick="ot_open();" ></td>
                <td align=center><input type=button id=ot_close_btn disabled value="Close" onclick="ot_close();" ></td>
+               <td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" onclick="ot_req_close();" ></td>
                </tr>
-               <tr><td colspan="2" id=ot_statustd align=center class="explain"><div id=ot_status>Not initialized</div></td></tr>
+               <tr><td colspan="3" id=ot_statustd align=center class="explain"><div id=ot_status>Not initialized</div></td></tr>
        </tr>
 </table>
 </td><td class="explain">
 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.
 </td></tr></table>
 </section>
 <br>
@@ -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('<p>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;