diff --git a/README.coding.md b/README.coding.md index 15d4c4a9..33a13f6e 100644 --- a/README.coding.md +++ b/README.coding.md @@ -440,6 +440,122 @@ on the flags during open. 7) There is an optional `mod_time` uint32_t member in the generic fop_fd. If you are able to set it during open, you should indicate it by setting `LWS_FOP_FLAG_MOD_TIME_VALID` on the flags. +@section rawfd RAW file descriptor polling + +LWS allows you to include generic platform file descriptors in the lws service / poll / event loop. + +Open your fd normally and then + +``` + lws_sock_file_fd_type u; + + u.filefd = your_open_file_fd; + + if (!lws_adopt_descriptor_vhost(vhost, 0, u, + "protocol-name-to-bind-to", + optional_wsi_parent_or_NULL)) { + // failed + } + + // OK +``` + +A wsi is created for the file fd that acts like other wsi, you will get these +callbacks on the named protocol + +``` + LWS_CALLBACK_RAW_ADOPT_FILE + LWS_CALLBACK_RAW_RX_FILE + LWS_CALLBACK_RAW_WRITEABLE_FILE + LWS_CALLBACK_RAW_CLOSE_FILE +``` + +starting with LWS_CALLBACK_RAW_ADOPT_FILE. + +`protocol-lws-raw-test` plugin provides a method for testing this with +`libwebsockets-test-server-v2.0`: + +The plugin creates a FIFO on your system called "/tmp/lws-test-raw" + +You can feed it data through the FIFO like this + +``` + $ sudo sh -c "echo hello > /tmp/lws-test-raw" +``` + +This plugin simply prints the data. But it does it through the lws event +loop / service poll. + +@section rawsrvsocket RAW server socket descriptor polling + +You can also enable your vhost to accept RAW socket connections, in addition to +HTTP[s] and WS[s]. If the first bytes written on the connection are not a +valid HTTP method, then the connection switches to RAW mode. + +This is disabled by default, you enable it by setting the `.options` flag +LWS_SERVER_OPTION_FALLBACK_TO_RAW when creating the vhost. + +RAW mode socket connections receive the following callbacks + +``` + LWS_CALLBACK_RAW_ADOPT + LWS_CALLBACK_RAW_RX + LWS_CALLBACK_RAW_WRITEABLE + LWS_CALLBACK_RAW_CLOSE +``` + +You can control which protocol on your vhost handles these RAW mode +incoming connections by marking the selected protocol with a pvo `raw`, eg + +``` + "protocol-lws-raw-test": { + "status": "ok", + "raw": "1" + }, +``` + +The "raw" pvo marks this protocol as being used for RAW connections. + +`protocol-lws-raw-test` plugin provides a method for testing this with +`libwebsockets-test-server-v2.0`: + +Run libwebsockets-test-server-v2.0 and connect to it by telnet, eg + +``` + $ telnet 127.0.0.1 7681 +``` + +type something that isn't a valid HTTP method and enter, before the +connection times out. The connection will switch to RAW mode using this +protocol, and pass the unused rx as a raw RX callback. + +The test protocol echos back what was typed on telnet to telnet. + +@section rawclientsocket RAW client socket descriptor polling + +You can now also open RAW socket connections in client mode. + +Follow the usual method for creating a client connection, but set the +`info.method` to "RAW". When the connection is made, the wsi will be +converted to RAW mode and operate using the same callbacks as the +server RAW sockets described above. + +The libwebsockets-test-client supports this using raw:// URLS. To +test, open a netcat listener in one window + +``` + $ nc -l 9999 +``` + +and in another window, connect to it using the test client + +``` + $ libwebsockets-test-client raw://127.0.0.1:9999 +``` + +The connection should succeed, and text typed in the netcat window (including a CRLF) +will be received in the client. + @section ecdh ECDH Support ECDH Certs are now supported. Enable the CMake option diff --git a/lib/client.c b/lib/client.c index 57b79f40..5a29aa6b 100755 --- a/lib/client.c +++ b/lib/client.c @@ -196,6 +196,9 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi, case LWSCM_WSCL_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { + if (wsi->mode == LWSCM_RAW) + return 0; + lwsl_err("Failed to generate handshake for client\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; @@ -1027,6 +1030,36 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) } else wsi->do_ws = 0; + if (!strcmp(meth, "RAW")) { + const char *pp = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_notice("client transition to raw\n"); + if (pp) { + const struct lws_protocols *pr; + + pr = lws_vhost_name_to_protocol(wsi->vhost, pp); + + if (!pr) { + lwsl_err("protocol %s not enabled on vhost\n", + pp); + return NULL; + } + + lws_bind_protocol(wsi, pr); + } + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + return NULL; + + wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; + lws_union_transition(wsi, LWSCM_RAW); + lws_header_table_detach(wsi, 1); + + return NULL; + } + if (wsi->do_ws) { /* * create the random key diff --git a/lib/context.c b/lib/context.c index 024cc815..7f4237a4 100644 --- a/lib/context.c +++ b/lib/context.c @@ -175,6 +175,13 @@ lws_protocol_init(struct lws_context *context) vh->protocols[n].name); vh->default_protocol_index = n; } + if (!strcmp(pvo->name, "raw")) { + lwsl_notice("Setting raw " + "protocol for vh %s to %s\n", + vh->name, + vh->protocols[n].name); + vh->raw_protocol_index = n; + } pvo = pvo->next; } diff --git a/lib/handshake.c b/lib/handshake.c index 68215cb7..5f34b0e2 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -121,6 +121,11 @@ lws_read(struct lws *wsi, unsigned char *buf, size_t len) /* Handshake indicates this session is done. */ goto bail; + /* we might have transitioned to RAW */ + if (wsi->mode == LWSCM_RAW) + /* we gave the read buffer to RAW handler already */ + goto read_ok; + /* * It's possible that we've exhausted our data already, or * rx flow control has stopped us dealing with this early, diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 9443589b..d03e740c 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -165,6 +165,32 @@ lws_remove_child_from_any_parent(struct lws *wsi) } } +int +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) +{ +// if (wsi->protocol == p) +// return 0; + + if (wsi->protocol) + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->user_space, NULL, 0); + if (!wsi->user_space_externally_allocated) + lws_free_set_NULL(wsi->user_space); + + wsi->protocol = p; + if (!p) + return 0; + + if (lws_ensure_user_space(wsi)) + return 1; + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL, + wsi->user_space, NULL, 0)) + return 1; + + return 0; +} + void lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) { diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 14682a9b..d76a5580 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1612,6 +1612,8 @@ enum lws_context_options { * the context, only the string you give in the client connect * info for .origin (if any) will be used directly. */ + LWS_SERVER_OPTION_FALLBACK_TO_RAW = (1 << 20), + /**< (VH) if invalid http is coming in the first line, */ /****** add new things just above ---^ ******/ }; diff --git a/lib/parsers.c b/lib/parsers.c index 26f53f4b..4eb0459d 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -813,9 +813,16 @@ swallow: } /* * hm it's an unknown http method from a client in fact, - * treat as dangerous + * it cannot be valid http */ if (m == ARRAY_SIZE(methods)) { + /* + * are we set up to accept raw in these cases? + */ + if (lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_FALLBACK_TO_RAW)) + return 2; /* transition to raw */ + lwsl_info("Unknown method - dropping\n"); goto forbid; } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 80d06368..b0b59470 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -795,6 +795,7 @@ struct lws_vhost { unsigned int created_vhost_protocols:1; unsigned char default_protocol_index; + unsigned char raw_protocol_index; }; /* diff --git a/lib/server.c b/lib/server.c index 32d972e4..94eb0d0a 100644 --- a/lib/server.c +++ b/lib/server.c @@ -210,7 +210,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) if (p) colon = p - servername; - /* first try exact matches */ + /* Priotity 1: first try exact matches */ while (vhost) { if (port == vhost->listen_port && @@ -222,7 +222,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) } /* - * if no exact matches, try matching *.vhost-name + * Priority 2: if no exact matches, try matching *.vhost-name * unintentional matches are possible but resolve to x.com for *.x.com * which is reasonable. If exact match exists we already chose it and * never reach here. SSL will still fail it if the cert doesn't allow @@ -243,6 +243,20 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername) vhost = vhost->vhost_next; } + /* Priority 3: match the first vhost on our port */ + + vhost = context->vhost_list; + while (vhost) { + if (port == vhost->listen_port) { + lwsl_info("vhost match to %s based on port %d\n", + vhost->name, port); + return vhost; + } + vhost = vhost->vhost_next; + } + + /* no match */ + return NULL; } @@ -1137,43 +1151,19 @@ transaction_result_n: #endif } -int -lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) -{ -// if (wsi->protocol == p) -// return 0; - - if (wsi->protocol) - wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, - wsi->user_space, NULL, 0); - if (!wsi->user_space_externally_allocated) - lws_free_set_NULL(wsi->user_space); - - wsi->protocol = p; - if (!p) - return 0; - - if (lws_ensure_user_space(wsi)) - return 1; - - if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL, - wsi->user_space, NULL, 0)) - return 1; - - return 0; -} - int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { + int protocol_len, n = 0, hit, non_space_char_found = 0, m; struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct _lws_header_related hdr; struct allocated_headers *ah; - int protocol_len, n = 0, hit, non_space_char_found = 0; + unsigned char *obuf = *buf; char protocol_list[128]; char protocol_name[64]; + size_t olen = len; char *p; if (len >= 10000000) { @@ -1195,7 +1185,38 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) goto bail_nuke_ah; } - if (lws_parse(wsi, *(*buf)++)) { + m = lws_parse(wsi, *(*buf)++); + if (m) { + if (m == 2) { + /* + * we are transitioning from http with + * an AH, to raw. Drop the ah and set + * the mode. + */ +raw_transition: + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lws_bind_protocol(wsi, &wsi->vhost->protocols[ + wsi->vhost-> + raw_protocol_index]); + lwsl_info("transition to raw vh %s prot %d\n", + wsi->vhost->name, + wsi->vhost->raw_protocol_index); + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + goto bail_nuke_ah; + + wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; + lws_union_transition(wsi, LWSCM_RAW); + lws_header_table_detach(wsi, 1); + + if (m == 2 && (wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_RX, + wsi->user_space, obuf, olen)) + return 1; + + return 0; + } lwsl_info("lws_parse failed\n"); goto bail_nuke_ah; } @@ -1253,13 +1274,8 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECT)) { lwsl_info("Changing to RAW mode\n"); - lws_union_transition(wsi, LWSCM_RAW); - if (!wsi->more_rx_waiting) { - wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; - - //lwsl_notice("%p: dropping ah EST\n", wsi); - lws_header_table_detach(wsi, 1); - } + m = 0; + goto raw_transition; } wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; @@ -1991,6 +2007,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, case LWSCM_HTTP_SERVING: case LWSCM_HTTP_SERVING_ACCEPTED: case LWSCM_HTTP2_SERVING: + case LWSCM_RAW: /* handle http headers coming in */ @@ -2033,9 +2050,9 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, /* these states imply we MUST have an ah attached */ - if (wsi->state == LWSS_HTTP || + if (wsi->mode != LWSCM_RAW && (wsi->state == LWSS_HTTP || wsi->state == LWSS_HTTP_ISSUING_FILE || - wsi->state == LWSS_HTTP_HEADERS) { + wsi->state == LWSS_HTTP_HEADERS)) { if (!wsi->u.hdr.ah) { //lwsl_err("wsi %p: missing ah\n", wsi); @@ -2105,7 +2122,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, len = lws_ssl_capable_read(wsi, pt->serv_buf, context->pt_serv_buf_size); -// lwsl_notice("%s: wsi %p read %d\r\n", __func__, wsi, len); + lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len); switch (len) { case 0: lwsl_info("%s: read 0 len\n", __func__); @@ -2124,10 +2141,10 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi, wsi, LWS_CALLBACK_RAW_RX, wsi->user_space, pt->serv_buf, len); if (n < 0) { - lwsl_info("raw writeable_fail\n"); + lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); goto fail; } - break; + goto try_pollout; } /* just ignore incoming if waiting for close */ @@ -2162,6 +2179,17 @@ try_pollout: goto fail; } + if (wsi->mode == LWSCM_RAW) { + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + break; + } + if (!wsi->hdr_parsing_completed) break; diff --git a/lib/service.c b/lib/service.c index 84e489dd..4b153195 100644 --- a/lib/service.c +++ b/lib/service.c @@ -863,13 +863,15 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t goto handled; } #endif + /* fallthru */ + case LWSCM_RAW: n = lws_server_socket_service(context, wsi, pollfd); if (n) /* closed by above */ return 1; goto handled; case LWSCM_RAW_FILEDESC: - case LWSCM_RAW: + if (pollfd->revents & LWS_POLLOUT) { n = lws_calllback_as_writeable(wsi); if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { @@ -892,6 +894,9 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t goto close_and_handled; } } + + if (pollfd->revents & LWS_POLLHUP) + goto close_and_handled; n = 0; goto handled; diff --git a/plugins/protocol_lws_raw_test.c b/plugins/protocol_lws_raw_test.c index 047f136e..35867e64 100644 --- a/plugins/protocol_lws_raw_test.c +++ b/plugins/protocol_lws_raw_test.c @@ -17,6 +17,13 @@ * may be proprietary. So unlike the library itself, they are licensed * Public Domain. * + * This plugin test both raw file descriptors and raw socket descriptors. It + * can test both or just one depending on how you configure it. libwebsockets- + * test-server-v2.0 is set up to test both. + * + * RAW File Descriptor Testing + * =========================== + * * Enable on a vhost like this * * "protocol-lws-raw-test": { @@ -28,8 +35,33 @@ * * $ sudo sh -c "echo hello > /tmp/lws-test-raw" * - * This plugin simply prints the data. But it does it through the lws event loop / - * service poll. + * This plugin simply prints the data. But it does it through the lws event + * loop / service poll. + * + * + * RAW Socket Descriptor Testing + * ============================= + * + * 1) You must give the vhost the option flag LWS_SERVER_OPTION_FALLBACK_TO_RAW + * + * 2) Enable on a vhost like this + * + * "protocol-lws-raw-test": { + * "status": "ok", + * "raw": "1" + * }, + * + * The "raw" pvo marks this protocol as being used for RAW connections. + * + * 3) Run libwebsockets-test-server-v2.0 and connect to it by telnet, eg + * + * telnet 127.0.0.1 7681 + * + * type something that isn't a valid HTTP method and enter, before the + * connection times out. The connection will switch to RAW mode using this + * protocol, and pass the unused rx as a raw RX callback. + * + * The test protocol echos back what was typed on telnet to telnet. */ #if !defined (LWS_PLUGIN_STATIC) @@ -51,6 +83,8 @@ struct per_vhost_data__raw_test { struct per_session_data__raw_test { int number; + unsigned char buf[128]; + int len; }; static int @@ -84,8 +118,8 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, pvo = pvo->next; } if (vhd->fifo_path[0] == '\0') { - lwsl_err("Missing pvo \"fifo-path\"\n"); - return 1; + lwsl_err("%s: Missing pvo \"fifo-path\", raw file fd testing disabled\n", __func__); + break; } } unlink(vhd->fifo_path); @@ -101,7 +135,9 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, } lwsl_notice("FIFO %s created\n", vhd->fifo_path); u.filefd = vhd->fifo; - if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test", NULL)) { + if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, + "protocol-lws-raw-test", + NULL)) { lwsl_err("Failed to adopt fifo descriptor\n"); close(vhd->fifo); unlink(vhd->fifo_path); @@ -118,6 +154,11 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, } break; + + /* + * Callbacks related to Raw file descriptor testing + */ + case LWS_CALLBACK_RAW_ADOPT_FILE: lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n"); break; @@ -179,6 +220,32 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n"); break; + /* + * Callbacks related to Raw socket descriptor testing + */ + + case LWS_CALLBACK_RAW_ADOPT: + lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n"); + break; + + case LWS_CALLBACK_RAW_RX: + lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len); + if (len > sizeof(pss->buf)) + len = sizeof(pss->buf); + memcpy(pss->buf, in, len); + pss->len = len; + lws_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_RAW_CLOSE: + lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n"); + break; + + case LWS_CALLBACK_RAW_WRITEABLE: + lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n"); + lws_write(wsi, pss->buf, pss->len, LWS_WRITE_HTTP); + break; + default: break; } diff --git a/test-server/test-client.c b/test-server/test-client.c index 63d0d6d0..4575ee04 100644 --- a/test-server/test-client.c +++ b/test-server/test-client.c @@ -334,10 +334,38 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, return 0; } +static int +callback_test_raw_client(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + case LWS_CALLBACK_RAW_ADOPT: + lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n"); + break; + + case LWS_CALLBACK_RAW_RX: + lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len); + puts(in); + break; + + case LWS_CALLBACK_RAW_CLOSE: + lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n"); + break; + + case LWS_CALLBACK_RAW_WRITEABLE: + lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n"); + break; + + default: + break; + } + + return 0; +} /* list of supported protocols and callbacks */ -static struct lws_protocols protocols[] = { +static const struct lws_protocols protocols[] = { { "dumb-increment-protocol", callback_dumb_increment, @@ -349,6 +377,11 @@ static struct lws_protocols protocols[] = { callback_lws_mirror, 0, 128, + }, { + "lws-test-raw-client", + callback_test_raw_client, + 0, + 128 }, { NULL, NULL, 0, 0 } /* end */ }; @@ -597,7 +630,13 @@ int main(int argc, char **argv) i.method = "GET"; do_ws = 0; } else - lwsl_notice("using %s mode (ws)\n", prot); + if (!strcmp(prot, "raw")) { + i.method = "RAW"; + i.protocol = "lws-test-raw-client"; + lwsl_notice("using RAW mode connection\n"); + do_ws = 0; + } else + lwsl_notice("using %s mode (ws)\n", prot); /* * sit there servicing the websocket context to handle incoming diff --git a/test-server/test-server-v2.0.c b/test-server/test-server-v2.0.c index 96e00a50..61149f80 100644 --- a/test-server/test-server-v2.0.c +++ b/test-server/test-server-v2.0.c @@ -155,13 +155,34 @@ static const struct lws_protocol_vhost_options pvo_opt = { "1" }; +static const struct lws_protocol_vhost_options pvo_opt4a = { + NULL, + NULL, + "raw", /* indicate we are the protocol that gets raw connections */ + "1" +}; + +static const struct lws_protocol_vhost_options pvo_opt4 = { + &pvo_opt4a, + NULL, + "fifo-path", /* tell the raw test plugin to open a raw file here */ + "/tmp/lws-test-raw" +}; + /* * We must enable the plugin protocols we want into our vhost with a * linked-list. We can also give the plugin per-vhost options here. */ -static const struct lws_protocol_vhost_options pvo_3 = { +static const struct lws_protocol_vhost_options pvo_4 = { NULL, + &pvo_opt4, /* set us as the protocol who gets raw connections */ + "protocol-lws-raw-test", + "" /* ignored, just matches the protocol name above */ +}; + +static const struct lws_protocol_vhost_options pvo_3 = { + &pvo_4, NULL, "protocol-post-demo", "" /* ignored, just matches the protocol name above */ @@ -367,7 +388,7 @@ int main(int argc, char **argv) info.gid = gid; info.uid = uid; info.max_http_header_pool = 16; - info.options = opts | + info.options = opts | LWS_SERVER_OPTION_FALLBACK_TO_RAW | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_LIBUV; /* plugins require this */