diff --git a/ext/iodine/http.c b/ext/iodine/http.c index 9607f09..8283310 100644 --- a/ext/iodine/http.c +++ b/ext/iodine/http.c @@ -834,6 +834,8 @@ static http_settings_s *http_settings_new(http_settings_s arg_settings) { if ((ssize_t)arg_settings.max_clients - HTTP_BUSY_UNLESS_HAS_FDS > 0) arg_settings.max_clients -= HTTP_BUSY_UNLESS_HAS_FDS; } + if (!arg_settings.deflate) + arg_settings.deflate = (size_t)-1; http_settings_s *settings = malloc(sizeof(*settings) + sizeof(void *)); *settings = arg_settings; diff --git a/ext/iodine/http.h b/ext/iodine/http.h index e13a8e2..ab61e4c 100644 --- a/ext/iodine/http.h +++ b/ext/iodine/http.h @@ -403,6 +403,8 @@ struct http_settings_s { uint8_t log; /** a read only flag set automatically to indicate the protocol's mode. */ uint8_t is_client; + /** minimum size of a message to deflate. Defaults to -1 bytes which means no deflation. */ + size_t deflate; }; /** @@ -543,6 +545,7 @@ typedef struct { void (*on_close)(intptr_t uuid, void *udata); /** Opaque user data. */ void *udata; + size_t deflate; } websocket_settings_s; /** diff --git a/ext/iodine/http1.c b/ext/iodine/http1.c index cbf4b6c..2bcb48e 100644 --- a/ext/iodine/http1.c +++ b/ext/iodine/http1.c @@ -349,6 +349,10 @@ static int http1_http2websocket_server(http_s *h, websocket_settings_s *args) { stmp = fiobj_obj2cstr(tmp); fiobj_str_resize(tmp, fio_base64_encode(stmp.data, fio_sha1_result(&sha1), 20)); + + if (args->deflate != (size_t)-1) { + http_set_header(h, HTTP_HEADER_WS_SEC_EXTENSIONS, fiobj_dup(HTTP_HVALUE_WS_DEFLATE)); + } http_set_header(h, HTTP_HEADER_CONNECTION, fiobj_dup(HTTP_HVALUE_WS_UPGRADE)); http_set_header(h, HTTP_HEADER_UPGRADE, fiobj_dup(HTTP_HVALUE_WEBSOCKET)); http_set_header(h, HTTP_HEADER_WS_SEC_KEY, tmp); diff --git a/ext/iodine/http_internal.c b/ext/iodine/http_internal.c index f3058d3..2b69b5b 100644 --- a/ext/iodine/http_internal.c +++ b/ext/iodine/http_internal.c @@ -126,6 +126,7 @@ FIOBJ HTTP_HEADER_ORIGIN; FIOBJ HTTP_HEADER_SET_COOKIE; FIOBJ HTTP_HEADER_UPGRADE; FIOBJ HTTP_HEADER_WS_SEC_CLIENT_KEY; +FIOBJ HTTP_HEADER_WS_SEC_EXTENSIONS; FIOBJ HTTP_HEADER_WS_SEC_KEY; FIOBJ HTTP_HVALUE_BYTES; FIOBJ HTTP_HVALUE_CLOSE; @@ -136,6 +137,7 @@ FIOBJ HTTP_HVALUE_MAX_AGE; FIOBJ HTTP_HVALUE_NO_CACHE; FIOBJ HTTP_HVALUE_SSE_MIME; FIOBJ HTTP_HVALUE_WEBSOCKET; +FIOBJ HTTP_HVALUE_WS_DEFLATE; FIOBJ HTTP_HVALUE_WS_SEC_VERSION; FIOBJ HTTP_HVALUE_WS_UPGRADE; FIOBJ HTTP_HVALUE_WS_VERSION; @@ -172,6 +174,7 @@ static void http_lib_cleanup(void *ignr_) { HTTPLIB_RESET(HTTP_HEADER_SET_COOKIE); HTTPLIB_RESET(HTTP_HEADER_UPGRADE); HTTPLIB_RESET(HTTP_HEADER_WS_SEC_CLIENT_KEY); + HTTPLIB_RESET(HTTP_HEADER_WS_SEC_EXTENSIONS); HTTPLIB_RESET(HTTP_HEADER_WS_SEC_KEY); HTTPLIB_RESET(HTTP_HVALUE_BYTES); HTTPLIB_RESET(HTTP_HVALUE_CLOSE); @@ -182,6 +185,7 @@ static void http_lib_cleanup(void *ignr_) { HTTPLIB_RESET(HTTP_HVALUE_NO_CACHE); HTTPLIB_RESET(HTTP_HVALUE_SSE_MIME); HTTPLIB_RESET(HTTP_HVALUE_WEBSOCKET); + HTTPLIB_RESET(HTTP_HVALUE_WS_DEFLATE); HTTPLIB_RESET(HTTP_HVALUE_WS_SEC_VERSION); HTTPLIB_RESET(HTTP_HVALUE_WS_UPGRADE); HTTPLIB_RESET(HTTP_HVALUE_WS_VERSION); @@ -213,6 +217,7 @@ static void http_lib_init(void *ignr_) { HTTP_HEADER_SET_COOKIE = fiobj_str_new("set-cookie", 10); HTTP_HEADER_UPGRADE = fiobj_str_new("upgrade", 7); HTTP_HEADER_WS_SEC_CLIENT_KEY = fiobj_str_new("sec-websocket-key", 17); + HTTP_HEADER_WS_SEC_EXTENSIONS = fiobj_str_new("sec-websocket-extensions", 24); HTTP_HEADER_WS_SEC_KEY = fiobj_str_new("sec-websocket-accept", 20); HTTP_HVALUE_BYTES = fiobj_str_new("bytes", 5); HTTP_HVALUE_CLOSE = fiobj_str_new("close", 5); @@ -224,6 +229,7 @@ static void http_lib_init(void *ignr_) { HTTP_HVALUE_NO_CACHE = fiobj_str_new("no-cache, max-age=0", 19); HTTP_HVALUE_SSE_MIME = fiobj_str_new("text/event-stream", 17); HTTP_HVALUE_WEBSOCKET = fiobj_str_new("websocket", 9); + HTTP_HVALUE_WS_DEFLATE = fiobj_str_new("permessage-deflate", 18); HTTP_HVALUE_WS_SEC_VERSION = fiobj_str_new("sec-websocket-version", 21); HTTP_HVALUE_WS_UPGRADE = fiobj_str_new("Upgrade", 7); HTTP_HVALUE_WS_VERSION = fiobj_str_new("13", 2); diff --git a/ext/iodine/http_internal.h b/ext/iodine/http_internal.h index 4002c02..8783bed 100644 --- a/ext/iodine/http_internal.h +++ b/ext/iodine/http_internal.h @@ -72,6 +72,7 @@ Constants that shouldn't be accessed by the users (`fiobj_dup` required). extern FIOBJ HTTP_HEADER_ACCEPT_RANGES; extern FIOBJ HTTP_HEADER_WS_SEC_CLIENT_KEY; +extern FIOBJ HTTP_HEADER_WS_SEC_EXTENSIONS; extern FIOBJ HTTP_HEADER_WS_SEC_KEY; extern FIOBJ HTTP_HVALUE_BYTES; extern FIOBJ HTTP_HVALUE_CLOSE; @@ -82,6 +83,7 @@ extern FIOBJ HTTP_HVALUE_MAX_AGE; extern FIOBJ HTTP_HVALUE_NO_CACHE; extern FIOBJ HTTP_HVALUE_SSE_MIME; extern FIOBJ HTTP_HVALUE_WEBSOCKET; +extern FIOBJ HTTP_HVALUE_WS_DEFLATE; extern FIOBJ HTTP_HVALUE_WS_SEC_VERSION; extern FIOBJ HTTP_HVALUE_WS_UPGRADE; extern FIOBJ HTTP_HVALUE_WS_VERSION; diff --git a/ext/iodine/iodine.c b/ext/iodine/iodine.c index 67d38a1..b12b24b 100644 --- a/ext/iodine/iodine.c +++ b/ext/iodine/iodine.c @@ -49,6 +49,7 @@ static VALUE max_msg_sym; static VALUE method_sym; static VALUE path_sym; static VALUE ping_sym; +static VALUE deflate_sym; static VALUE port_sym; static VALUE public_sym; static VALUE service_sym; @@ -414,6 +415,8 @@ static VALUE iodine_cli_parse(VALUE self) { FIO_CLI_INT("-max-msg -maxms incoming WebSocket message limit in Kb. " "Default: 250Kb"), FIO_CLI_INT("-ping websocket ping interval (1..255). Default: 40s"), + FIO_CLI_INT("-deflate minimum size of outgoing message to deflate. Default: -1. " + "-1 turns off deflation."), FIO_CLI_PRINT_HEADER("SSL/TLS:"), FIO_CLI_BOOL("-tls enable SSL/TLS using a self-signed certificate."), FIO_CLI_STRING( @@ -503,6 +506,9 @@ static VALUE iodine_cli_parse(VALUE self) { if (fio_cli_get("-ping")) { rb_hash_aset(defaults, ping_sym, INT2NUM(fio_cli_get_i("-ping"))); } + if (fio_cli_get("-deflate")) { + rb_hash_aset(defaults, deflate_sym, INT2NUM(fio_cli_get_i("-deflate"))); + } if (fio_cli_get("-redis-ping")) { rb_hash_aset(defaults, ID2SYM(rb_intern("redis_ping_")), INT2NUM(fio_cli_get_i("-redis-ping"))); @@ -659,6 +665,7 @@ Supported Settigs: - `:public` (public folder, HTTP server only) - `:timeout` (HTTP only) - `:ping` (`:raw` clients and WebSockets only) +- `:deflate` (`:raw` WebSockets only) - `:max_headers` (HTTP only) - `:max_body` (HTTP only) - `:max_msg` (WebSockets only) @@ -682,6 +689,7 @@ FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) { VALUE method = rb_hash_aref(s, method_sym); VALUE path = rb_hash_aref(s, path_sym); VALUE ping = rb_hash_aref(s, ping_sym); + VALUE deflate = rb_hash_aref(s, deflate_sym); VALUE port = rb_hash_aref(s, port_sym); VALUE r_public = rb_hash_aref(s, public_sym); VALUE service = rb_hash_aref(s, service_sym); @@ -717,6 +725,8 @@ FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) { path = rb_hash_aref(iodine_default_args, path_sym); if (ping == Qnil) ping = rb_hash_aref(iodine_default_args, ping_sym); + if (deflate == Qnil) + deflate = rb_hash_aref(iodine_default_args, deflate_sym); if (port == Qnil) port = rb_hash_aref(iodine_default_args, port_sym); if (r_public == Qnil) { @@ -792,6 +802,9 @@ FIO_FUNC iodine_connection_args_s iodine_connect_args(VALUE s, uint8_t is_srv) { else r.ping = FIX2ULONG(ping); } + if (deflate != Qnil && RB_TYPE_P(deflate, T_FIXNUM)) { + r.deflate = FIX2LONG(deflate); + } if (port != Qnil) { if (RB_TYPE_P(port, T_STRING)) { char *tmp = RSTRING_PTR(port); @@ -1285,6 +1298,7 @@ void Init_iodine(void) { IODINE_MAKE_SYM(method); IODINE_MAKE_SYM(path); IODINE_MAKE_SYM(ping); + IODINE_MAKE_SYM(deflate); IODINE_MAKE_SYM(port); IODINE_MAKE_SYM(public); IODINE_MAKE_SYM(service); diff --git a/ext/iodine/iodine.h b/ext/iodine/iodine.h index 5757aa5..d6cc2c4 100644 --- a/ext/iodine/iodine.h +++ b/ext/iodine/iodine.h @@ -26,6 +26,7 @@ typedef struct { uint8_t timeout; uint8_t ping; uint8_t log; + size_t deflate; enum { IODINE_SERVICE_RAW, IODINE_SERVICE_HTTP, diff --git a/ext/iodine/iodine_connection.c b/ext/iodine/iodine_connection.c index 6931195..2b82cb3 100644 --- a/ext/iodine/iodine_connection.c +++ b/ext/iodine/iodine_connection.c @@ -900,7 +900,7 @@ void iodine_connection_init(void) { IodineStore.add(RAWSymbol); // define the Connection Class and it's methods - ConnectionKlass = rb_define_class_under(IodineModule, "Connection", rb_cData); + ConnectionKlass = rb_define_class_under(IodineModule, "Connection", rb_cObject); rb_define_alloc_func(ConnectionKlass, iodine_connection_data_alloc_c); rb_define_method(ConnectionKlass, "write", iodine_connection_write, 1); rb_define_method(ConnectionKlass, "close", iodine_connection_close, 0); diff --git a/ext/iodine/iodine_http.c b/ext/iodine/iodine_http.c index 70bbd9d..53207d7 100644 --- a/ext/iodine/iodine_http.c +++ b/ext/iodine/iodine_http.c @@ -7,6 +7,7 @@ Feel free to copy, use and enjoy according to the license provided. #include "iodine.h" #include "http.h" +#include "http_internal.h" #include #include @@ -34,6 +35,7 @@ VALUE IODINE_R_HIJACK; VALUE IODINE_R_HIJACK_IO; VALUE IODINE_R_HIJACK_CB; +static VALUE RACK_WS_EXTENSIONS; static VALUE RACK_UPGRADE; static VALUE RACK_UPGRADE_Q; static VALUE RACK_UPGRADE_SSE; @@ -193,16 +195,31 @@ static void iodine_ws_on_close(intptr_t uuid, void *udata) { } static void iodine_ws_attach(http_s *h, VALUE handler, VALUE env) { + http_settings_s *http_settings = (http_settings_s *)((http_fio_protocol_s *)h->private_data.flag)->settings; + VALUE io = iodine_connection_new(.type = IODINE_CONNECTION_WEBSOCKET, .arg = NULL, .handler = handler, .env = env, .uuid = 0); if (io == Qnil) return; + size_t deflate = (size_t)-1; + + // check if permessage-deflate allowed + // must have header from client for extensions + // must have the permessage-deflate extension requested + // must have server authorize deflation + VALUE extension_header = rb_hash_aref(env, RACK_WS_EXTENSIONS); + char *extensions = (extension_header == Qnil ? NULL : StringValueCStr(extension_header)); + if (http_settings->deflate >= 0 && extensions != NULL && strcasestr(extensions, "permessage-deflate") != NULL) { + deflate = http_settings->deflate; + } + http_upgrade2ws(h, .on_message = iodine_ws_on_message, .on_open = iodine_ws_on_open, .on_ready = iodine_ws_on_ready, .on_shutdown = iodine_ws_on_shutdown, - .on_close = iodine_ws_on_close, .udata = (void *)io); + .on_close = iodine_ws_on_close, .udata = (void *)io, + .deflate = deflate); } /* ***************************************************************************** @@ -944,7 +961,8 @@ intptr_t iodine_http_listen(iodine_connection_args_s args){ .tls = args.tls, .timeout = args.timeout, .ws_timeout = args.ping, .ws_max_msg_size = args.max_msg, .max_header_size = args.max_headers, .on_finish = free_iodine_http, .log = args.log, - .max_body_size = args.max_body, .public_folder = args.public.data); + .max_body_size = args.max_body, .public_folder = args.public.data, + .deflate = args.deflate); if (uuid == -1) return uuid; @@ -1120,6 +1138,7 @@ void iodine_init_http(void) { rack_set(IODINE_R_HIJACK, "rack.hijack"); rack_set(IODINE_R_HIJACK_CB, "iodine.hijack_cb"); + rack_set(RACK_WS_EXTENSIONS, "HTTP_SEC_WEBSOCKET_EXTENSIONS"); rack_set(RACK_UPGRADE, "rack.upgrade"); rack_set(RACK_UPGRADE_Q, "rack.upgrade?"); rack_set_sym(RACK_UPGRADE_SSE, "sse"); diff --git a/ext/iodine/iodine_mustache.c b/ext/iodine/iodine_mustache.c index c88a0b1..9df9470 100644 --- a/ext/iodine/iodine_mustache.c +++ b/ext/iodine/iodine_mustache.c @@ -558,7 +558,7 @@ void iodine_init_mustache(void) { rb_global_variable(&filename_id); rb_global_variable(&data_id); rb_global_variable(&template_id); - VALUE tmp = rb_define_class_under(IodineModule, "Mustache", rb_cData); + VALUE tmp = rb_define_class_under(IodineModule, "Mustache", rb_cObject); rb_define_alloc_func(tmp, iodine_mustache_data_alloc_c); rb_define_method(tmp, "initialize", iodine_mustache_new, -1); rb_define_method(tmp, "render", iodine_mustache_render, 1); diff --git a/ext/iodine/iodine_store.c b/ext/iodine/iodine_store.c index 05fa5f4..495e08f 100644 --- a/ext/iodine/iodine_store.c +++ b/ext/iodine/iodine_store.c @@ -132,7 +132,7 @@ struct IodineStorage_s IodineStore = { void iodine_storage_init(void) { fio_store_capa_require(&iodine_storage, 512); VALUE tmp = - rb_define_class_under(rb_cObject, "IodineObjectStorage", rb_cData); + rb_define_class_under(rb_cObject, "IodineObjectStorage", rb_cObject); VALUE storage_obj = TypedData_Wrap_Struct(tmp, &storage_type_struct, &iodine_storage); // rb_global_variable(&iodine_storage_obj); diff --git a/ext/iodine/iodine_tls.c b/ext/iodine/iodine_tls.c index 9d05d61..e296e7c 100644 --- a/ext/iodine/iodine_tls.c +++ b/ext/iodine/iodine_tls.c @@ -234,7 +234,7 @@ void iodine_init_tls(void) { IODINE_MAKE_SYM(private_key); IODINE_MAKE_SYM(password); - IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cData); + IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cObject); rb_define_alloc_func(IodineTLSClass, iodine_tls_data_alloc_c); rb_define_method(IodineTLSClass, "initialize", iodine_tls_new, -1); rb_define_method(IodineTLSClass, "use_certificate", diff --git a/ext/iodine/websocket_deflate.h b/ext/iodine/websocket_deflate.h new file mode 100644 index 0000000..fce6b44 --- /dev/null +++ b/ext/iodine/websocket_deflate.h @@ -0,0 +1,94 @@ +/* + copyright: Boaz Segev, 2017-2019 + license: MIT + + Feel free to copy, use and enjoy according to the license specified. +*/ +#ifndef H_WEBSOCKET_DEFLATE_H +/**\file + + A single file WebSocket permessage-deflate wrapper + +*/ +#define H_WEBSOCKET_DEFLATE_H +#include +#include +#include + +#define WS_CHUNK 16384 + +z_stream *new_z_stream() { + z_stream *strm = malloc(sizeof(*strm)); + + *strm = (z_stream){ + .zalloc = Z_NULL, + .zfree = Z_NULL, + .opaque = Z_NULL, + }; + + return strm; +} + +z_stream *new_deflator() { + z_stream *strm = new_z_stream(); + + int ret = deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -MAX_WBITS, 4, Z_DEFAULT_STRATEGY); + + return strm; +} + +z_stream *new_inflator() { + z_stream *strm = new_z_stream(); + + inflateInit2(strm, -MAX_WBITS); + + return strm; +} + +int deflate_message(fio_str_info_s src, FIOBJ dest, z_stream *strm) { + int ret, flush; + unsigned have; + unsigned char out[WS_CHUNK]; + + strm->avail_in = src.len; + strm->next_in = src.data; + + do { + strm->avail_out = WS_CHUNK; + strm->next_out = out; + ret = deflate(strm, Z_SYNC_FLUSH); + have = WS_CHUNK - strm->avail_out; + fiobj_str_write(dest, out, have); + } while (strm->avail_out == 0); + + return Z_OK; +} + +int inflate_message(fio_str_info_s src, FIOBJ dest, z_stream *strm) { + int ret; + unsigned have; + unsigned char out[WS_CHUNK]; + + strm->avail_in = src.len; + strm->next_in = src.data; + + do { + strm->avail_out = WS_CHUNK; + strm->next_out = out; + ret = inflate(strm, Z_SYNC_FLUSH); + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(strm); + return ret; + } + have = WS_CHUNK - strm->avail_out; + fiobj_str_write(dest, out, have); + } while (strm->avail_out == 0); + + return ret; +} +#endif diff --git a/ext/iodine/websockets.c b/ext/iodine/websockets.c index 87353ef..8d93bbf 100644 --- a/ext/iodine/websockets.c +++ b/ext/iodine/websockets.c @@ -26,6 +26,8 @@ Feel free to copy, use and enjoy according to the license provided. #include +#include + #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) #include #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \ @@ -130,6 +132,10 @@ struct ws_s { uint8_t is_text; /** websocket connection type. */ uint8_t is_client; + + z_stream *inflator; + z_stream *deflator; + size_t deflate_min; }; /* ***************************************************************************** @@ -152,7 +158,10 @@ static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len, char first, char last, char text, unsigned char rsv) { ws_s *ws = ws_p; - if (last && first) { + static char ws_payload_tail[] = {0x00, 0x00, 0xFF, 0xFF}; + + // Shortcut only if not deflated + if (last && first && !(rsv & 4)) { ws->on_message(ws, (fio_str_info_s){.data = msg, .len = len}, (uint8_t)text); return; @@ -165,7 +174,20 @@ static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len, } fiobj_str_write(ws->msg, msg, len); if (last) { - ws->on_message(ws, fiobj_obj2cstr(ws->msg), ws->is_text); + if (rsv & 4) { + fiobj_str_write(ws->msg, ws_payload_tail, 4); + fio_str_info_s deflated = fiobj_obj2cstr(ws->msg); + FIOBJ inflated = fiobj_str_buf(deflated.len); + + if (ws->inflator == NULL) { + ws->inflator = new_inflator(); + } + int ret = inflate_message(deflated, inflated, ws->inflator); + + ws->on_message(ws, fiobj_obj2cstr(inflated), ws->is_text); + } else { + ws->on_message(ws, fiobj_obj2cstr(ws->msg), ws->is_text); + } } (void)rsv; @@ -296,7 +318,7 @@ static void on_data_first(intptr_t sockfd, fio_protocol_s *ws_) { /* later */ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text, - char first, char last, char client); + char first, char last, char client, char rsv); /******************************************************************************* Create/Destroy the websocket object @@ -305,6 +327,7 @@ Create/Destroy the websocket object static ws_s *new_websocket(intptr_t uuid) { // allocate the protocol object ws_s *ws = malloc(sizeof(*ws)); + *ws = (ws_s){ .protocol.ping = ws_ping, .protocol.on_data = on_data_first, @@ -314,6 +337,9 @@ static ws_s *new_websocket(intptr_t uuid) { .subscriptions = FIO_LS_INIT(ws->subscriptions), .is_client = 0, .fd = uuid, + .inflator = NULL, + .deflator = NULL, + .deflate_min = (size_t)-1, }; return ws; } @@ -330,6 +356,10 @@ static void destroy_ws(ws_s *ws) { void websocket_attach(intptr_t uuid, http_settings_s *http_settings, websocket_settings_s *args, void *data, size_t length) { ws_s *ws = new_websocket(uuid); + if (http_settings->deflate && http_settings->deflate != -1) { + ws->deflator = new_deflator(); + ws->deflate_min = http_settings->deflate; + } FIO_ASSERT_ALLOC(ws); // we have an active websocket connection - prep the connection buffer ws->buffer = create_ws_buffer(ws); @@ -380,24 +410,30 @@ Writing to the Websocket (FIO_MEMORY_BLOCK_ALLOC_LIMIT - 4096) // should be less then `unsigned short` static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text, - char first, char last, char client) { + char first, char last, char client, char rsv) { if (len <= WS_MAX_FRAME_SIZE) { void *buff = fio_malloc(len + 16); len = (client ? websocket_client_wrap(buff, data, len, (text ? 1 : 2), - first, last, 0) + first, last, rsv) : websocket_server_wrap(buff, data, len, (text ? 1 : 2), - first, last, 0)); + first, last, rsv)); fio_write2(fd, .data.buffer = buff, .length = len, .after.dealloc = fio_free); } else { + int firstFrame = 1; /* frame fragmentation is better for large data then large frames */ while (len > WS_MAX_FRAME_SIZE) { - websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client); + websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client, rsv); + if (firstFrame) { + firstFrame = 0; + // should only set compressed flag on first frame + rsv &= 3; + } data = ((uint8_t *)data) + WS_MAX_FRAME_SIZE; first = 0; len -= WS_MAX_FRAME_SIZE; } - websocket_write_impl(fd, data, len, text, first, 1, client); + websocket_write_impl(fd, data, len, text, first, 1, client, rsv); } return; } @@ -715,9 +751,22 @@ uint8_t websocket_is_client(ws_s *ws) { return ws->is_client; } /** Writes data to the websocket. Returns -1 on failure (0 on success). */ int websocket_write(ws_s *ws, fio_str_info_s msg, uint8_t is_text) { + char rsv = 0; + if (msg.len >= ws->deflate_min && is_text == 1) { + rsv |= 4; + } + + if (rsv & 4) { + size_t orig_len = msg.len; + FIOBJ deflated = fiobj_str_buf(msg.len); + deflate_message(msg, deflated, ws->deflator); + msg = fiobj_obj2cstr(deflated); + msg.len -= 4; + } + if (fio_is_valid(ws->fd)) { websocket_write_impl(ws->fd, msg.data, msg.len, is_text, 1, 1, - ws->is_client); + ws->is_client, rsv); return 0; } return -1; diff --git a/iodine.gemspec b/iodine.gemspec index 5eaadd1..2114459 100644 --- a/iodine.gemspec +++ b/iodine.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |spec| spec.requirements << 'TLS requires OpenSSL >= 1.1.0' # spec.add_development_dependency 'bundler', '>= 1.10', '< 2.0' - spec.add_development_dependency 'rake', '~> 12.0', '< 13.0' + spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'minitest', '>=5', '< 6.0' spec.add_development_dependency 'rspec', '>=3.9.0', '< 4.0' spec.add_development_dependency 'spec', '>=5.3.0', '< 6.0'