Skip to content

Commit 6a71926

Browse files
committed
Add support for compressing websockets
PR ipkn#329 fran6co
1 parent 7cb27fc commit 6a71926

File tree

6 files changed

+224
-24
lines changed

6 files changed

+224
-24
lines changed

CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
1111
find_package(Tcmalloc)
1212
find_package(Threads)
1313
find_package(OpenSSL)
14+
find_package(zlib)
1415

1516
if(OPENSSL_FOUND)
1617
include_directories(${OPENSSL_INCLUDE_DIR})
1718
endif()
19+
if(ZLIB_FOUND)
20+
add_definitions(-DHAVE_ZLIB)
21+
include_directories(${ZLIB_INCLUDE_DIR})
22+
endif()
1823

1924
find_program(CCACHE_FOUND ccache)
2025
if(CCACHE_FOUND)

examples/CMakeLists.txt

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,30 @@ project (crow_examples)
33

44
if (MSVC)
55
add_executable(example_vs example_vs.cpp)
6-
target_link_libraries(example_vs ${Boost_LIBRARIES})
6+
target_link_libraries(example_vs ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
77
target_link_libraries(example_vs ${CMAKE_THREAD_LIBS_INIT})
8+
add_executable(example_websocket websocket/example_ws.cpp)
9+
target_link_libraries(example_websocket ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
10+
target_link_libraries(example_websocket ${CMAKE_THREAD_LIBS_INIT})
11+
add_custom_command(OUTPUT ws.html
12+
COMMAND ${CMAKE_COMMAND} -E
13+
copy ${PROJECT_SOURCE_DIR}/websocket/templates/ws.html ${CMAKE_CURRENT_BINARY_DIR}/templates/ws.html
14+
DEPENDS ${PROJECT_SOURCE_DIR}/websocket/templates/ws.html
15+
)
16+
add_custom_target(example_ws_copy ALL DEPENDS ws.html)
817
else ()
918
add_executable(helloworld helloworld.cpp)
10-
target_link_libraries(helloworld ${Boost_LIBRARIES})
19+
target_link_libraries(helloworld ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
1120
target_link_libraries(helloworld ${CMAKE_THREAD_LIBS_INIT})
1221

1322
if (OPENSSL_FOUND)
1423
add_executable(example_ssl ssl/example_ssl.cpp)
15-
target_link_libraries(example_ssl ${Boost_LIBRARIES})
24+
target_link_libraries(example_ssl ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
1625
target_link_libraries(example_ssl ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES})
1726
endif()
1827

1928
add_executable(example_websocket websocket/example_ws.cpp)
20-
target_link_libraries(example_websocket ${Boost_LIBRARIES})
29+
target_link_libraries(example_websocket ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
2130
target_link_libraries(example_websocket ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES})
2231
add_custom_command(OUTPUT ws.html
2332
COMMAND ${CMAKE_COMMAND} -E
@@ -28,7 +37,7 @@ add_custom_target(example_ws_copy ALL DEPENDS ws.html)
2837

2938
add_executable(example example.cpp)
3039
#target_link_libraries(example crow)
31-
target_link_libraries(example ${Boost_LIBRARIES})
40+
target_link_libraries(example ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
3241
target_link_libraries(example ${CMAKE_THREAD_LIBS_INIT})
3342

3443
if (Tcmalloc_FOUND)
@@ -38,7 +47,7 @@ endif(Tcmalloc_FOUND)
3847
add_executable(example_with_all example_with_all.cpp)
3948
add_dependencies(example_with_all amalgamation)
4049
#target_link_libraries(example crow)
41-
target_link_libraries(example_with_all ${Boost_LIBRARIES})
50+
target_link_libraries(example_with_all ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
4251
target_link_libraries(example_with_all ${CMAKE_THREAD_LIBS_INIT})
4352

4453
add_custom_command(OUTPUT example_test.py
@@ -49,7 +58,7 @@ add_custom_command(OUTPUT example_test.py
4958
add_custom_target(example_copy ALL DEPENDS example_test.py)
5059

5160
add_executable(example_chat example_chat.cpp)
52-
target_link_libraries(example_chat ${Boost_LIBRARIES})
61+
target_link_libraries(example_chat ${Boost_LIBRARIES} ${ZLIB_LIBRARIES})
5362
target_link_libraries(example_chat ${CMAKE_THREAD_LIBS_INIT})
5463
add_custom_command(OUTPUT example_chat.html
5564
COMMAND ${CMAKE_COMMAND} -E

include/crow/settings.h

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
/* #ifdef - enables ssl */
1212
//#define CROW_ENABLE_SSL
1313

14+
/* #ifdef - enables websocket compression */
15+
#ifdef HAVE_ZLIB
16+
#define CROW_ENABLE_WEBSOCKET_COMPRESSION
17+
#endif
18+
1419
/* #define - specifies log level */
1520
/*
1621
Debug = 0

include/crow/socket_adaptors.h

+5-5
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ namespace crow
9090

9191
void close()
9292
{
93-
if (ssl_socket_)
94-
{
95-
boost::system::error_code ec;
96-
raw_socket().close(ec);
97-
}
93+
if (ssl_socket_)
94+
{
95+
boost::system::error_code ec;
96+
raw_socket().close(ec);
97+
}
9898
}
9999

100100
boost::asio::io_service& get_io_service()

include/crow/websocket.h

+61-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#pragma once
22
#include <boost/algorithm/string/predicate.hpp>
3+
#include <boost/algorithm/string/split.hpp>
34
#include <boost/array.hpp>
45
#include "crow/socket_adaptors.h"
56
#include "crow/http_request.h"
67
#include "crow/TinySHA1.hpp"
8+
#include "crow/zlib.hpp"
79

810
namespace crow
911
{
@@ -61,8 +63,19 @@ namespace crow
6163
return;
6264
}
6365
}
64-
65-
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
66+
#ifdef CROW_ENABLE_WEBSOCKET_COMPRESSION
67+
std::string extensionsHeader = req.get_header_value("Sec-WebSocket-Extensions");
68+
std::vector<std::string> extensions;
69+
boost::split(extensions, extensionsHeader, boost::is_any_of(";"));
70+
if (std::find(extensions.begin(), extensions.end(), "permessage-deflate") != extensions.end())
71+
{
72+
bool reset_compressor_on_send_ = std::find(extensions.begin(), extensions.end(), "server_no_context_takeover") != extensions.end();
73+
compressor_.reset(new zlib_compressor(reset_compressor_on_send_, true, 15, Z_BEST_COMPRESSION, 8, Z_DEFAULT_STRATEGY));
74+
bool reset_decompressor_on_send_ = std::find(extensions.begin(), extensions.end(), "client_no_context_takeover") != extensions.end();
75+
decompressor_.reset(new zlib_decompressor(reset_decompressor_on_send_, true, 15));
76+
}
77+
#endif
78+
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
6679
// Sec-WebSocket-Version: 13
6780
std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
6881
sha1::SHA1 s;
@@ -98,19 +111,35 @@ namespace crow
98111
void send_binary(const std::string& msg) override
99112
{
100113
dispatch([this, msg]{
101-
auto header = build_header(2, msg.size());
102-
write_buffers_.emplace_back(std::move(header));
103-
write_buffers_.emplace_back(msg);
114+
if (compressor_) {
115+
std::string msg_ = compressor_->compress(msg);
116+
auto header = build_header(0x42, msg_.size());
117+
write_buffers_.emplace_back(std::move(header));
118+
write_buffers_.emplace_back(std::move(msg_));
119+
}
120+
else {
121+
auto header = build_header(2, msg.size());
122+
write_buffers_.emplace_back(std::move(header));
123+
write_buffers_.emplace_back(msg);
124+
}
104125
do_write();
105126
});
106127
}
107128

108129
void send_text(const std::string& msg) override
109130
{
110131
dispatch([this, msg]{
111-
auto header = build_header(1, msg.size());
112-
write_buffers_.emplace_back(std::move(header));
113-
write_buffers_.emplace_back(msg);
132+
if (compressor_) {
133+
std::string msg_ = compressor_->compress(msg);
134+
auto header = build_header(0x41, msg_.size());
135+
write_buffers_.emplace_back(std::move(header));
136+
write_buffers_.emplace_back(std::move(msg_));
137+
}
138+
else {
139+
auto header = build_header(1, msg.size());
140+
write_buffers_.emplace_back(std::move(header));
141+
write_buffers_.emplace_back(msg);
142+
}
114143
do_write();
115144
});
116145
}
@@ -167,6 +196,16 @@ namespace crow
167196
write_buffers_.emplace_back(header);
168197
write_buffers_.emplace_back(std::move(hello));
169198
write_buffers_.emplace_back(crlf);
199+
if (compressor_ && decompressor_) {
200+
write_buffers_.emplace_back(
201+
"Sec-WebSocket-Extensions: permessage-deflate"
202+
"; server_max_window_bits=" + std::to_string(decompressor_->window_bits) +
203+
"; client_max_window_bits=" + std::to_string(compressor_->window_bits) +
204+
(compressor_->reset_before_compress ? "; server_no_context_takeover" : "") +
205+
(decompressor_->reset_before_decompress ? "; client_no_context_takeover" : "")
206+
);
207+
write_buffers_.emplace_back(crlf);
208+
}
170209
write_buffers_.emplace_back(crlf);
171210
do_write();
172211
if (open_handler_)
@@ -368,6 +407,11 @@ namespace crow
368407
return mini_header_ & 0x8000;
369408
}
370409

410+
bool is_compressed()
411+
{
412+
return mini_header_ & 0x4000;
413+
}
414+
371415
int opcode()
372416
{
373417
return (mini_header_ & 0x0f00) >> 8;
@@ -387,7 +431,7 @@ namespace crow
387431
if (is_FIN())
388432
{
389433
if (message_handler_)
390-
message_handler_(*this, message_, is_binary_);
434+
message_handler_(*this, (is_compressed() && decompressor_) ? decompressor_->decompress(message_) : message_, is_binary_);
391435
message_.clear();
392436
}
393437
}
@@ -398,7 +442,7 @@ namespace crow
398442
if (is_FIN())
399443
{
400444
if (message_handler_)
401-
message_handler_(*this, message_, is_binary_);
445+
message_handler_(*this, (is_compressed() && decompressor_) ? decompressor_->decompress(message_) : message_, is_binary_);
402446
message_.clear();
403447
}
404448
}
@@ -410,7 +454,7 @@ namespace crow
410454
if (is_FIN())
411455
{
412456
if (message_handler_)
413-
message_handler_(*this, message_, is_binary_);
457+
message_handler_(*this, (is_compressed() && decompressor_) ? decompressor_->decompress(message_) : message_, is_binary_);
414458
message_.clear();
415459
}
416460
}
@@ -514,7 +558,12 @@ namespace crow
514558
bool pong_received_{false};
515559
bool is_close_handler_called_{false};
516560

517-
std::function<void(crow::websocket::connection&)> open_handler_;
561+
bool reset_compressor_on_send_{ false };
562+
bool reset_decompressor_on_send_{ false };
563+
std::unique_ptr<zlib_compressor> compressor_;
564+
std::unique_ptr<zlib_decompressor> decompressor_;
565+
566+
std::function<void(crow::websocket::connection&)> open_handler_;
518567
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_;
519568
std::function<void(crow::websocket::connection&, const std::string&)> close_handler_;
520569
std::function<void(crow::websocket::connection&)> error_handler_;

include/crow/zlib.hpp

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#pragma once
2+
#include "crow/settings.h"
3+
#include <boost/asio/streambuf.hpp>
4+
#include <string>
5+
#ifdef HAVE_ZLIB
6+
#include <zlib.h>
7+
8+
namespace crow {
9+
class zlib_compressor {
10+
public:
11+
zlib_compressor(bool reset_before_compress, bool noheader, int window_bits, int level, int mem_level, int strategy)
12+
: reset_before_compress(reset_before_compress)
13+
, window_bits(window_bits) {
14+
stream = std::make_unique<z_stream>();
15+
stream->zalloc = 0;
16+
stream->zfree = 0;
17+
stream->opaque = 0;
18+
19+
::deflateInit2(stream.get(),
20+
level,
21+
Z_DEFLATED,
22+
(noheader ? -window_bits : window_bits),
23+
mem_level,
24+
strategy
25+
);
26+
}
27+
28+
~zlib_compressor() {
29+
::deflateEnd(stream.get());
30+
}
31+
32+
std::string compress(const std::string & src) {
33+
if (reset_before_compress)
34+
::deflateReset(stream.get());
35+
36+
stream->next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(src.c_str()));
37+
stream->avail_in = src.size();
38+
39+
const uint64_t bufferSize = 256;
40+
boost::asio::streambuf buffer;
41+
do {
42+
boost::asio::streambuf::mutable_buffers_type chunk = buffer.prepare(bufferSize);
43+
44+
uint8_t* next_out = boost::asio::buffer_cast<uint8_t*>(chunk);
45+
46+
stream->next_out = next_out;
47+
stream->avail_out = bufferSize;
48+
49+
::deflate(stream.get(), reset_before_compress ? Z_FINISH : Z_SYNC_FLUSH);
50+
51+
uint64_t outputSize = stream->next_out - next_out;
52+
buffer.commit(outputSize);
53+
} while (stream->avail_out == 0);
54+
55+
uint64_t buffer_size = buffer.size();
56+
if (!reset_before_compress) buffer_size -= 4;
57+
58+
return std::string(boost::asio::buffer_cast<const char*>(buffer.data()), buffer_size);
59+
}
60+
61+
std::unique_ptr<z_stream> stream;
62+
63+
bool reset_before_compress;
64+
int window_bits;
65+
};
66+
67+
class zlib_decompressor {
68+
public:
69+
zlib_decompressor(bool reset_before_decompress, bool noheader, int window_bits)
70+
: reset_before_decompress(reset_before_decompress)
71+
, window_bits(window_bits) {
72+
stream = std::make_unique<z_stream>();
73+
stream->zalloc = 0;
74+
stream->zfree = 0;
75+
stream->opaque = 0;
76+
77+
::inflateInit2(stream.get(), (noheader ? -window_bits : window_bits));
78+
}
79+
80+
~zlib_decompressor() {
81+
inflateEnd(stream.get());
82+
}
83+
84+
std::string decompress(std::string src) {
85+
if (reset_before_decompress)
86+
inflateReset(stream.get());
87+
88+
src.push_back('\x00');
89+
src.push_back('\x00');
90+
src.push_back('\xff');
91+
src.push_back('\xff');
92+
93+
stream->next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(src.c_str()));
94+
stream->avail_in = src.size();
95+
96+
const uint64_t bufferSize = 256;
97+
boost::asio::streambuf buffer;
98+
do {
99+
boost::asio::streambuf::mutable_buffers_type chunk = buffer.prepare(bufferSize);
100+
101+
uint8_t* next_out = boost::asio::buffer_cast<uint8_t*>(chunk);
102+
103+
stream->next_out = next_out;
104+
stream->avail_out = bufferSize;
105+
106+
::inflate(stream.get(), reset_before_decompress ? Z_FINISH : Z_SYNC_FLUSH);
107+
buffer.commit(stream->next_out - next_out);
108+
} while (stream->avail_out == 0);
109+
110+
return std::string(boost::asio::buffer_cast<const char*>(buffer.data()), buffer.size());
111+
}
112+
113+
std::unique_ptr<z_stream> stream;
114+
115+
bool reset_before_decompress;
116+
int window_bits;
117+
};
118+
}
119+
#else
120+
namespace crow {
121+
struct zlib_compressor {
122+
std::string compress(const std::string& src) { return src; }
123+
bool reset_before_compress;
124+
int window_bits;
125+
};
126+
struct zlib_decompressor {
127+
std::string decompress(const std::string& src) { return src; }
128+
bool reset_before_decompress;
129+
int window_bits;
130+
};
131+
}
132+
#endif

0 commit comments

Comments
 (0)