diff --git a/src/App.h b/src/App.h index c80679b..1dc7f0c 100644 --- a/src/App.h +++ b/src/App.h @@ -27,19 +27,10 @@ #include "WebSocket.h" #include "WebSocketExtensions.h" #include "WebSocketHandshake.h" +#include "PerMessageDeflate.h" namespace uWS { -/* Compress options (really more like PerMessageDeflateOptions) */ -enum CompressOptions { - /* Compression disabled */ - DISABLED = 0, - /* We compress using a shared non-sliding window. No added memory usage, worse compression. */ - SHARED_COMPRESSOR = 1, - /* We compress using a dedicated sliding window. Major memory usage added, better compression of similarly repeated messages. */ - DEDICATED_COMPRESSOR = 2 -}; - template struct TemplatedApp { private: @@ -130,7 +121,7 @@ public: if (!loopData->zlibContext) { loopData->zlibContext = new ZlibContext; loopData->inflationStream = new InflationStream; - loopData->deflationStream = new DeflationStream; + loopData->deflationStream = new DeflationStream(CompressOptions::DEDICATED_COMPRESSOR); } } @@ -173,9 +164,10 @@ public: res->writeHeader("Sec-WebSocket-Protocol", secWebSocketProtocol.substr(0, secWebSocketProtocol.find(','))); } - /* Negotiate compression */ + /* Negotiate compression, we may use a smaller compression window than we negotiate */ bool perMessageDeflate = false; - bool slidingDeflateWindow = false; + /* We are always allowed to share compressor, if perMessageDeflate */ + int compressOptions = behavior.compression & SHARED_COMPRESSOR; if (behavior.compression != DISABLED) { std::string_view extensions = req->getHeader("sec-websocket-extensions"); if (extensions.length()) { @@ -205,7 +197,7 @@ public: /* Is the server allowed to compress with a sliding window? */ if (!(extensionsNegotiator.getNegotiatedOptions() & SERVER_NO_CONTEXT_TAKEOVER)) { - slidingDeflateWindow = true; + compressOptions = behavior.compression; } } } @@ -231,7 +223,7 @@ public: /* Initialize websocket with any moved backpressure intact */ httpContext->upgradeToWebSocket( - webSocket->init(perMessageDeflate, slidingDeflateWindow, std::move(backpressure)) + webSocket->init(perMessageDeflate, compressOptions, std::move(backpressure)) ); /* Arm idleTimeout */ diff --git a/src/PerMessageDeflate.h b/src/PerMessageDeflate.h index e0ed2fb..da21b23 100644 --- a/src/PerMessageDeflate.h +++ b/src/PerMessageDeflate.h @@ -20,6 +20,28 @@ #ifndef UWS_PERMESSAGEDEFLATE_H #define UWS_PERMESSAGEDEFLATE_H +/* We always define these options no matter if ZLIB is enabled or not */ +namespace uWS { + /* Compress options (really more like PerMessageDeflateOptions) */ + enum CompressOptions : int { + /* Compression disabled */ + DISABLED = 0, + /* We compress using a shared non-sliding window. No added memory usage, worse compression. */ + SHARED_COMPRESSOR = 1, + /* We compress using a dedicated sliding window. Major memory usage added, better compression of similarly repeated messages. */ + DEDICATED_COMPRESSOR = 2, + /* Flags for limiting memory usage of dedicated compressor */ + DEDICATED_COMPRESSOR_2KB = 2 | 4, + DEDICATED_COMPRESSOR_4KB = 2 | 8, + DEDICATED_COMPRESSOR_8KB = 2 | 16, + DEDICATED_COMPRESSOR_16KB = 2 | 32, + DEDICATED_COMPRESSOR_32KB = 2 | 64, + DEDICATED_COMPRESSOR_64KB = 2 | 128, + DEDICATED_COMPRESSOR_128KB = 2 | 256, + DEDICATED_COMPRESSOR_256KB = 2 | 512 + }; +} + #ifndef UWS_NO_ZLIB #include #endif @@ -68,8 +90,42 @@ struct ZlibContext { struct DeflationStream { z_stream deflationStream = {}; - DeflationStream() { - deflateInit2(&deflationStream, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + DeflationStream(int compressOptions) { + + /* Sliding inflator should be about 44kb by default, less than compressor */ + + /* Memory usage is given by 2 ^ (windowBits + 2) + 2 ^ (memLevel + 9) */ + int windowBits = -15, memLevel = 8; + + if (compressOptions == DEDICATED_COMPRESSOR_2KB) { + windowBits = -8; + memLevel = 1; + } else if (compressOptions == DEDICATED_COMPRESSOR_4KB) { + windowBits = -9; + memLevel = 2; + } else if (compressOptions == DEDICATED_COMPRESSOR_8KB) { + windowBits = -10; + memLevel = 3; + } else if (compressOptions == DEDICATED_COMPRESSOR_16KB) { + windowBits = -11; + memLevel = 4; + } else if (compressOptions == DEDICATED_COMPRESSOR_32KB) { + windowBits = -12; + memLevel = 5; + } else if (compressOptions == DEDICATED_COMPRESSOR_64KB) { + windowBits = -13; + memLevel = 6; + } else if (compressOptions == DEDICATED_COMPRESSOR_128KB) { + windowBits = -14; + memLevel = 7; + } else if (compressOptions == DEDICATED_COMPRESSOR_256KB) { + windowBits = -15; + memLevel = 8; + } + + /* DEDICATED_COMPRESSOR_256KB is the same as DEDICATED_COMPRESSOR */ + + deflateInit2(&deflationStream, 1, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY); } /* Deflate and optionally reset */ diff --git a/src/WebSocket.h b/src/WebSocket.h index ee05b8b..8154c81 100644 --- a/src/WebSocket.h +++ b/src/WebSocket.h @@ -33,8 +33,8 @@ struct WebSocket : AsyncSocket { private: typedef AsyncSocket Super; - void *init(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) { - new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, slidingCompression, std::move(backpressure)); + void *init(bool perMessageDeflate, int compressOptions, std::string &&backpressure) { + new (us_socket_ext(SSL, (us_socket_t *) this)) WebSocketData(perMessageDeflate, compressOptions, std::move(backpressure)); return this; } public: diff --git a/src/WebSocketData.h b/src/WebSocketData.h index 0f2e59d..a51c1b1 100644 --- a/src/WebSocketData.h +++ b/src/WebSocketData.h @@ -45,12 +45,12 @@ private: /* We could be a subscriber */ Subscriber *subscriber = nullptr; public: - WebSocketData(bool perMessageDeflate, bool slidingCompression, std::string &&backpressure) : AsyncSocketData(std::move(backpressure)), WebSocketState() { + WebSocketData(bool perMessageDeflate, int compressOptions, std::string &&backpressure) : AsyncSocketData(std::move(backpressure)), WebSocketState() { compressionStatus = perMessageDeflate ? ENABLED : DISABLED; /* Initialize the dedicated sliding window */ - if (perMessageDeflate && slidingCompression) { - deflationStream = new DeflationStream; + if (perMessageDeflate && (compressOptions & CompressOptions::DEDICATED_COMPRESSOR)) { + deflationStream = new DeflationStream(compressOptions); } }