Compare commits

..

86 Commits

Author SHA1 Message Date
317e3c44bf Update CHANGELOG.md 2021-10-22 11:10:42 -07:00
61b90251af Update IXWebSocketVersion.h 2021-10-22 11:09:12 -07:00
851157f252 update api call 2021-10-22 10:59:51 -07:00
69541cf750 define macro for mbedtls >= 3 2021-10-22 10:58:56 -07:00
1e166eee57 cmake change find header file 2021-10-22 10:57:51 -07:00
6860b2d09d Fix mbedtls-3.0 problem
This cause CI to fail on macOS.

See this migration guide => https://github.com/ARMmbed/mbedtls/blob/development/docs/3.0-migration-guide.md
2021-10-22 10:22:57 -07:00
9bbd1f1b30 Update package-lock.json
Fix a security issue with the node.js ws package, which is only used in testing.
2021-09-29 12:18:15 -07:00
18c2b69633 Update build.md 2021-09-29 12:15:31 -07:00
178f218374 Update IXWebSocketVersion.h 2021-09-20 18:19:29 -07:00
6a1aa27b5f Update CHANGELOG.md 2021-09-20 18:19:10 -07:00
e7f89ae529 Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional. (#307)
Co-authored-by: Benjamin Sergeant <bsergean@gmail.com>
2021-09-20 18:15:37 -07:00
cdeaf8e2be use GNUInstallDirs in cmake (#318)
Signed-off-by: NexAdn <git@nexadn.de>

Co-authored-by: NexAdn <git@nexadn.de>
2021-09-20 18:11:46 -07:00
dbafa0aa07 trigger github actions on pull requests (#313) 2021-08-13 05:07:24 -07:00
3baf59a031 (ws) bump CLI command line parsing library from 1.8 to 2.0 2021-07-27 20:48:25 +02:00
b5804c2082 cmake tweak 2021-06-09 10:46:31 -07:00
30bcddb99f (ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global. 2021-06-08 09:49:27 -07:00
47fd04e210 (websocket client + server) WebSocketMessage class tweak to fix unsafe patterns 2021-06-08 09:47:53 -07:00
4f5b0c4f07 Noexcept ix web socket per message deflate options (#299)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage

* Avoid returning references that are mutex protected
Motivation for this MR

The antipattern of returning references to mutex protected members was
removed. Since a caller can hold the reference it would make all class
level locking meaningless.

Instead values are returned. The IXWebSocketPerMessageDeflateOptions
class was shrunk by 7 bytes (1 padding + 2*3) after changing the int
members to the used uint8_t; side effects of that were handled.

An inefficient "string -> int" was replaced by standard library. As
seen here http://coliru.stacked-crooked.com/a/46b5990bafb9c626 this
gives an order of magnitude better performance.

* noexcept string to integer conversion
2021-06-07 11:19:52 -07:00
c2d497abc5 Avoid returning references that are mutex protected (#297)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage

* Avoid returning references that are mutex protected
Motivation for this MR

The antipattern of returning references to mutex protected members was
removed. Since a caller can hold the reference it would make all class
level locking meaningless.

Instead values are returned. The IXWebSocketPerMessageDeflateOptions
class was shrunk by 7 bytes (1 padding + 2*3) after changing the int
members to the used uint8_t; side effects of that were handled.

An inefficient "string -> int" was replaced by standard library. As
seen here http://coliru.stacked-crooked.com/a/46b5990bafb9c626 this
gives an order of magnitude better performance.
2021-06-05 11:23:18 -07:00
bbe2ae6dd3 fix: check the request's headers rather than the empty response's headers for User-Agent and Accept (#296) 2021-06-05 11:19:53 -07:00
26897b2425 Fix unsafe calls and safeguard WebSocketMessage (#294)
* Fix unsafe calls and safeguard WebSocketMessage from being called w/
temporaries

* Use unnamed namespace to express internal linkage
2021-06-03 18:39:38 -07:00
e3c98a03cc (websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade) 2021-05-27 10:54:21 -07:00
97fedf9482 (Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289) 2021-05-27 10:54:21 -07:00
ae187c0e98 Readme: Add mingw to build matrix 2021-05-18 11:15:21 -07:00
0f21a20fe3 Move errno windows definitions to IXNetSystem.h 2021-05-17 19:04:02 -07:00
54db6ec8bb add notes about ssl configuration in demo program 2021-05-09 13:45:01 -07:00
0e0a748037 Remove warnings (#284) 2021-04-19 09:25:06 -07:00
3b19b0eeca http client: DEL is not a verb, but DELETE is, fix #281 2021-04-04 23:27:28 -07:00
dbfe3104e8 Fixed example code for the new API of IXWebSocketServer (#279) 2021-03-26 23:55:34 -07:00
68fd8c20d6 change CI mkdocs invocation 2021-03-25 11:12:59 -07:00
d932af8568 (cmake) install IXUniquePtr.h 2021-03-25 10:55:59 -07:00
3add6d4c2e (ssl + windows) missing include for CertOpenStore function 2021-03-24 08:03:56 -07:00
0d7fb05567 (ixwebsocket) version bump 2021-03-23 21:54:54 -07:00
bf1747ef18 (ixwebsocket) version bump 2021-03-23 21:54:15 -07:00
5c9c05caff bump version 2021-03-23 21:52:49 -07:00
2573ca151b CaseInsensitiveLess::NocaseCompare::operator mingw fix attempt 2021-03-23 21:21:36 -07:00
c5b5fa82be use inet_* wrapper only on mingw 2021-03-23 21:13:18 -07:00
80dff08304 invoke ctest manually on ci for windows + gcc builder 2021-03-23 21:07:26 -07:00
24c2eae3d7 use inet_ntop and inet_pton musl implementations on all platforms 2021-03-23 20:53:19 -07:00
449c5fa138 (ixwebsocket) add getMinWaitBetweenReconnectionRetries 2021-03-23 08:29:50 -07:00
b6234ff908 compile errors due to missing changes for the introduction of setMinWaitBetweenReconnectionRetries and getMinWaitBetweenReconnectionRetries 2021-03-23 08:28:40 -07:00
d26664fccc (ixwebsocket) New option to set the min wait between reconnection attempts. Still default to 1ms. (setMinWaitBetweenReconnectionRetries). 2021-03-23 07:33:48 -07:00
def0243d6d (ws) initialize maxWaitBetweenReconnectionRetries to a non zero value ; a zero value was causing spurious reconnections attempts 2021-03-22 21:10:18 -07:00
1410797d6f document -DBUILD_DEMO=ON 2021-03-22 08:51:58 -07:00
2670187fe0 Adds demo building option. (#278) 2021-03-22 08:47:10 -07:00
95359461d7 enable test on windows + gcc 2021-03-21 10:22:44 -07:00
4d7b149649 mingw: cast fixes 2021-03-21 10:16:06 -07:00
b29a37ce76 mingw: inet_ntop and inet_pton compilation fix, use correct parameter names 2021-03-21 09:50:15 -07:00
9a4dfb40da mingw: add real implementation of inet_ntop and inet_pton taken from musl C library 2021-03-21 09:43:16 -07:00
c4c344518d disable shared-libs on windows where test does not work yet 2021-03-20 10:18:10 -07:00
d706a4a73e doc: document BUILD_SHARED_LIBS 2021-03-20 09:50:21 -07:00
88970604e3 ixwebsocketserver::broadcast server to return a boolean to know whether the server could start/listen, and use that in ws 2021-03-19 11:52:41 -07:00
7fee54464e WebSocketServer::listenAndStart: fix branch where we do not return an integer 2021-03-19 11:48:21 -07:00
1c7634d075 ws: cannot use << with an std::vector 2021-03-19 11:43:29 -07:00
99f9556aa9 ws + mingw: uses << operator to write file to disk in WebSocketReceiver::handleMessage 2021-03-19 11:39:14 -07:00
39b2a3d6df ws curl -O mingw compile fix + detect when we cannot extract a filename from the url to save file to disk with -O option 2021-03-19 11:35:25 -07:00
056b02a494 ws: WebSocketSender uses anonymous namespace load instead of its own method 2021-03-19 11:25:48 -07:00
48166a9a72 mingw: fix compile errors with linenoise and fstream 2021-03-19 11:18:55 -07:00
b36a2d1faa mingw compile fix / remove restrict in inet_* functions 2021-03-19 10:58:38 -07:00
968cc5c1c4 reference wslay as alternative C websocket library 2021-03-19 08:05:01 -07:00
0813eb1788 mention disablePerMessageDeflate in the doc 2021-03-16 09:56:08 -07:00
cadb8336f2 add reference to Teleport which is using ixwebsockets 2021-03-16 09:10:36 -07:00
7fd782f72f add WIN32_LEAN_AND_MEAN windows blip 2021-03-15 19:58:18 -07:00
85bcdaaec3 stub inet_ntop and inet_pton function that mingw does not have 2021-03-14 14:25:40 -07:00
461641f3d0 ci with unity build for windows + gcc 2021-03-14 13:23:16 -07:00
2d65c27d11 rename windows+gcc unittest ci file 2021-03-14 13:18:09 -07:00
6a7785d9d9 no set thread name on mingw 2021-03-13 19:02:20 -08:00
78a670e0c8 more mingw quirks 2021-03-13 18:55:30 -08:00
e63ac69ec6 mock poll struct and macro for mingw 2021-03-13 18:49:29 -08:00
afa15d6dcf mingw build problem fix attempt 2021-03-13 18:31:42 -08:00
432a202c07 use https://github.com/marketplace/actions/install-mingw 2021-03-13 18:21:46 -08:00
d609370a85 change compiler 2021-03-13 18:16:21 -08:00
bbe3a766f4 ci windows_gcc, disable zlib 2021-03-13 18:04:00 -08:00
09d3520b66 Create windows_gcc.yml 2021-03-13 18:00:32 -08:00
f090c7659b (ixwebsocket) Expose setHandshakeTimeout method 2021-03-07 19:29:28 -08:00
7c195219cd reorder methods in IXWebSocket.h 2021-03-07 19:25:53 -08:00
d739662a7c Allow customizing the websocket handshake timeout (#264) 2021-03-07 19:23:43 -08:00
e7f7e470e2 Case sensitive link (#269) 2021-03-04 23:04:04 -08:00
d239738ec6 add a.out to .gitignore 2021-02-19 13:51:10 -08:00
c61975bf75 minor improvement to the main.cpp builtin example 2021-02-19 13:50:50 -08:00
39cc0ed32f add comment in WebSocketServer::makeBroadcastServer 2021-01-28 21:04:18 -08:00
22c3a7264e ws: document bug in ws dnslookup command 2021-01-21 15:07:47 -08:00
ee5a2eb46e mention C++11 compatibility in the readme 2021-01-03 11:48:10 -08:00
f6e34e4b34 stop using C++14 lambda capture init, code should be C++11 compatible 2021-01-03 11:44:05 -08:00
d0359a1764 new makeBroadcastServer websocket server method for classic servers, used by ws 2021-01-03 11:24:12 -08:00
8910ebcc3c enable some unittests on windows 2020-12-26 12:44:06 -08:00
52 changed files with 4537 additions and 1931 deletions

View File

@ -21,5 +21,7 @@ jobs:
pip install pygments pip install pygments
- name: Build doc - name: Build doc
run: | run: |
git clean -dfx .
git fetch
git pull git pull
mkdocs gh-deploy mkdocs gh-deploy

View File

@ -2,7 +2,8 @@ name: linux
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
linux: linux:

View File

@ -2,7 +2,8 @@ name: linux_asan
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
linux: linux:

View File

@ -2,7 +2,8 @@ name: mac_tsan_mbedtls
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
mac_tsan_mbedtls: mac_tsan_mbedtls:

View File

@ -2,7 +2,8 @@ name: mac_tsan_openssl
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
mac_tsan_openssl: mac_tsan_openssl:

View File

@ -2,7 +2,8 @@ name: mac_tsan_sectransport
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
mac_tsan_sectransport: mac_tsan_sectransport:

View File

@ -2,7 +2,8 @@ name: uwp
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
pull_request:
jobs: jobs:
uwp: uwp:

View File

@ -3,6 +3,7 @@ on:
push: push:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
pull_request:
jobs: jobs:
windows: windows:
@ -14,7 +15,7 @@ jobs:
- run: | - run: |
mkdir build mkdir build
cd build cd build
cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 .. cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=OFF -DBUILD_SHARED_LIBS=OFF ..
- run: | - run: |
cd build cd build
ninja ninja

View File

@ -0,0 +1,28 @@
name: windows_gcc
on:
push:
paths-ignore:
- 'docs/**'
pull_request:
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
- uses: seanmiddleditch/gha-setup-ninja@master
- uses: egor-tensin/setup-mingw@v2
- run: |
mkdir build
cd build
cmake -GNinja -DCMAKE_CXX_COMPILER=c++ -DCMAKE_C_COMPILER=cc -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=0 -DCMAKE_UNITY_BUILD=ON ..
- run: |
cd build
ninja
- run: |
cd build
ctest -V
# ninja test
#- run: ../build/test/ixwebsocket_unittest.exe
# working-directory: test

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ ws/.certs/
ws/.srl ws/.srl
ixhttpd ixhttpd
makefile makefile
a.out

View File

@ -1,5 +1,8 @@
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h) find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
# mbedtls-3.0 changed headers files, and we need to ifdef'out a few things
find_path(MBEDTLS_VERSION_GREATER_THAN_3 mbedtls/build_info.h)
find_library(MBEDTLS_LIBRARY mbedtls) find_library(MBEDTLS_LIBRARY mbedtls)
find_library(MBEDX509_LIBRARY mbedx509) find_library(MBEDX509_LIBRARY mbedx509)
find_library(MBEDCRYPTO_LIBRARY mbedcrypto) find_library(MBEDCRYPTO_LIBRARY mbedcrypto)

View File

@ -12,6 +12,8 @@ set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON) set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF) set (CMAKE_CXX_EXTENSIONS OFF)
option (BUILD_DEMO OFF)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif() endif()
@ -86,6 +88,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSocketTLSOptions.h ixwebsocket/IXSocketTLSOptions.h
ixwebsocket/IXStrCaseCompare.h ixwebsocket/IXStrCaseCompare.h
ixwebsocket/IXUdpSocket.h ixwebsocket/IXUdpSocket.h
ixwebsocket/IXUniquePtr.h
ixwebsocket/IXUrlParser.h ixwebsocket/IXUrlParser.h
ixwebsocket/IXUuid.h ixwebsocket/IXUuid.h
ixwebsocket/IXUtf8Validator.h ixwebsocket/IXUtf8Validator.h
@ -111,6 +114,7 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketVersion.h ixwebsocket/IXWebSocketVersion.h
) )
option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF)
option(USE_TLS "Enable TLS support" FALSE) option(USE_TLS "Enable TLS support" FALSE)
if (USE_TLS) if (USE_TLS)
@ -144,7 +148,7 @@ if (USE_TLS)
endif() endif()
endif() endif()
add_library( ixwebsocket STATIC add_library( ixwebsocket
${IXWEBSOCKET_SOURCES} ${IXWEBSOCKET_SOURCES}
${IXWEBSOCKET_HEADERS} ${IXWEBSOCKET_HEADERS}
) )
@ -176,31 +180,43 @@ if (USE_TLS)
# set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0) # set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /opt/local/include/openssl-1.0)
endif() endif()
# Use OPENSSL_ROOT_DIR CMake variable if you need to use your own openssl # This OPENSSL_FOUND check is to help find a cmake manually configured OpenSSL
find_package(OpenSSL REQUIRED) if (NOT OPENSSL_FOUND)
find_package(OpenSSL REQUIRED)
endif()
message(STATUS "OpenSSL: " ${OPENSSL_VERSION}) message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
add_definitions(${OPENSSL_DEFINITIONS}) add_definitions(${OPENSSL_DEFINITIONS})
target_include_directories(ixwebsocket PUBLIC ${OPENSSL_INCLUDE_DIR}) target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>)
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES}) target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
elseif (USE_MBED_TLS) elseif (USE_MBED_TLS)
message(STATUS "TLS configured to use mbedtls") message(STATUS "TLS configured to use mbedtls")
find_package(MbedTLS REQUIRED) # This MBEDTLS_FOUND check is to help find a cmake manually configured MbedTLS
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS}) if (NOT MBEDTLS_FOUND)
find_package(MbedTLS REQUIRED)
if (MBEDTLS_VERSION_GREATER_THAN_3)
target_compile_definitions(ixwebsocket PRIVATE IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3)
endif()
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${MBEDTLS_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES}) target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT) elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport") message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket "-framework foundation" "-framework security") target_link_libraries(ixwebsocket "-framework Foundation" "-framework Security")
endif() endif()
endif() endif()
option(USE_ZLIB "Enable zlib support" TRUE) option(USE_ZLIB "Enable zlib support" TRUE)
if (USE_ZLIB) if (USE_ZLIB)
# Use ZLIB_ROOT CMake variable if you need to use your own zlib # This ZLIB_FOUND check is to help find a cmake manually configured zlib
find_package(ZLIB REQUIRED) if (NOT ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIRS}) find_package(ZLIB REQUIRED)
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${ZLIB_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES}) target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB) target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
@ -238,23 +254,29 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_compile_options(ixwebsocket PRIVATE /MP) target_compile_options(ixwebsocket PRIVATE /MP)
endif() endif()
include(GNUInstallDirs)
target_include_directories(ixwebsocket PUBLIC target_include_directories(ixwebsocket PUBLIC
$<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/> $<BUILD_INTERFACE:${IXWEBSOCKET_INCLUDE_DIRS}/>
$<INSTALL_INTERFACE:include/ixwebsocket> $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket>
) )
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}") set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
install(TARGETS ixwebsocket option(IXWEBSOCKET_INSTALL "Install IXWebSocket" TRUE)
EXPORT ixwebsocket
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include/ixwebsocket/
)
install(EXPORT ixwebsocket if (IXWEBSOCKET_INSTALL)
FILE ixwebsocket-config.cmake install(TARGETS ixwebsocket
NAMESPACE ixwebsocket:: EXPORT ixwebsocket
DESTINATION lib/cmake/ixwebsocket) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
)
install(EXPORT ixwebsocket
FILE ixwebsocket-config.cmake
NAMESPACE ixwebsocket::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket)
endif()
if (USE_WS OR USE_TEST) if (USE_WS OR USE_TEST)
include(FetchContent) include(FetchContent)
@ -273,3 +295,8 @@ if (USE_WS OR USE_TEST)
add_subdirectory(test) add_subdirectory(test)
endif() endif()
endif() endif()
if (BUILD_DEMO)
add_executable(demo main.cpp)
target_link_libraries(demo ixwebsocket)
endif()

View File

@ -15,13 +15,16 @@ A bad security bug affecting users compiling with SSL enabled and OpenSSL as the
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more. * Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
* *
* On macOS * On macOS
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install * $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation * $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out * $ ./a.out
*
* Or use cmake -DBUILD_DEMO=ON option for other platforms
*/ */
#include <ixwebsocket/IXNetSystem.h> #include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h> #include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXUserAgent.h>
#include <iostream> #include <iostream>
int main() int main()
@ -32,6 +35,8 @@ int main()
// Our websocket object // Our websocket object
ix::WebSocket webSocket; ix::WebSocket webSocket;
// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
std::string url("wss://echo.websocket.org"); std::string url("wss://echo.websocket.org");
webSocket.setUrl(url); webSocket.setUrl(url);
@ -44,10 +49,18 @@ int main()
if (msg->type == ix::WebSocketMessageType::Message) if (msg->type == ix::WebSocketMessageType::Message)
{ {
std::cout << "received message: " << msg->str << std::endl; std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
} }
else if (msg->type == ix::WebSocketMessageType::Open) else if (msg->type == ix::WebSocketMessageType::Open)
{ {
std::cout << "Connection established" << std::endl; std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
// Maybe SSL is not configured properly
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
std::cout << "> " << std::flush;
} }
} }
); );
@ -58,13 +71,16 @@ int main()
// Send a message to the server (default to TEXT mode) // Send a message to the server (default to TEXT mode)
webSocket.send("hello world"); webSocket.send("hello world");
while (true) // Display a prompt
{ std::cout << "> " << std::flush;
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text); webSocket.send(text);
std::cout << "> " << std::flush;
} }
return 0; return 0;
@ -77,6 +93,8 @@ IXWebSocket is actively being developed, check out the [changelog](https://machi
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code. IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
## Users ## Users
If your company or project is using this library, feel free to open an issue or PR to amend this list. If your company or project is using this library, feel free to open an issue or PR to amend this list.
@ -87,6 +105,7 @@ If your company or project is using this library, feel free to open an issue or
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod - [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper - [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots - [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
## Alternative libraries ## Alternative libraries
@ -96,6 +115,7 @@ There are plenty of great websocket libraries out there, which might work for yo
* [beast](https://github.com/boostorg/beast) - C++ * [beast](https://github.com/boostorg/beast) - C++
* [libwebsockets](https://libwebsockets.org/) - C * [libwebsockets](https://libwebsockets.org/) - C
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C * [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv. [uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.
@ -112,6 +132,11 @@ To check the performance of a websocket library, you can look at the [autoroute]
| Windows | Disabled | None | [![Build2][5]][0] | | Windows | Disabled | None | [![Build2][5]][0] |
| UWP | Disabled | None | [![Build2][6]][0] | | UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] | | Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
| Mingw | Disabled | None | [![Build2][8]][0] |
* ASAN fails on Linux because of a known problem, we need a
* Some tests are disabled on Windows/UWP because of a pathing problem
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
[0]: https://github.com/machinezone/IXWebSocket [0]: https://github.com/machinezone/IXWebSocket
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg [1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
@ -121,4 +146,5 @@ To check the performance of a websocket library, you can look at the [autoroute]
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg [5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg [6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg [7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
[8]: https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg

View File

@ -2,6 +2,82 @@
All changes to this project will be documented in this file. All changes to this project will be documented in this file.
## [11.3.1] - 2021-10-22
(library/cmake) Compatible with MbedTLS 3 + fix a bug on Windows where the incorrect remote port is computed (#320)
## [11.3.0] - 2021-09-20
(library/cmake) Only find OpenSSL, MbedTLS, zlib if they have not already been found, make CMake install optional (#317) + Use GNUInstallDirs in cmake (#318)
## [11.2.10] - 2021-07-27
(ws) bump CLI command line parsing library from 1.8 to 2.0
## [11.2.9] - 2021-06-08
(ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global.
## [11.2.8] - 2021-06-03
(websocket client + server) WebSocketMessage class tweak to fix unsafe patterns
## [11.2.7] - 2021-05-27
(websocket server) Handle and accept firefox browser special upgrade value (keep-alive, Upgrade)
## [11.2.6] - 2021-05-18
(Windows) move EINVAL (re)definition from IXSocket.h to IXNetSystem.h (fix #289)
## [11.2.5] - 2021-04-04
(http client) DEL is not an HTTP method name, but DELETE is
## [11.2.4] - 2021-03-25
(cmake) install IXUniquePtr.h
## [11.2.3] - 2021-03-24
(ssl + windows) missing include for CertOpenStore function
## [11.2.2] - 2021-03-23
(ixwebsocket) version bump
## [11.2.1] - 2021-03-23
(ixwebsocket) version bump
## [11.2.0] - 2021-03-23
(ixwebsocket) correct mingw support (gcc on windows)
## [11.1.4] - 2021-03-23
(ixwebsocket) add getMinWaitBetweenReconnectionRetries
## [11.1.3] - 2021-03-23
(ixwebsocket) New option to set the min wait between reconnection attempts. Still default to 1ms. (setMinWaitBetweenReconnectionRetries).
## [11.1.2] - 2021-03-22
(ws) initialize maxWaitBetweenReconnectionRetries to a non zero value ; a zero value was causing spurious reconnections attempts
## [11.1.1] - 2021-03-20
(cmake) Library can be built as a static or a dynamic library, controlled with BUILD_SHARED_LIBS. Default to static library
## [11.1.0] - 2021-03-16
(ixwebsocket) Use LEAN_AND_MEAN Windows define to help with undefined link error when building a DLL. Support websocket server disablePerMessageDeflate option correctly.
## [11.0.9] - 2021-03-07
(ixwebsocket) Expose setHandshakeTimeout method
## [11.0.8] - 2020-12-25 ## [11.0.8] - 2020-12-25
(ws) trim ws dependencies no more ixcrypto and ixcore deps (ws) trim ws dependencies no more ixcrypto and ixcore deps

View File

@ -17,13 +17,13 @@ There is a unittest which can be executed by typing `make test`.
Options for building: Options for building:
* `-DBUILD_SHARED_LIBS=ON` will build the unittest as a shared libary instead of a static library, which is the default
* `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension * `-DUSE_ZLIB=1` will enable zlib support, required for http client + server + websocket per message deflate extension
* `-DUSE_TLS=1` will enable TLS support * `-DUSE_TLS=1` will enable TLS support
* `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows) * `-DUSE_OPEN_SSL=1` will use [openssl](https://www.openssl.org/) for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this [comment](https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032))
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support * `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support
* `-DUSE_WS=1` will build the ws interactive command line tool * `-DUSE_WS=1` will build the ws interactive command line tool
* `-DUSE_TEST=1` will build the unittest * `-DUSE_TEST=1` will build the unittest
* `-DUSE_PYTHON=1` will use Python3 for cobra bots, require Python3 to be installed.
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies. If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file (not maintained much though) or rather the [github actions](https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml) which have instructions for building dependencies.

View File

@ -256,11 +256,24 @@ Wait time(ms): 6400
Wait time(ms): 10000 Wait time(ms): 10000
``` ```
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried. The waiting time is capped by default at 10s between 2 attempts, but that value
can be changed and queried. The minimum waiting time can also be set.
```cpp ```cpp
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries(); uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
webSocket.setMinWaitBetweenReconnectionRetries(1000); // 1000ms = 1s
uint32_t m = webSocket.getMinWaitBetweenReconnectionRetries();
```
## Handshake timeout
You can control how long to wait until timing out while waiting for the websocket handshake to be performed.
```
int handshakeTimeoutSecs = 1;
setHandshakeTimeout(handshakeTimeoutSecs);
``` ```
## WebSocket server API ## WebSocket server API
@ -334,6 +347,10 @@ if (!res.first)
return 1; return 1;
} }
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop() // Run the server in the background. Server can be stoped by calling server.stop()
server.start(); server.start();
@ -357,13 +374,10 @@ The webSocket reference is guaranteed to be always valid ; by design the callbac
// Bound host name, max connections and listen backlog can also be passed in as parameters. // Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port); ix::WebSocketServer server(port);
server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionState, server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
WebSocket& webSocket,
const WebSocketMessagePtr& msg)
{
// The ConnectionState object contains information about the connection, // The ConnectionState object contains information about the connection,
// at this point only the client ip address and the port. // at this point only the client ip address and the port.
std::cout << "Remote ip: " << connectionState->getRemoteIp(); std::cout << "Remote ip: " << connectionState->getRemoteIp() << std::endl;
if (msg->type == ix::WebSocketMessageType::Open) if (msg->type == ix::WebSocketMessageType::Open)
{ {
@ -381,7 +395,7 @@ server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionSta
std::cout << "Headers:" << std::endl; std::cout << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers) for (auto it : msg->openInfo.headers)
{ {
std::cout << it.first << ": " << it.second << std::endl; std::cout << "\t" << it.first << ": " << it.second << std::endl;
} }
} }
else if (msg->type == ix::WebSocketMessageType::Message) else if (msg->type == ix::WebSocketMessageType::Message)
@ -390,9 +404,11 @@ server.setOnClientMessageCallback(std::shared_ptr<ConnectionState> connectionSta
// All connected clients are available in an std::set. See the broadcast cpp example. // All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode. // Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received. // Here we send it in the same mode as it was received.
std::cout << "Received: " << msg->str << std::endl;
webSocket.send(msg->str, msg->binary); webSocket.send(msg->str, msg->binary);
} }
); });
auto res = server.listen(); auto res = server.listen();
if (!res.first) if (!res.first)
@ -401,6 +417,10 @@ if (!res.first)
return 1; return 1;
} }
// Per message deflate connection is enabled by default. It can be disabled
// which might be helpful when running on low power devices such as a Rasbery Pi
server.disablePerMessageDeflate();
// Run the server in the background. Server can be stoped by calling server.stop() // Run the server in the background. Server can be stoped by calling server.stop()
server.start(); server.start();

View File

@ -24,6 +24,12 @@
#include <string.h> #include <string.h>
#include <thread> #include <thread>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
#define AI_NUMERICSERV NI_NUMERICSERV
#define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif
namespace ix namespace ix
{ {
const int64_t DNSLookup::kDefaultWait = 1; // ms const int64_t DNSLookup::kDefaultWait = 1; // ms

View File

@ -10,16 +10,22 @@
namespace ix namespace ix
{ {
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count, uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries) uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries)
{ {
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0; uint32_t waitTime = (retryCount < 26) ? (std::pow(2, retryCount) * 100) : 0;
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0) if (waitTime < minWaitBetweenReconnectionRetries)
{ {
wait_time = maxWaitBetweenReconnectionRetries; waitTime = minWaitBetweenReconnectionRetries;
} }
return wait_time; if (waitTime > maxWaitBetweenReconnectionRetries || waitTime == 0)
{
waitTime = maxWaitBetweenReconnectionRetries;
}
return waitTime;
} }
} // namespace ix } // namespace ix

View File

@ -10,6 +10,7 @@
namespace ix namespace ix
{ {
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count, uint32_t calculateRetryWaitMilliseconds(uint32_t retryCount,
uint32_t maxWaitBetweenReconnectionRetries); uint32_t maxWaitBetweenReconnectionRetries,
uint32_t minWaitBetweenReconnectionRetries);
} // namespace ix } // namespace ix

View File

@ -137,7 +137,7 @@ namespace ix
{ {
contentLength = std::stoi(headers["Content-Length"]); contentLength = std::stoi(headers["Content-Length"]);
} }
catch (std::exception) catch (const std::exception&)
{ {
return std::make_tuple( return std::make_tuple(
false, "Error parsing HTTP Header 'Content-Length'", httpRequest); false, "Error parsing HTTP Header 'Content-Length'", httpRequest);

View File

@ -20,10 +20,11 @@
namespace ix namespace ix
{ {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
const std::string HttpClient::kPost = "POST"; const std::string HttpClient::kPost = "POST";
const std::string HttpClient::kGet = "GET"; const std::string HttpClient::kGet = "GET";
const std::string HttpClient::kHead = "HEAD"; const std::string HttpClient::kHead = "HEAD";
const std::string HttpClient::kDel = "DEL"; const std::string HttpClient::kDelete = "DELETE";
const std::string HttpClient::kPut = "PUT"; const std::string HttpClient::kPut = "PUT";
const std::string HttpClient::kPatch = "PATCH"; const std::string HttpClient::kPatch = "PATCH";
@ -189,14 +190,14 @@ namespace ix
} }
// Set a default Accept header if none is present // Set a default Accept header if none is present
if (headers.find("Accept") == headers.end()) if (args->extraHeaders.find("Accept") == args->extraHeaders.end())
{ {
ss << "Accept: */*" ss << "Accept: */*"
<< "\r\n"; << "\r\n";
} }
// Set a default User agent if none is present // Set a default User agent if none is present
if (headers.find("User-Agent") == headers.end()) if (args->extraHeaders.find("User-Agent") == args->extraHeaders.end())
{ {
ss << "User-Agent: " << userAgent() << "\r\n"; ss << "User-Agent: " << userAgent() << "\r\n";
} }
@ -557,9 +558,9 @@ namespace ix
return request(url, kHead, std::string(), args); return request(url, kHead, std::string(), args);
} }
HttpResponsePtr HttpClient::del(const std::string& url, HttpRequestArgsPtr args) HttpResponsePtr HttpClient::Delete(const std::string& url, HttpRequestArgsPtr args)
{ {
return request(url, kDel, std::string(), args); return request(url, kDelete, std::string(), args);
} }
HttpResponsePtr HttpClient::request(const std::string& url, HttpResponsePtr HttpClient::request(const std::string& url,

View File

@ -30,7 +30,7 @@ namespace ix
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args); HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args); HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr del(const std::string& url, HttpRequestArgsPtr args); HttpResponsePtr Delete(const std::string& url, HttpRequestArgsPtr args);
HttpResponsePtr post(const std::string& url, HttpResponsePtr post(const std::string& url,
const HttpParameters& httpParameters, const HttpParameters& httpParameters,
@ -94,7 +94,7 @@ namespace ix
const static std::string kPost; const static std::string kPost;
const static std::string kGet; const static std::string kGet;
const static std::string kHead; const static std::string kHead;
const static std::string kDel; const static std::string kDelete;
const static std::string kPut; const static std::string kPut;
const static std::string kPatch; const static std::string kPatch;

View File

@ -124,4 +124,158 @@ namespace ix
#endif #endif
} }
//
// mingw does not have inet_ntop, which were taken as is from the musl C library.
//
const char* inet_ntop(int af, const void* a0, char* s, socklen_t l)
{
#if defined(_WIN32) && defined(__GNUC__)
const unsigned char* a = (const unsigned char*) a0;
int i, j, max, best;
char buf[100];
switch (af)
{
case AF_INET:
if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) return s;
break;
case AF_INET6:
if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12))
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%x:%x",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
256 * a[12] + a[13],
256 * a[14] + a[15]);
else
snprintf(buf,
sizeof buf,
"%x:%x:%x:%x:%x:%x:%d.%d.%d.%d",
256 * a[0] + a[1],
256 * a[2] + a[3],
256 * a[4] + a[5],
256 * a[6] + a[7],
256 * a[8] + a[9],
256 * a[10] + a[11],
a[12],
a[13],
a[14],
a[15]);
/* Replace longest /(^0|:)[:0]{2,}/ with "::" */
for (i = best = 0, max = 2; buf[i]; i++)
{
if (i && buf[i] != ':') continue;
j = strspn(buf + i, ":0");
if (j > max) best = i, max = j;
}
if (max > 3)
{
buf[best] = buf[best + 1] = ':';
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
}
if (strlen(buf) < l)
{
strcpy(s, buf);
return s;
}
break;
default: errno = EAFNOSUPPORT; return 0;
}
errno = ENOSPC;
return 0;
#else
return ::inet_ntop(af, a0, s, l);
#endif
}
#if defined(_WIN32) && defined(__GNUC__)
static int hexval(unsigned c)
{
if (c - '0' < 10) return c - '0';
c |= 32;
if (c - 'a' < 6) return c - 'a' + 10;
return -1;
}
#endif
//
// mingw does not have inet_pton, which were taken as is from the musl C library.
//
int inet_pton(int af, const char* s, void* a0)
{
#if defined(_WIN32) && defined(__GNUC__)
uint16_t ip[8];
unsigned char* a = (unsigned char*) a0;
int i, j, v, d, brk = -1, need_v4 = 0;
if (af == AF_INET)
{
for (i = 0; i < 4; i++)
{
for (v = j = 0; j < 3 && isdigit(s[j]); j++)
v = 10 * v + s[j] - '0';
if (j == 0 || (j > 1 && s[0] == '0') || v > 255) return 0;
a[i] = v;
if (s[j] == 0 && i == 3) return 1;
if (s[j] != '.') return 0;
s += j + 1;
}
return 0;
}
else if (af != AF_INET6)
{
errno = EAFNOSUPPORT;
return -1;
}
if (*s == ':' && *++s != ':') return 0;
for (i = 0;; i++)
{
if (s[0] == ':' && brk < 0)
{
brk = i;
ip[i & 7] = 0;
if (!*++s) break;
if (i == 7) return 0;
continue;
}
for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++)
v = 16 * v + d;
if (j == 0) return 0;
ip[i & 7] = v;
if (!s[j] && (brk >= 0 || i == 7)) break;
if (i == 7) return 0;
if (s[j] != ':')
{
if (s[j] != '.' || (i < 6 && brk < 0)) return 0;
need_v4 = 1;
i++;
break;
}
s += j + 1;
}
if (brk >= 0)
{
memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk));
for (j = 0; j < 7 - i; j++)
ip[brk + j] = 0;
}
for (j = 0; j < 8; j++)
{
*a++ = ip[j] >> 8;
*a++ = ip[j];
}
if (need_v4 && inet_pton(AF_INET, (const char*) s, a - 4) <= 0) return 0;
return 1;
#else
return ::inet_pton(af, s, a0);
#endif
}
} // namespace ix } // namespace ix

View File

@ -7,15 +7,49 @@
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <WS2tcpip.h> #include <WS2tcpip.h>
#include <WinSock2.h> #include <WinSock2.h>
#include <basetsd.h> #include <basetsd.h>
#include <io.h> #include <io.h>
#include <ws2def.h> #include <ws2def.h>
#undef EWOULDBLOCK
#undef EAGAIN
#undef EINPROGRESS
#undef EBADF
#undef EINVAL
// map to WSA error codes
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EAGAIN WSATRY_AGAIN
#define EINPROGRESS WSAEINPROGRESS
#define EBADF WSAEBADF
#define EINVAL WSAEINVAL
// Define our own poll on Windows, as a wrapper on top of select // Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t; typedef unsigned long int nfds_t;
// mingw does not know about poll so mock it
#if defined(__GNUC__)
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
#define POLLIN 0x001 /* There is data to read. */
#define POLLOUT 0x004 /* Writing now will not block. */
#define POLLERR 0x008 /* Error condition. */
#define POLLHUP 0x010 /* Hung up. */
#define POLLNVAL 0x020 /* Invalid polling request. */
#endif
#else #else
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h> #include <errno.h>
@ -44,4 +78,7 @@ namespace ix
bool uninitNetSystem(); bool uninitNetSystem();
int poll(struct pollfd* fds, nfds_t nfds, int timeout); int poll(struct pollfd* fds, nfds_t nfds, int timeout);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
int inet_pton(int af, const char* src, void* dst);
} // namespace ix } // namespace ix

View File

@ -37,6 +37,7 @@ namespace ix
void SetThreadName(DWORD dwThreadID, const char* threadName) void SetThreadName(DWORD dwThreadID, const char* threadName)
{ {
#ifndef __GNUC__
THREADNAME_INFO info; THREADNAME_INFO info;
info.dwType = 0x1000; info.dwType = 0x1000;
info.szName = threadName; info.szName = threadName;
@ -51,6 +52,7 @@ namespace ix
__except (EXCEPTION_EXECUTE_HANDLER) __except (EXCEPTION_EXECUTE_HANDLER)
{ {
} }
#endif
} }
#endif #endif

View File

@ -15,20 +15,6 @@
#ifdef _WIN32 #ifdef _WIN32
#include <BaseTsd.h> #include <BaseTsd.h>
typedef SSIZE_T ssize_t; typedef SSIZE_T ssize_t;
#undef EWOULDBLOCK
#undef EAGAIN
#undef EINPROGRESS
#undef EBADF
#undef EINVAL
// map to WSA error codes
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EAGAIN WSATRY_AGAIN
#define EINPROGRESS WSAEINPROGRESS
#define EBADF WSAEBADF
#define EINVAL WSAEINVAL
#endif #endif
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"

View File

@ -16,6 +16,11 @@
#include "IXSocketConnect.h" #include "IXSocketConnect.h"
#include <string.h> #include <string.h>
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
namespace ix namespace ix
{ {
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd) SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
@ -127,7 +132,11 @@ namespace ix
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'"; errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
return false; return false;
} }
#ifdef IXWEBSOCKET_USE_MBED_TLS_MIN_VERSION_3
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "", mbedtls_ctr_drbg_random, &_ctr_drbg) < 0)
#else
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0) if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
#endif
{ {
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'"; errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
return false; return false;

View File

@ -13,7 +13,7 @@
#include <mbedtls/debug.h> #include <mbedtls/debug.h>
#include <mbedtls/entropy.h> #include <mbedtls/entropy.h>
#include <mbedtls/error.h> #include <mbedtls/error.h>
#include <mbedtls/net.h> #include <mbedtls/net_sockets.h>
#include <mbedtls/platform.h> #include <mbedtls/platform.h>
#include <mbedtls/x509.h> #include <mbedtls/x509.h>
#include <mbedtls/x509_crt.h> #include <mbedtls/x509_crt.h>

View File

@ -24,6 +24,11 @@
#endif #endif
#define socketerrno errno #define socketerrno errno
#ifdef _WIN32
// For manipulating the certificate store
#include <wincrypt.h>
#endif
#ifdef _WIN32 #ifdef _WIN32
namespace namespace
{ {

View File

@ -104,7 +104,7 @@ namespace ix
server.sin_family = _addressFamily; server.sin_family = _addressFamily;
server.sin_port = htons(_port); server.sin_port = htons(_port);
if (inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0) if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton " ss << "SocketServer::listen() error calling inet_pton "
@ -133,7 +133,7 @@ namespace ix
server.sin6_family = _addressFamily; server.sin6_family = _addressFamily;
server.sin6_port = htons(_port); server.sin6_port = htons(_port);
if (inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0) if (ix::inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
{ {
std::stringstream ss; std::stringstream ss;
ss << "SocketServer::listen() error calling inet_pton " ss << "SocketServer::listen() error calling inet_pton "
@ -338,7 +338,7 @@ namespace ix
if (_addressFamily == AF_INET) if (_addressFamily == AF_INET)
{ {
char remoteIp4[INET_ADDRSTRLEN]; char remoteIp4[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr) if (ix::inet_ntop(AF_INET, &client.sin_addr, remoteIp4, INET_ADDRSTRLEN) == nullptr)
{ {
int err = Socket::getErrno(); int err = Socket::getErrno();
std::stringstream ss; std::stringstream ss;
@ -357,7 +357,8 @@ namespace ix
else // AF_INET6 else // AF_INET6
{ {
char remoteIp6[INET6_ADDRSTRLEN]; char remoteIp6[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) == nullptr) if (ix::inet_ntop(AF_INET6, &client.sin_addr, remoteIp6, INET6_ADDRSTRLEN) ==
nullptr)
{ {
int err = Socket::getErrno(); int err = Socket::getErrno();
std::stringstream ss; std::stringstream ss;

View File

@ -14,7 +14,7 @@ namespace ix
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1, bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
const unsigned char& c2) const const unsigned char& c2) const
{ {
#ifdef _WIN32 #if defined(_WIN32) && !defined(__GNUC__)
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale()); return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
#else #else
return std::tolower(c1) < std::tolower(c2); return std::tolower(c1) < std::tolower(c2);

View File

@ -15,6 +15,12 @@
#include <cmath> #include <cmath>
namespace
{
const std::string emptyMsg;
} // namespace
namespace ix namespace ix
{ {
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr; OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
@ -22,12 +28,14 @@ namespace ix
const int WebSocket::kDefaultPingIntervalSecs(-1); const int WebSocket::kDefaultPingIntervalSecs(-1);
const bool WebSocket::kDefaultEnablePong(true); const bool WebSocket::kDefaultEnablePong(true);
const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s
const uint32_t WebSocket::kDefaultMinWaitBetweenReconnectionRetries(1); // 1 ms
WebSocket::WebSocket() WebSocket::WebSocket()
: _onMessageCallback(OnMessageCallback()) : _onMessageCallback(OnMessageCallback())
, _stop(false) , _stop(false)
, _automaticReconnection(true) , _automaticReconnection(true)
, _maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries) , _maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries)
, _minWaitBetweenReconnectionRetries(kDefaultMinWaitBetweenReconnectionRetries)
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs) , _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
, _enablePong(kDefaultEnablePong) , _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs) , _pingIntervalSecs(kDefaultPingIntervalSecs)
@ -36,7 +44,7 @@ namespace ix
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) { [this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
_onMessageCallback( _onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close, ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
"", emptyMsg,
wireSize, wireSize,
WebSocketErrorInfo(), WebSocketErrorInfo(),
WebSocketOpenInfo(), WebSocketOpenInfo(),
@ -56,13 +64,18 @@ namespace ix
_url = url; _url = url;
} }
void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs)
{
_handshakeTimeoutSecs = handshakeTimeoutSecs;
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers) void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
_extraHeaders = headers; _extraHeaders = headers;
} }
const std::string& WebSocket::getUrl() const const std::string WebSocket::getUrl() const
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
return _url; return _url;
@ -81,7 +94,7 @@ namespace ix
_socketTLSOptions = socketTLSOptions; _socketTLSOptions = socketTLSOptions;
} }
const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const const WebSocketPerMessageDeflateOptions WebSocket::getPerMessageDeflateOptions() const
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
return _perMessageDeflateOptions; return _perMessageDeflateOptions;
@ -131,12 +144,24 @@ namespace ix
_maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries; _maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries;
} }
void WebSocket::setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_minWaitBetweenReconnectionRetries = minWaitBetweenReconnectionRetries;
}
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
return _maxWaitBetweenReconnectionRetries; return _maxWaitBetweenReconnectionRetries;
} }
uint32_t WebSocket::getMinWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _minWaitBetweenReconnectionRetries;
}
void WebSocket::start() void WebSocket::start()
{ {
if (_thread.joinable()) return; // we've already been started if (_thread.joinable()) return; // we've already been started
@ -198,7 +223,7 @@ namespace ix
_onMessageCallback(ix::make_unique<WebSocketMessage>( _onMessageCallback(ix::make_unique<WebSocketMessage>(
WebSocketMessageType::Open, WebSocketMessageType::Open,
"", emptyMsg,
0, 0,
WebSocketErrorInfo(), WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers, status.protocol), WebSocketOpenInfo(status.uri, status.headers, status.protocol),
@ -213,7 +238,9 @@ namespace ix
return status; return status;
} }
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket, int timeoutSecs) WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate)
{ {
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);
@ -221,7 +248,8 @@ namespace ix
_perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs); _perMessageDeflateOptions, _socketTLSOptions, _enablePong, _pingIntervalSecs);
} }
WebSocketInitResult status = _ws.connectToSocket(std::move(socket), timeoutSecs); WebSocketInitResult status =
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate);
if (!status.success) if (!status.success)
{ {
return status; return status;
@ -229,7 +257,7 @@ namespace ix
_onMessageCallback( _onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open, ix::make_unique<WebSocketMessage>(WebSocketMessageType::Open,
"", emptyMsg,
0, 0,
WebSocketErrorInfo(), WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers), WebSocketOpenInfo(status.uri, status.headers),
@ -303,8 +331,10 @@ namespace ix
if (_automaticReconnection) if (_automaticReconnection)
{ {
duration = millis(calculateRetryWaitMilliseconds( duration =
retries++, _maxWaitBetweenReconnectionRetries)); millis(calculateRetryWaitMilliseconds(retries++,
_maxWaitBetweenReconnectionRetries,
_minWaitBetweenReconnectionRetries));
connectErr.wait_time = duration.count(); connectErr.wait_time = duration.count();
connectErr.retries = retries; connectErr.retries = retries;
@ -314,7 +344,7 @@ namespace ix
connectErr.http_status = status.http_status; connectErr.http_status = status.http_status;
_onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error, _onMessageCallback(ix::make_unique<WebSocketMessage>(WebSocketMessageType::Error,
"", emptyMsg,
0, 0,
connectErr, connectErr,
WebSocketOpenInfo(), WebSocketOpenInfo(),

View File

@ -58,6 +58,7 @@ namespace ix
void enablePerMessageDeflate(); void enablePerMessageDeflate();
void disablePerMessageDeflate(); void disablePerMessageDeflate();
void addSubProtocol(const std::string& subProtocol); void addSubProtocol(const std::string& subProtocol);
void setHandshakeTimeout(int handshakeTimeoutSecs);
// Run asynchronously, by calling start and stop. // Run asynchronously, by calling start and stop.
void start(); void start();
@ -91,8 +92,8 @@ namespace ix
ReadyState getReadyState() const; ReadyState getReadyState() const;
static std::string readyStateToString(ReadyState readyState); static std::string readyStateToString(ReadyState readyState);
const std::string& getUrl() const; const std::string getUrl() const;
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const; const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
int getPingInterval() const; int getPingInterval() const;
size_t bufferedAmount() const; size_t bufferedAmount() const;
@ -100,7 +101,9 @@ namespace ix
void disableAutomaticReconnection(); void disableAutomaticReconnection();
bool isAutomaticReconnectionEnabled() const; bool isAutomaticReconnectionEnabled() const;
void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries); void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries);
void setMinWaitBetweenReconnectionRetries(uint32_t minWaitBetweenReconnectionRetries);
uint32_t getMaxWaitBetweenReconnectionRetries() const; uint32_t getMaxWaitBetweenReconnectionRetries() const;
uint32_t getMinWaitBetweenReconnectionRetries() const;
const std::vector<std::string>& getSubProtocols(); const std::vector<std::string>& getSubProtocols();
private: private:
@ -114,7 +117,9 @@ namespace ix
static void invokeTrafficTrackerCallback(size_t size, bool incoming); static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server // Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>, int timeoutSecs); WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
int timeoutSecs,
bool enablePerMessageDeflate);
WebSocketTransport _ws; WebSocketTransport _ws;
@ -137,7 +142,9 @@ namespace ix
// Automatic reconnection // Automatic reconnection
std::atomic<bool> _automaticReconnection; std::atomic<bool> _automaticReconnection;
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries; static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
static const uint32_t kDefaultMinWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries; uint32_t _maxWaitBetweenReconnectionRetries;
uint32_t _minWaitBetweenReconnectionRetries;
// Make the sleeping in the automatic reconnection cancellable // Make the sleeping in the automatic reconnection cancellable
std::mutex _sleepMutex; std::mutex _sleepMutex;

View File

@ -204,6 +204,9 @@ namespace ix
// Check the value of the connection field // Check the value of the connection field
// Some websocket servers (Go/Gorilla?) send lowercase values for the // Some websocket servers (Go/Gorilla?) send lowercase values for the
// connection header, so do a case insensitive comparison // connection header, so do a case insensitive comparison
//
// See https://github.com/apache/thrift/commit/7c4bdf9914fcba6c89e0f69ae48b9675578f084a
//
if (!insensitiveStringCompare(headers["connection"], "Upgrade")) if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
{ {
std::stringstream ss; std::stringstream ss;
@ -241,7 +244,8 @@ namespace ix
return WebSocketInitResult(true, status, "", headers, path); return WebSocketInitResult(true, status, "", headers, path);
} }
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs) WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate)
{ {
_requestInitCancellation = false; _requestInitCancellation = false;
@ -295,7 +299,8 @@ namespace ix
return sendErrorResponse(400, "Missing Upgrade header"); return sendErrorResponse(400, "Missing Upgrade header");
} }
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket")) if (!insensitiveStringCompare(headers["upgrade"], "WebSocket") &&
headers["Upgrade"] != "keep-alive, Upgrade") // special case for firefox
{ {
return sendErrorResponse(400, return sendErrorResponse(400,
"Invalid Upgrade header, " "Invalid Upgrade header, "
@ -338,7 +343,7 @@ namespace ix
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header); WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
// If the client has requested that extension, // If the client has requested that extension,
if (webSocketPerMessageDeflateOptions.enabled()) if (webSocketPerMessageDeflateOptions.enabled() && enablePerMessageDeflate)
{ {
_enablePerMessageDeflate = true; _enablePerMessageDeflate = true;

View File

@ -35,7 +35,7 @@ namespace ix
int port, int port,
int timeoutSecs); int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs); WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate);
private: private:
std::string genRandomString(const int len); std::string genRandomString(const int len);

View File

@ -42,6 +42,18 @@ namespace ix
{ {
; ;
} }
/**
* @brief Deleted overload to prevent binding `str` to a temporary, which would cause
* undefined behavior since class members don't extend lifetime beyond the constructor call.
*/
WebSocketMessage(WebSocketMessageType t,
std::string&& s,
size_t w,
WebSocketErrorInfo e,
WebSocketOpenInfo o,
WebSocketCloseInfo c,
bool b = false) = delete;
}; };
using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>; using WebSocketMessagePtr = std::unique_ptr<WebSocketMessage>;

View File

@ -14,12 +14,12 @@ namespace ix
{ {
/// Default values as defined in the RFC /// Default values as defined in the RFC
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15; const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15;
static const int minServerMaxWindowBits = 8; static const uint8_t minServerMaxWindowBits = 8;
static const int maxServerMaxWindowBits = 15; static const uint8_t maxServerMaxWindowBits = 15;
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15; const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
static const int minClientMaxWindowBits = 8; static const uint8_t minClientMaxWindowBits = 8;
static const int maxClientMaxWindowBits = 15; static const uint8_t maxClientMaxWindowBits = 15;
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions( WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
bool enabled, bool enabled,
@ -85,11 +85,7 @@ namespace ix
if (startsWith(token, "server_max_window_bits=")) if (startsWith(token, "server_max_window_bits="))
{ {
std::string val = token.substr(token.find_last_of("=") + 1); uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
std::stringstream ss;
ss << val;
int x;
ss >> x;
// Sanitize values to be in the proper range [8, 15] in // Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values // case a server would give us bogus values
@ -99,11 +95,7 @@ namespace ix
if (startsWith(token, "client_max_window_bits=")) if (startsWith(token, "client_max_window_bits="))
{ {
std::string val = token.substr(token.find_last_of("=") + 1); uint8_t x = strtol(token.substr(token.find_last_of("=") + 1).c_str(), nullptr, 10);
std::stringstream ss;
ss << val;
int x;
ss >> x;
// Sanitize values to be in the proper range [8, 15] in // Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values // case a server would give us bogus values

View File

@ -39,8 +39,8 @@ namespace ix
bool _enabled; bool _enabled;
bool _clientNoContextTakeover; bool _clientNoContextTakeover;
bool _serverNoContextTakeover; bool _serverNoContextTakeover;
int _clientMaxWindowBits; uint8_t _clientMaxWindowBits;
int _serverMaxWindowBits; uint8_t _serverMaxWindowBits;
void sanitizeClientMaxWindowBits(); void sanitizeClientMaxWindowBits();
}; };

View File

@ -97,9 +97,10 @@ namespace ix
} }
else if (_onClientMessageCallback) else if (_onClientMessageCallback)
{ {
WebSocket* webSocketRawPtr = webSocket.get();
webSocket->setOnMessageCallback( webSocket->setOnMessageCallback(
[this, &ws = *webSocket.get(), connectionState](const WebSocketMessagePtr& msg) { [this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
_onClientMessageCallback(connectionState, ws, msg); _onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
}); });
} }
else else
@ -128,7 +129,8 @@ namespace ix
_clients.insert(webSocket); _clients.insert(webSocket);
} }
auto status = webSocket->connectToSocket(std::move(socket), _handshakeTimeoutSecs); auto status = webSocket->connectToSocket(
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate);
if (status.success) if (status.success)
{ {
// Process incoming messages and execute callbacks // Process incoming messages and execute callbacks
@ -168,4 +170,45 @@ namespace ix
std::lock_guard<std::mutex> lock(_clientsMutex); std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients.size(); return _clients.size();
} }
//
// Classic servers
//
void WebSocketServer::makeBroadcastServer()
{
setOnClientMessageCallback([this](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const WebSocketMessagePtr& msg) {
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Message)
{
for (auto&& client : getClients())
{
if (client.get() != &webSocket)
{
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on
do
{
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
});
}
bool WebSocketServer::listenAndStart()
{
auto res = listen();
if (!res.first)
{
return false;
}
start();
return true;
}
} // namespace ix } // namespace ix

View File

@ -47,6 +47,9 @@ namespace ix
// Get all the connected clients // Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients(); std::set<std::shared_ptr<WebSocket>> getClients();
void makeBroadcastServer();
bool listenAndStart();
const static int kDefaultHandShakeTimeoutSecs; const static int kDefaultHandShakeTimeoutSecs;
private: private:

View File

@ -169,7 +169,8 @@ namespace ix
// Server // Server
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs) int timeoutSecs,
bool enablePerMessageDeflate)
{ {
std::lock_guard<std::mutex> lock(_socketMutex); std::lock_guard<std::mutex> lock(_socketMutex);
@ -186,7 +187,7 @@ namespace ix
_perMessageDeflateOptions, _perMessageDeflateOptions,
_enablePerMessageDeflate); _enablePerMessageDeflate);
auto result = webSocketHandshake.serverHandshake(timeoutSecs); auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate);
if (result.success) if (result.success)
{ {
setReadyState(ReadyState::OPEN); setReadyState(ReadyState::OPEN);

View File

@ -83,7 +83,9 @@ namespace ix
int timeoutSecs); int timeoutSecs);
// Server // Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket, int timeoutSecs); WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate);
PollResult poll(); PollResult poll();
WebSocketSendInfo sendBinary(const std::string& message, WebSocketSendInfo sendBinary(const std::string& message,

View File

@ -6,4 +6,4 @@
#pragma once #pragma once
#define IX_WEBSOCKET_VERSION "11.0.8" #define IX_WEBSOCKET_VERSION "11.3.1"

View File

@ -9,10 +9,13 @@
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install * $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation * $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out * $ ./a.out
*
* Or use cmake -DBUILD_DEMO=ON option for other platform
*/ */
#include <ixwebsocket/IXNetSystem.h> #include <ixwebsocket/IXNetSystem.h>
#include <ixwebsocket/IXWebSocket.h> #include <ixwebsocket/IXWebSocket.h>
#include <ixwebsocket/IXUserAgent.h>
#include <iostream> #include <iostream>
int main() int main()
@ -23,9 +26,12 @@ int main()
// Our websocket object // Our websocket object
ix::WebSocket webSocket; ix::WebSocket webSocket;
// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
std::string url("wss://echo.websocket.org"); std::string url("wss://echo.websocket.org");
webSocket.setUrl(url); webSocket.setUrl(url);
std::cout << ix::userAgent() << std::endl;
std::cout << "Connecting to " << url << "..." << std::endl; std::cout << "Connecting to " << url << "..." << std::endl;
// Setup a callback to be fired (in a background thread, watch out for race conditions !) // Setup a callback to be fired (in a background thread, watch out for race conditions !)
@ -35,10 +41,18 @@ int main()
if (msg->type == ix::WebSocketMessageType::Message) if (msg->type == ix::WebSocketMessageType::Message)
{ {
std::cout << "received message: " << msg->str << std::endl; std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
} }
else if (msg->type == ix::WebSocketMessageType::Open) else if (msg->type == ix::WebSocketMessageType::Open)
{ {
std::cout << "Connection established" << std::endl; std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
// Maybe SSL is not configured properly
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
std::cout << "> " << std::flush;
} }
} }
); );
@ -49,13 +63,16 @@ int main()
// Send a message to the server (default to TEXT mode) // Send a message to the server (default to TEXT mode)
webSocket.send("hello world"); webSocket.send("hello world");
while (true) // Display a prompt
{ std::cout << "> " << std::flush;
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text); webSocket.send(text);
std::cout << "> " << std::flush;
} }
return 0; return 0;

View File

@ -22,37 +22,37 @@ install: brew
# Default rule does not use python as that requires first time users to have Python3 installed # Default rule does not use python as that requires first time users to have Python3 installed
# #
brew: brew:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the # Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
# server side ?) and I can't work-around it easily, so we're using mbedtls on # server side ?) and I can't work-around it easily, so we're using mbedtls on
# Linux for the SSL backend, which works great. # Linux for the SSL backend, which works great.
ws_mbedtls_install: ws_mbedtls_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
ws: ws:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
ws_unity: ws_unity:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
ws_install: ws_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
ws_install_release: ws_install_release:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
ws_openssl_install: ws_openssl_install:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install)
ws_mbedtls: ws_mbedtls:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
ws_no_ssl: ws_no_ssl:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
ws_no_python: ws_no_python:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install) mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install)
uninstall: uninstall:
xargs rm -fv < build/install_manifest.txt xargs rm -fv < build/install_manifest.txt
@ -111,27 +111,27 @@ test_server:
(cd test && npm i ws && node broadcast-server.js) (cd test && npm i ws && node broadcast-server.js)
test: test:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..) mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 ..)
(cd build ; ninja) (cd build ; ninja)
(cd build ; ninja test) (cd build ; ninja -v test)
test_asan: test_asan:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer") mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer")
(cd build ; ninja) (cd build ; ninja)
(cd build ; ctest -V .) (cd build ; ctest -V .)
test_tsan_mbedtls: test_tsan_mbedtls:
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
(cd build ; ninja) (cd build ; ninja)
(cd build ; ninja test) (cd build ; ninja test)
test_tsan_openssl: test_tsan_openssl:
mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
(cd build ; ninja) (cd build ; ninja)
(cd build ; ninja test) (cd build ; ninja test)
test_tsan_sectransport: test_tsan_sectransport:
mkdir -p build && (cd build ; cmake -GNinja --DCMAKE_UNITY_BUILD=ON DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer") mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_UNITY_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPENS_SSL=1 -DUSE_TEST=1 .. -DCMAKE_C_FLAGS="-fsanitize=thread -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer")
(cd build ; ninja) (cd build ; ninja)
(cd build ; ninja test) (cd build ; ninja test)

View File

@ -33,11 +33,7 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80); auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
std::string errMsg; std::string errMsg;
struct addrinfo* res = dnsLookup->resolve(errMsg, struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
[]
{
return false;
});
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }
@ -48,11 +44,7 @@ TEST_CASE("dns", "[net]")
std::string errMsg; std::string errMsg;
// The callback returning true means we are requesting cancellation // The callback returning true means we are requesting cancellation
struct addrinfo* res = dnsLookup->resolve(errMsg, struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
[]
{
return true;
});
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }

View File

@ -29,6 +29,7 @@ namespace ix
// Comparison should be case insensitive // Comparison should be case insensitive
REQUIRE(httpHeaders["Foo"] == "foo"); REQUIRE(httpHeaders["Foo"] == "foo");
REQUIRE(httpHeaders["Foo"] != "bar");
} }
SECTION("2") SECTION("2")
@ -39,7 +40,7 @@ namespace ix
headers["Upgrade"] = "webSocket"; headers["Upgrade"] = "webSocket";
REQUIRE(CaseInsensitiveLess::cmp(headers["upgrade"], "WebSocket") == 0); REQUIRE(!CaseInsensitiveLess::cmp(headers["upGRADE"], "webSocket"));
} }
} }

View File

@ -139,8 +139,9 @@ namespace ix
std::streamoff size = file.tellg(); std::streamoff size = file.tellg();
file.seekg(0, file.beg); file.seekg(0, file.beg);
memblock.resize((size_t) size); memblock.reserve((size_t) size);
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size)); memblock.insert(
memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
return memblock; return memblock;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1639,7 +1639,10 @@ bool enableRawMode(int fd) {
/* Init windows console handles only once */ /* Init windows console handles only once */
hOut = GetStdHandle(STD_OUTPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut==INVALID_HANDLE_VALUE) goto fatal; if (hOut==INVALID_HANDLE_VALUE) {
errno = ENOTTY;
return false;
}
} }
DWORD consolemodeOut; DWORD consolemodeOut;

2
ws/package-lock.json generated
View File

@ -8,7 +8,7 @@
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
}, },
"ws": { "ws": {
"version": "6.2.0", "version": ">=6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==", "integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
"requires": { "requires": {

205
ws/ws.cpp
View File

@ -55,16 +55,17 @@ namespace
std::pair<bool, std::vector<uint8_t>> load(const std::string& path) std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
{ {
std::vector<uint8_t> memblock; std::vector<uint8_t> memblock;
std::ifstream file(path); std::ifstream file(path);
if (!file.is_open()) return std::make_pair(false, memblock); if (!file.is_open()) return std::make_pair(false, memblock);
file.seekg(0, file.end); file.seekg(0, file.end);
std::streamoff size = file.tellg(); std::streamoff size = file.tellg();
file.seekg(0, file.beg); file.seekg(0, file.beg);
memblock.resize((size_t) size); memblock.reserve((size_t) size);
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size)); memblock.insert(
memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
return std::make_pair(true, memblock); return std::make_pair(true, memblock);
} }
@ -86,9 +87,9 @@ namespace
std::streamoff size = file.tellg(); std::streamoff size = file.tellg();
file.seekg(0, file.beg); file.seekg(0, file.beg);
memblock.resize(size); memblock.reserve((size_t) size);
memblock.insert(
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size)); memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
std::string bytes(memblock.begin(), memblock.end()); std::string bytes(memblock.begin(), memblock.end());
return bytes; return bytes;
@ -439,93 +440,6 @@ namespace ix
return generateReport(url) ? 0 : 1; return generateReport(url) ? 0 : 1;
} }
//
// broadcast server
//
int ws_broadcast_server_main(int port,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions)
{
spdlog::info("Listening on {}:{}", hostname, port);
ix::WebSocketServer server(port, hostname);
server.setTLSOptions(tlsOptions);
server.setOnClientMessageCallback(
[&server](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const WebSocketMessagePtr& msg) {
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Open)
{
spdlog::info("New connection");
spdlog::info("remote ip: {}", remoteIp);
spdlog::info("id: {}", connectionState->getId());
spdlog::info("Uri: {}", msg->openInfo.uri);
spdlog::info("Headers:");
for (auto it : msg->openInfo.headers)
{
spdlog::info("{}: {}", it.first, it.second);
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
spdlog::info("Closed connection: code {} reason {}",
msg->closeInfo.code,
msg->closeInfo.reason);
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
spdlog::info(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Fragment)
{
spdlog::info("Received message fragment");
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
spdlog::info("Received {} bytes", msg->wireSize);
for (auto&& client : server.getClients())
{
if (client.get() != &webSocket)
{
client->send(msg->str, msg->binary, [](int current, int total) -> bool {
spdlog::info("Step {} out of {}", current, total);
return true;
});
do
{
size_t bufferedAmount = client->bufferedAmount();
spdlog::info("{} bytes left to be sent", bufferedAmount);
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
});
auto res = server.listen();
if (!res.first)
{
spdlog::info(res.second);
return 1;
}
server.start();
server.wait();
return 0;
}
/* /*
* ws_chat.cpp * ws_chat.cpp
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
@ -718,7 +632,8 @@ namespace ix
uint32_t maxWaitBetweenReconnectionRetries, uint32_t maxWaitBetweenReconnectionRetries,
const ix::SocketTLSOptions& tlsOptions, const ix::SocketTLSOptions& tlsOptions,
const std::string& subprotocol, const std::string& subprotocol,
int pingIntervalSecs); int pingIntervalSecs,
bool decompressGzipMessages);
void subscribe(const std::string& channel); void subscribe(const std::string& channel);
void start(); void start();
@ -743,6 +658,7 @@ namespace ix
bool _binaryMode; bool _binaryMode;
std::atomic<int> _receivedBytes; std::atomic<int> _receivedBytes;
std::atomic<int> _sentBytes; std::atomic<int> _sentBytes;
bool _decompressGzipMessages;
void log(const std::string& msg); void log(const std::string& msg);
WebSocketHttpHeaders parseHeaders(const std::string& data); WebSocketHttpHeaders parseHeaders(const std::string& data);
@ -756,12 +672,14 @@ namespace ix
uint32_t maxWaitBetweenReconnectionRetries, uint32_t maxWaitBetweenReconnectionRetries,
const ix::SocketTLSOptions& tlsOptions, const ix::SocketTLSOptions& tlsOptions,
const std::string& subprotocol, const std::string& subprotocol,
int pingIntervalSecs) int pingIntervalSecs,
bool decompressGzipMessages)
: _url(url) : _url(url)
, _disablePerMessageDeflate(disablePerMessageDeflate) , _disablePerMessageDeflate(disablePerMessageDeflate)
, _binaryMode(binaryMode) , _binaryMode(binaryMode)
, _receivedBytes(0) , _receivedBytes(0)
, _sentBytes(0) , _sentBytes(0)
, _decompressGzipMessages(decompressGzipMessages)
{ {
if (disableAutomaticReconnection) if (disableAutomaticReconnection)
{ {
@ -870,7 +788,21 @@ namespace ix
{ {
spdlog::info("Received {} bytes", msg->wireSize); spdlog::info("Received {} bytes", msg->wireSize);
ss << "ws_connect: received message: " << msg->str; std::string payload = msg->str;
if (_decompressGzipMessages)
{
std::string decompressedBytes;
if (gzipDecompress(payload, decompressedBytes))
{
payload = decompressedBytes;
}
else
{
spdlog::error("Error decompressing: {}", payload);
}
}
ss << "ws_connect: received message: " << payload;
log(ss.str()); log(ss.str());
} }
else if (msg->type == ix::WebSocketMessageType::Error) else if (msg->type == ix::WebSocketMessageType::Error)
@ -923,7 +855,8 @@ namespace ix
uint32_t maxWaitBetweenReconnectionRetries, uint32_t maxWaitBetweenReconnectionRetries,
const ix::SocketTLSOptions& tlsOptions, const ix::SocketTLSOptions& tlsOptions,
const std::string& subprotocol, const std::string& subprotocol,
int pingIntervalSecs) int pingIntervalSecs,
bool decompressGzipMessages)
{ {
std::cout << "Type Ctrl-D to exit prompt..." << std::endl; std::cout << "Type Ctrl-D to exit prompt..." << std::endl;
WebSocketConnect webSocketChat(url, WebSocketConnect webSocketChat(url,
@ -934,7 +867,8 @@ namespace ix
maxWaitBetweenReconnectionRetries, maxWaitBetweenReconnectionRetries,
tlsOptions, tlsOptions,
subprotocol, subprotocol,
pingIntervalSecs); pingIntervalSecs,
decompressGzipMessages);
webSocketChat.start(); webSocketChat.start();
while (true) while (true)
@ -988,8 +922,11 @@ namespace ix
auto addr = res->ai_addr; auto addr = res->ai_addr;
// FIXME: this display weird addresses / we could steal libuv inet.c
// code which display correct results
char str[INET_ADDRSTRLEN]; char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN); ix::inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN);
spdlog::info("host: {} ip: {}", hostname, str); spdlog::info("host: {} ip: {}", hostname, str);
@ -1508,10 +1445,18 @@ namespace ix
filename = output; filename = output;
} }
spdlog::info("Writing to disk: {}", filename); if (filename.empty())
std::ofstream out(filename); {
out.write((char*) &response->body.front(), response->body.size()); spdlog::error("Cannot save content to disk: No output file supplied, and not "
out.close(); "filename could be extracted from the url {}",
url);
}
else
{
spdlog::info("Writing to disk: {}", filename);
std::ofstream out(filename);
out << response->body;
}
} }
else else
{ {
@ -1968,7 +1913,8 @@ namespace ix
spdlog::info("ws_receive: Writing to disk: {}", filenameTmp); spdlog::info("ws_receive: Writing to disk: {}", filenameTmp);
std::ofstream out(filenameTmp); std::ofstream out(filenameTmp);
out.write((char*) &content.front(), content.size()); std::string contentAsString(content.begin(), content.end());
out << contentAsString;
out.close(); out.close();
spdlog::info("ws_receive: Renaming {} to {}", filenameTmp, filename); spdlog::info("ws_receive: Renaming {} to {}", filenameTmp, filename);
@ -2156,23 +2102,6 @@ namespace ix
_condition.wait(lock); _condition.wait(lock);
} }
std::vector<uint8_t> load(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
if (!file.is_open()) return memblock;
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.resize((size_t) size);
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
return memblock;
}
void WebSocketSender::start() void WebSocketSender::start()
{ {
_webSocket.setUrl(_url); _webSocket.setUrl(_url);
@ -2266,7 +2195,8 @@ namespace ix
std::vector<uint8_t> content; std::vector<uint8_t> content;
{ {
Bench bench("ws_send: load file from disk"); Bench bench("ws_send: load file from disk");
content = load(filename); auto res = load(filename);
content = res.second;
} }
_id = uuid4(); _id = uuid4();
@ -2462,9 +2392,9 @@ namespace ix
else else
{ {
std::string readyStateString = std::string readyStateString =
readyState == ReadyState::Connecting readyState == ReadyState::Connecting ? "Connecting"
? "Connecting" : readyState == ReadyState::Closing ? "Closing"
: readyState == ReadyState::Closing ? "Closing" : "Closed"; : "Closed";
size_t bufferedAmount = client->bufferedAmount(); size_t bufferedAmount = client->bufferedAmount();
spdlog::info( spdlog::info(
@ -2577,9 +2507,10 @@ int main(int argc, char** argv)
int delayMs = -1; int delayMs = -1;
int count = 1; int count = 1;
int msgCount = 1000 * 1000; int msgCount = 1000 * 1000;
uint32_t maxWaitBetweenReconnectionRetries; uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
int pingIntervalSecs = 30; int pingIntervalSecs = 30;
int runCount = 1; int runCount = 1;
bool decompressGzipMessages = false;
auto addGenericOptions = [&pidfile](CLI::App* app) { auto addGenericOptions = [&pidfile](CLI::App* app) {
app->add_option("--pidfile", pidfile, "Pid file"); app->add_option("--pidfile", pidfile, "Pid file");
@ -2642,6 +2573,7 @@ int main(int argc, char** argv)
"Max Wait Time between reconnection retries"); "Max Wait Time between reconnection retries");
connectApp->add_option("--ping_interval", pingIntervalSecs, "Interval between sending pings"); connectApp->add_option("--ping_interval", pingIntervalSecs, "Interval between sending pings");
connectApp->add_option("--subprotocol", subprotocol, "Subprotocol"); connectApp->add_option("--subprotocol", subprotocol, "Subprotocol");
connectApp->add_flag("-g", decompressGzipMessages, "Decompress gziped messages");
addGenericOptions(connectApp); addGenericOptions(connectApp);
addTLSOptions(connectApp); addTLSOptions(connectApp);
@ -2830,7 +2762,8 @@ int main(int argc, char** argv)
maxWaitBetweenReconnectionRetries, maxWaitBetweenReconnectionRetries,
tlsOptions, tlsOptions,
subprotocol, subprotocol,
pingIntervalSecs); pingIntervalSecs,
decompressGzipMessages);
} }
else if (app.got_subcommand("autoroute")) else if (app.got_subcommand("autoroute"))
{ {
@ -2853,9 +2786,19 @@ int main(int argc, char** argv)
ret = ix::ws_push_server( ret = ix::ws_push_server(
port, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong, sendMsg); port, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong, sendMsg);
} }
else if (app.got_subcommand("transfer")) else if (app.got_subcommand("transfer") || app.got_subcommand("broadcast_server"))
{ {
ret = ix::ws_transfer_main(port, hostname, tlsOptions); ix::WebSocketServer server(port, hostname);
server.setTLSOptions(tlsOptions);
server.makeBroadcastServer();
if (!server.listenAndStart())
{
spdlog::error("Error while starting the server");
}
else
{
server.wait();
}
} }
else if (app.got_subcommand("send")) else if (app.got_subcommand("send"))
{ {
@ -2870,10 +2813,6 @@ int main(int argc, char** argv)
{ {
ret = ix::ws_chat_main(url, user); ret = ix::ws_chat_main(url, user);
} }
else if (app.got_subcommand("broadcast_server"))
{
ret = ix::ws_broadcast_server_main(port, hostname, tlsOptions);
}
else if (app.got_subcommand("ping")) else if (app.got_subcommand("ping"))
{ {
ret = ix::ws_ping_pong_main(url, tlsOptions); ret = ix::ws_ping_pong_main(url, tlsOptions);