Compare commits
183 Commits
Kumamon38-
...
v3.1.0
Author | SHA1 | Date | |
---|---|---|---|
68c1bf7017 | |||
257c901255 | |||
15d8c663da | |||
d50125c62d | |||
9262880369 | |||
2b111e8352 | |||
a35cbdfb7c | |||
6a41b7389f | |||
a187e69650 | |||
fcacddbd9f | |||
fa84ade6be | |||
17eaa323ed | |||
6177fe7803 | |||
57976cf613 | |||
977e8794ec | |||
c68848eecc | |||
c6dfb14953 | |||
5bad02ccae | |||
2e379cbf2a | |||
0e23584751 | |||
49fd2a9e53 | |||
6264a8b41d | |||
3990d3bcbf | |||
aa3f201ced | |||
83c261977d | |||
6ca28d96bf | |||
c4a5647b62 | |||
720d5593a5 | |||
13fa325134 | |||
773cbb4907 | |||
a696264b48 | |||
b7db5f77fb | |||
b11678e636 | |||
f746070944 | |||
3323a51ab5 | |||
0e59927384 | |||
5c4840f129 | |||
9ac02323ad | |||
cdbed26d1f | |||
23f171f34d | |||
20b625e483 | |||
f1604c6460 | |||
ba0e007c05 | |||
643e1bf20f | |||
24a32a0603 | |||
c5caf32b77 | |||
09956d7500 | |||
d91c896e46 | |||
042e6a22b8 | |||
14ec12d1f0 | |||
288b05a048 | |||
5af3096070 | |||
570fa01c04 | |||
2a69038c4c | |||
0ba127e447 | |||
7714bdf7e0 | |||
4e5e7ae50a | |||
5741b2f6c1 | |||
76172f92e9 | |||
f8b547c028 | |||
7ccd9e1709 | |||
9217b27d40 | |||
819e9025b1 | |||
53ceab9f91 | |||
a7ed4fe5c3 | |||
3190cd322d | |||
dad2b64e15 | |||
e527ab1613 | |||
d7a0bc212d | |||
aecd5e9c94 | |||
e0edca43d5 | |||
ce70d3d728 | |||
d9be40a0de | |||
e469f04c39 | |||
11774e6825 | |||
42bdfb51c3 | |||
fd637bf1e1 | |||
8085e1416c | |||
671c9f805f | |||
ace7a7ccae | |||
9c3bdf1a77 | |||
f5242b3102 | |||
f1272f059a | |||
91595ff4c2 | |||
3755d29a45 | |||
c2b75399ae | |||
a33ecd1338 | |||
a7e29a9f36 | |||
02399dfa5c | |||
aec2941bac | |||
9315eb5289 | |||
5b2b2ea7b0 | |||
d90b634e80 | |||
6dd8cda074 | |||
701be31554 | |||
25eaf730bc | |||
4edb7447df | |||
5f3de60962 | |||
79c17aba49 | |||
80a90496d9 | |||
bbca803840 | |||
160d3869a9 | |||
afd8f64da8 | |||
6d2548b823 | |||
642356d353 | |||
ba0fa36c2a | |||
12f6cd878d | |||
9aacebbbaf | |||
701c3745c2 | |||
a41d08343c | |||
156288b17b | |||
6467f98241 | |||
b24e4334f6 | |||
bf8abcbf4a | |||
bb484414b1 | |||
fc75b13fae | |||
78f59b4207 | |||
7c5567db56 | |||
ed0e23e8a5 | |||
4c4f99606e | |||
a61586c846 | |||
d64d50c978 | |||
a64b7b0c4a | |||
0caeb81327 | |||
edac7a0171 | |||
abfadad2e9 | |||
2dc1547bbd | |||
5eb23c9764 | |||
9f4b2856b0 | |||
b5fc10326e | |||
8d3a47a873 | |||
4df58f3059 | |||
06b8cb8d3b | |||
ff81f5b496 | |||
c89f73006e | |||
c28951f049 | |||
dfaaaca223 | |||
c7f0bf3d64 | |||
234ce4c173 | |||
f60293b2e7 | |||
9441095637 | |||
f82d38f758 | |||
a7f42f35db | |||
cb1d1bfd85 | |||
28c3f2ea26 | |||
7ecaf1f982 | |||
d0a41f3894 | |||
57562b234f | |||
469d127d61 | |||
d6e9b61c8e | |||
8dc132dbd3 | |||
98e2fbca6a | |||
fa7f0fadde | |||
7fb1b65ddd | |||
77c7fdc636 | |||
2732dfd0f1 | |||
2e4c4b72b6 | |||
fc21ad519b | |||
c65cfd3d26 | |||
8955462f73 | |||
205c8c15bd | |||
78198a0147 | |||
d561e1141e | |||
753fc845ac | |||
5dbc00bbfe | |||
14ec8522ef | |||
0c2d1c22bc | |||
1d39a9c9a9 | |||
b588ed0fa1 | |||
d9f7a138b8 | |||
d3e04ff619 | |||
372dd24cc7 | |||
a9422cf34d | |||
c7e52e6fcd | |||
705e0823cb | |||
8e4cf74974 | |||
0a7157655b | |||
58d65926bb | |||
b178ba16af | |||
e4c09284b5 | |||
9367a1feff | |||
d37ed300e2 | |||
3207ce37b6 |
46
.clang-format
Normal file
46
.clang-format
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormatStyleOptions.html
|
||||||
|
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: true
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
|
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||||
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 100
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^["<](stdafx|pch)\.h[">]$'
|
||||||
|
Priority: -1
|
||||||
|
- Regex: '^<Windows\.h>$'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '^<(WinIoCtl|winhttp|Shellapi)\.h>$'
|
||||||
|
Priority: 4
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 2
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
NamespaceIndentation: All
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||||
|
PointerAlignment: Left
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
Standard: Cpp11
|
||||||
|
UseTab: Never
|
@ -1,3 +1,4 @@
|
|||||||
build
|
build
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
ws/CMakeCache.txt
|
ws/CMakeCache.txt
|
||||||
|
test/build
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
*.pyc
|
||||||
|
50
.travis.yml
50
.travis.yml
@ -1,17 +1,41 @@
|
|||||||
language: cpp
|
language: bash
|
||||||
dist: xenial
|
|
||||||
|
|
||||||
compiler:
|
# See https://github.com/amaiorano/vectrexy/blob/master/.travis.yml
|
||||||
- gcc
|
# for ideas on installing vcpkg
|
||||||
- clang
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
include:
|
||||||
# GCC fails on recent Travis OSX images.
|
# macOS
|
||||||
- compiler: gcc
|
- os: osx
|
||||||
os: osx
|
compiler: clang
|
||||||
|
script:
|
||||||
|
- python test/run.py
|
||||||
|
- make ws
|
||||||
|
|
||||||
script: python test/run.py
|
# # Linux
|
||||||
|
# - os: linux
|
||||||
|
# dist: xenial
|
||||||
|
# script:
|
||||||
|
# - python test/run.py
|
||||||
|
# - make ws
|
||||||
|
# env:
|
||||||
|
# - CC=gcc
|
||||||
|
# - CXX=g++
|
||||||
|
|
||||||
|
# Clang + Linux disabled for now
|
||||||
|
- os: linux
|
||||||
|
dist: xenial
|
||||||
|
script: python test/run.py
|
||||||
|
env:
|
||||||
|
- CC=clang
|
||||||
|
- CXX=clang++
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
- os: windows
|
||||||
|
env:
|
||||||
|
- CMAKE_PATH="/c/Program Files/CMake/bin"
|
||||||
|
script:
|
||||||
|
- export PATH=$CMAKE_PATH:$PATH
|
||||||
|
- cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .
|
||||||
|
- cmake --build --parallel .
|
||||||
|
- python test/run.py
|
||||||
|
@ -26,6 +26,7 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXSocketFactory.cpp
|
ixwebsocket/IXSocketFactory.cpp
|
||||||
ixwebsocket/IXDNSLookup.cpp
|
ixwebsocket/IXDNSLookup.cpp
|
||||||
ixwebsocket/IXCancellationRequest.cpp
|
ixwebsocket/IXCancellationRequest.cpp
|
||||||
|
ixwebsocket/IXNetSystem.cpp
|
||||||
ixwebsocket/IXWebSocket.cpp
|
ixwebsocket/IXWebSocket.cpp
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
ixwebsocket/IXWebSocketServer.cpp
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
ixwebsocket/IXWebSocketTransport.cpp
|
||||||
@ -36,9 +37,12 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXHttpClient.cpp
|
||||||
ixwebsocket/IXUrlParser.cpp
|
ixwebsocket/IXUrlParser.cpp
|
||||||
|
ixwebsocket/LUrlParser.cpp
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
ixwebsocket/IXConnectionState.cpp
|
ixwebsocket/IXConnectionState.cpp
|
||||||
|
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
@ -49,6 +53,7 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/IXSetThreadName.h
|
ixwebsocket/IXSetThreadName.h
|
||||||
ixwebsocket/IXDNSLookup.h
|
ixwebsocket/IXDNSLookup.h
|
||||||
ixwebsocket/IXCancellationRequest.h
|
ixwebsocket/IXCancellationRequest.h
|
||||||
|
ixwebsocket/IXNetSystem.h
|
||||||
ixwebsocket/IXProgressCallback.h
|
ixwebsocket/IXProgressCallback.h
|
||||||
ixwebsocket/IXWebSocket.h
|
ixwebsocket/IXWebSocket.h
|
||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketServer.h
|
||||||
@ -63,9 +68,12 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/libwshandshake.hpp
|
ixwebsocket/libwshandshake.hpp
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXUrlParser.h
|
ixwebsocket/IXUrlParser.h
|
||||||
|
ixwebsocket/LUrlParser.h
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
ixwebsocket/IXSelectInterruptFactory.h
|
ixwebsocket/IXSelectInterruptFactory.h
|
||||||
ixwebsocket/IXConnectionState.h
|
ixwebsocket/IXConnectionState.h
|
||||||
|
ixwebsocket/IXWebSocketCloseConstants.h
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
@ -85,17 +93,26 @@ else()
|
|||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(USE_MBED_TLS TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(USE_OPEN_SSL FALSE)
|
set(USE_OPEN_SSL FALSE)
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
||||||
|
|
||||||
if (APPLE)
|
if (USE_MBED_TLS)
|
||||||
|
add_definitions(-DIXWEBSOCKET_USE_MBED_TLS)
|
||||||
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
||||||
|
elseif (APPLE)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||||
elseif (WIN32)
|
elseif (WIN32)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
||||||
else()
|
else()
|
||||||
|
add_definitions(-DIXWEBSOCKET_USE_OPEN_SSL)
|
||||||
set(USE_OPEN_SSL TRUE)
|
set(USE_OPEN_SSL TRUE)
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||||
@ -107,7 +124,7 @@ add_library( ixwebsocket STATIC
|
|||||||
${IXWEBSOCKET_HEADERS}
|
${IXWEBSOCKET_HEADERS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
|
||||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -119,6 +136,14 @@ if (USE_OPEN_SSL)
|
|||||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (USE_MBED_TLS)
|
||||||
|
set (ENABLE_PROGRAMS OFF)
|
||||||
|
add_subdirectory(third_party/mbedtls)
|
||||||
|
include_directories(third_party/mbedtls/include)
|
||||||
|
|
||||||
|
target_link_libraries(ixwebsocket mbedtls)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_subdirectory(third_party/zlib)
|
add_subdirectory(third_party/zlib)
|
||||||
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
||||||
@ -137,6 +162,11 @@ set( IXWEBSOCKET_INCLUDE_DIRS
|
|||||||
.
|
.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||||
|
# Build with Multiple Processes
|
||||||
|
target_compile_options(ixwebsocket PRIVATE /MP)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
||||||
|
|
||||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
||||||
@ -146,6 +176,6 @@ install(TARGETS ixwebsocket
|
|||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
||||||
)
|
)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (USE_WS)
|
||||||
add_subdirectory(ws)
|
add_subdirectory(ws)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1 +1 @@
|
|||||||
1.4.2
|
2.2.1
|
||||||
|
52
Dockerfile
52
Dockerfile
@ -1,52 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM debian:buster as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install wget
|
|
||||||
RUN mkdir -p /tmp/cmake
|
|
||||||
WORKDIR /tmp/cmake
|
|
||||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
|
|
||||||
RUN apt-get -y install g++
|
|
||||||
RUN apt-get -y install libssl-dev
|
|
||||||
RUN apt-get -y install libz-dev
|
|
||||||
RUN apt-get -y install make
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
RUN ["make"]
|
|
||||||
|
|
||||||
# Runtime
|
|
||||||
FROM debian:buster as runtime
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
# Runtime
|
|
||||||
RUN apt-get install -y libssl1.1
|
|
||||||
RUN apt-get install -y ca-certificates
|
|
||||||
RUN ["update-ca-certificates"]
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
RUN apt-get install -y strace
|
|
||||||
RUN apt-get install -y procps
|
|
||||||
RUN apt-get install -y htop
|
|
||||||
|
|
||||||
RUN adduser --disabled-password --gecos '' app
|
|
||||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
|
||||||
RUN chmod +x /usr/local/bin/ws
|
|
||||||
RUN ldd /usr/local/bin/ws
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
COPY --chown=app:app ws/snake/appsConfig.json .
|
|
||||||
COPY --chown=app:app ws/cobraMetricsSample.json .
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
docker/Dockerfile.alpine
|
88
README.md
88
README.md
@ -10,8 +10,7 @@
|
|||||||
* iOS
|
* iOS
|
||||||
* Linux
|
* Linux
|
||||||
* Android
|
* Android
|
||||||
|
* Windows
|
||||||
The code was made to compile once on Windows but support is currently broken on this platform.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@ -38,7 +37,7 @@ webSocket.setOnMessageCallback(
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Message)
|
if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
std::cout << str << std::endl;
|
std::cout << str << std::endl;
|
||||||
}
|
}
|
||||||
@ -78,7 +77,7 @@ server.setOnConnectionCallback(
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
|
||||||
@ -97,7 +96,7 @@ server.setOnConnectionCallback(
|
|||||||
std::cerr << it.first << ": " << it.second << std::endl;
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
// 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.
|
||||||
@ -130,33 +129,33 @@ Here is what the HTTP client API looks like. Note that HTTP client support is ve
|
|||||||
// Preparation
|
// Preparation
|
||||||
//
|
//
|
||||||
HttpClient httpClient;
|
HttpClient httpClient;
|
||||||
HttpRequestArgs args;
|
HttpRequestArgsPtr args = httpClient.createRequest();
|
||||||
|
|
||||||
// Custom headers can be set
|
// Custom headers can be set
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Foo"] = "bar";
|
headers["Foo"] = "bar";
|
||||||
args.extraHeaders = headers;
|
args->extraHeaders = headers;
|
||||||
|
|
||||||
// Timeout options
|
// Timeout options
|
||||||
args.connectTimeout = connectTimeout;
|
args->connectTimeout = connectTimeout;
|
||||||
args.transferTimeout = transferTimeout;
|
args->transferTimeout = transferTimeout;
|
||||||
|
|
||||||
// Redirect options
|
// Redirect options
|
||||||
args.followRedirects = followRedirects;
|
args->followRedirects = followRedirects;
|
||||||
args.maxRedirects = maxRedirects;
|
args->maxRedirects = maxRedirects;
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
args.compress = compress; // Enable gzip compression
|
args->compress = compress; // Enable gzip compression
|
||||||
args.verbose = verbose;
|
args->verbose = verbose;
|
||||||
args.logger = [](const std::string& msg)
|
args->logger = [](const std::string& msg)
|
||||||
{
|
{
|
||||||
std::cout << msg;
|
std::cout << msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Request
|
// Synchronous Request
|
||||||
//
|
//
|
||||||
HttpResponse out;
|
HttpResponsePtr out;
|
||||||
std::string url = "https://www.google.com";
|
std::string url = "https://www.google.com";
|
||||||
|
|
||||||
// HEAD request
|
// HEAD request
|
||||||
@ -176,13 +175,30 @@ out = httpClient.post(url, std::string("foo=bar"), args);
|
|||||||
//
|
//
|
||||||
// Result
|
// Result
|
||||||
//
|
//
|
||||||
auto statusCode = std::get<0>(out);
|
auto errorCode = response->errorCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
|
||||||
auto errorCode = std::get<1>(out);
|
auto errorCode = response->errorCode; // 200, 404, etc...
|
||||||
auto responseHeaders = std::get<2>(out);
|
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
|
||||||
auto payload = std::get<3>(out);
|
auto payload = response->payload; // All the bytes from the response as an std::string
|
||||||
auto errorMsg = std::get<4>(out);
|
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
|
||||||
auto uploadSize = std::get<5>(out);
|
auto uploadSize = response->uploadSize; // Byte count of uploaded data
|
||||||
auto downloadSize = std::get<6>(out);
|
auto downloadSize = response->downloadSize; // Byte count of downloaded data
|
||||||
|
|
||||||
|
//
|
||||||
|
// Asynchronous Request
|
||||||
|
//
|
||||||
|
bool async = true;
|
||||||
|
HttpClient httpClient(async);
|
||||||
|
auto args = httpClient.createRequest(url, HttpClient::kGet);
|
||||||
|
|
||||||
|
// Push the request to a queue,
|
||||||
|
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
||||||
|
{
|
||||||
|
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
|
||||||
|
auto statusCode = response->statusCode; // acess results
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ok will be false if your httpClient is not async
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
@ -199,6 +215,8 @@ make install # will install to /usr/local on Unix, on macOS it is a good idea to
|
|||||||
|
|
||||||
Headers and a static library will be installed to the target dir.
|
Headers and a static library will be installed to the target dir.
|
||||||
|
|
||||||
|
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
|
||||||
|
|
||||||
There is a unittest which can be executed by typing `make test`.
|
There is a unittest which can be executed by typing `make test`.
|
||||||
|
|
||||||
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
|
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
|
||||||
@ -229,7 +247,7 @@ The per message deflate compression option is supported. It can lead to very nic
|
|||||||
|
|
||||||
### TLS/SSL
|
### TLS/SSL
|
||||||
|
|
||||||
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, and OpenSSL is used on Android and Linux.
|
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL is used on Android and Linux, mbedTLS is used on Windows.
|
||||||
|
|
||||||
### Polling and background thread work
|
### Polling and background thread work
|
||||||
|
|
||||||
@ -245,6 +263,8 @@ Large frames are broken up into smaller chunks or messages to avoid filling up t
|
|||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
|
* On Windows TLS is not setup yet to validate certificates.
|
||||||
|
* There is no convenient way to embed a ca cert.
|
||||||
* No utf-8 validation is made when sending TEXT message with sendText()
|
* No utf-8 validation is made when sending TEXT message with sendText()
|
||||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
||||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
||||||
@ -300,10 +320,10 @@ If the connection was closed and sending failed, the return value will be set to
|
|||||||
|
|
||||||
`getReadyState()` returns the state of the connection. There are 4 possible states.
|
`getReadyState()` returns the state of the connection. There are 4 possible states.
|
||||||
|
|
||||||
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
|
1. ReadyState::Connecting - The connection is not yet open.
|
||||||
2. WebSocket_ReadyState_Open - The connection is open and ready to communicate.
|
2. ReadyState::Open - The connection is open and ready to communicate.
|
||||||
3. WebSocket_ReadyState_Closing - The connection is in the process of closing.
|
3. ReadyState::Closing - The connection is in the process of closing.
|
||||||
4. WebSocket_MessageType_Close - The connection is closed or could not be opened.
|
4. ReadyState::Closed - The connection is closed or could not be opened.
|
||||||
|
|
||||||
### Open and Close notifications
|
### Open and Close notifications
|
||||||
|
|
||||||
@ -318,7 +338,7 @@ webSocket.setOnMessageCallback(
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
std::cout << "send greetings" << std::endl;
|
std::cout << "send greetings" << std::endl;
|
||||||
|
|
||||||
@ -329,7 +349,7 @@ webSocket.setOnMessageCallback(
|
|||||||
std::cout << it.first << ": " << it.second << std::endl;
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
std::cout << "disconnected" << std::endl;
|
std::cout << "disconnected" << std::endl;
|
||||||
|
|
||||||
@ -344,7 +364,7 @@ webSocket.setOnMessageCallback(
|
|||||||
|
|
||||||
### Error notification
|
### Error notification
|
||||||
|
|
||||||
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocket_MessageType_Error`. Multiple fields will be available on the event to describe the error.
|
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
|
||||||
|
|
||||||
```
|
```
|
||||||
webSocket.setOnMessageCallback(
|
webSocket.setOnMessageCallback(
|
||||||
@ -355,7 +375,7 @@ webSocket.setOnMessageCallback(
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Error)
|
if (messageType == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Error: " << error.reason << std::endl;
|
ss << "Error: " << error.reason << std::endl;
|
||||||
@ -395,8 +415,8 @@ webSocket.setOnMessageCallback(
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Ping ||
|
if (messageType == ix::WebSocketMessageType::Ping ||
|
||||||
messageType == ix::WebSocket_MessageType_Pong)
|
messageType == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
std::cout << "pong data: " << str << std::endl;
|
std::cout << "pong data: " << str << std::endl;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
image:
|
image:
|
||||||
- Visual Studio 2017
|
- Visual Studio 2017
|
||||||
- Ubuntu
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- ls -al
|
- ls -al
|
||||||
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
- python test/run.py
|
- cd test
|
||||||
|
- mkdir build
|
||||||
|
- cd build
|
||||||
|
- cmake -G"NMake Makefiles" ..
|
||||||
|
- nmake
|
||||||
|
- ixwebsocket_unittest.exe
|
||||||
|
|
||||||
build: off
|
build: off
|
||||||
|
@ -29,5 +29,15 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- ws-net
|
- ws-net
|
||||||
|
|
||||||
|
statsd:
|
||||||
|
image: jaconel/statsd
|
||||||
|
ports:
|
||||||
|
- "8125:8125"
|
||||||
|
environment:
|
||||||
|
- STATSD_DUMP_MSG=true
|
||||||
|
- GRAPHITE_HOST=127.0.0.1
|
||||||
|
networks:
|
||||||
|
- ws-net
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
ws-net:
|
ws-net:
|
||||||
|
33
docker/Dockerfile.alpine
Normal file
33
docker/Dockerfile.alpine
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
FROM alpine as build
|
||||||
|
|
||||||
|
RUN apk add --no-cache gcc g++ musl-dev linux-headers cmake openssl-dev
|
||||||
|
RUN apk add --no-cache make
|
||||||
|
RUN apk add --no-cache zlib-dev
|
||||||
|
|
||||||
|
RUN addgroup -S app && adduser -S -G app app
|
||||||
|
RUN chown -R app:app /opt
|
||||||
|
RUN chown -R app:app /usr/local
|
||||||
|
|
||||||
|
# There is a bug in CMake where we cannot build from the root top folder
|
||||||
|
# So we build from /opt
|
||||||
|
COPY --chown=app:app . /opt
|
||||||
|
WORKDIR /opt
|
||||||
|
|
||||||
|
USER app
|
||||||
|
RUN [ "make" ]
|
||||||
|
|
||||||
|
FROM alpine as runtime
|
||||||
|
|
||||||
|
RUN apk add --no-cache libstdc++
|
||||||
|
|
||||||
|
RUN addgroup -S app && adduser -S -G app app
|
||||||
|
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
||||||
|
RUN chmod +x /usr/local/bin/ws
|
||||||
|
RUN ldd /usr/local/bin/ws
|
||||||
|
|
||||||
|
# Now run in usermode
|
||||||
|
USER app
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
ENTRYPOINT ["ws"]
|
||||||
|
CMD ["--help"]
|
52
docker/Dockerfile.debian
Normal file
52
docker/Dockerfile.debian
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Build time
|
||||||
|
FROM debian:buster as build
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install wget
|
||||||
|
RUN mkdir -p /tmp/cmake
|
||||||
|
WORKDIR /tmp/cmake
|
||||||
|
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
|
||||||
|
RUN apt-get -y install g++
|
||||||
|
RUN apt-get -y install libssl-dev
|
||||||
|
RUN apt-get -y install libz-dev
|
||||||
|
RUN apt-get -y install make
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
RUN ["make"]
|
||||||
|
|
||||||
|
# Runtime
|
||||||
|
FROM debian:buster as runtime
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
# Runtime
|
||||||
|
RUN apt-get install -y libssl1.1
|
||||||
|
RUN apt-get install -y ca-certificates
|
||||||
|
RUN ["update-ca-certificates"]
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
RUN apt-get install -y strace
|
||||||
|
RUN apt-get install -y procps
|
||||||
|
RUN apt-get install -y htop
|
||||||
|
|
||||||
|
RUN adduser --disabled-password --gecos '' app
|
||||||
|
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
||||||
|
RUN chmod +x /usr/local/bin/ws
|
||||||
|
RUN ldd /usr/local/bin/ws
|
||||||
|
|
||||||
|
# Now run in usermode
|
||||||
|
USER app
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
COPY --chown=app:app ws/snake/appsConfig.json .
|
||||||
|
COPY --chown=app:app ws/cobraMetricsSample.json .
|
||||||
|
|
||||||
|
ENTRYPOINT ["ws"]
|
||||||
|
CMD ["--help"]
|
43
docker/Dockerfile.fedora
Normal file
43
docker/Dockerfile.fedora
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
FROM fedora:30 as build
|
||||||
|
|
||||||
|
RUN yum install -y gcc-g++
|
||||||
|
RUN yum install -y cmake
|
||||||
|
RUN yum install -y make
|
||||||
|
RUN yum install -y openssl-devel
|
||||||
|
|
||||||
|
RUN yum install -y wget
|
||||||
|
RUN mkdir -p /tmp/cmake
|
||||||
|
WORKDIR /tmp/cmake
|
||||||
|
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
|
||||||
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
RUN yum install -y python
|
||||||
|
RUN yum install -y libtsan
|
||||||
|
RUN yum install -y zlib-devel
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
# RUN ["make", "test"]
|
||||||
|
RUN ["make"]
|
||||||
|
|
||||||
|
# Runtime
|
||||||
|
FROM fedora:30 as runtime
|
||||||
|
|
||||||
|
RUN yum install -y libtsan
|
||||||
|
|
||||||
|
RUN groupadd app && useradd -g app app
|
||||||
|
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
||||||
|
RUN chmod +x /usr/local/bin/ws
|
||||||
|
RUN ldd /usr/local/bin/ws
|
||||||
|
|
||||||
|
# Now run in usermode
|
||||||
|
USER app
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
COPY --chown=app:app ws/snake/appsConfig.json .
|
||||||
|
COPY --chown=app:app ws/cobraMetricsSample.json .
|
||||||
|
|
||||||
|
ENTRYPOINT ["ws"]
|
||||||
|
CMD ["--help"]
|
23
docker/Dockerfile.ubuntu_bionic
Normal file
23
docker/Dockerfile.ubuntu_bionic
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Build time
|
||||||
|
FROM ubuntu:bionic as build
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install wget
|
||||||
|
RUN mkdir -p /tmp/cmake
|
||||||
|
WORKDIR /tmp/cmake
|
||||||
|
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
|
||||||
|
RUN apt-get -y install g++
|
||||||
|
RUN apt-get -y install libssl-dev
|
||||||
|
RUN apt-get -y install libz-dev
|
||||||
|
RUN apt-get -y install make
|
||||||
|
RUN apt-get -y install python
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
RUN ["make", "ws"]
|
24
docker/Dockerfile.ubuntu_xenial
Normal file
24
docker/Dockerfile.ubuntu_xenial
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Build time
|
||||||
|
FROM ubuntu:xenial as build
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get -y install wget
|
||||||
|
RUN mkdir -p /tmp/cmake
|
||||||
|
WORKDIR /tmp/cmake
|
||||||
|
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
||||||
|
|
||||||
|
RUN apt-get -y install g++
|
||||||
|
RUN apt-get -y install libssl-dev
|
||||||
|
RUN apt-get -y install libz-dev
|
||||||
|
RUN apt-get -y install make
|
||||||
|
RUN apt-get -y install python
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
# RUN ["make"]
|
||||||
|
RUN ["make", "test"]
|
@ -6,14 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using CancellationRequest = std::function<bool()>;
|
using CancellationRequest = std::function<bool()>;
|
||||||
|
|
||||||
CancellationRequest makeCancellationRequestWithTimeout(int seconds,
|
CancellationRequest makeCancellationRequestWithTimeout(
|
||||||
std::atomic<bool>& requestInitCancellation);
|
int seconds, std::atomic<bool>& requestInitCancellation);
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
@ -6,14 +6,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class ConnectionState {
|
class ConnectionState
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ConnectionState();
|
ConnectionState();
|
||||||
virtual ~ConnectionState() = default;
|
virtual ~ConnectionState() = default;
|
||||||
@ -32,6 +33,4 @@ namespace ix
|
|||||||
|
|
||||||
static std::atomic<uint64_t> _globalId;
|
static std::atomic<uint64_t> _globalId;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,28 +17,25 @@ namespace ix
|
|||||||
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
||||||
std::set<uint64_t> DNSLookup::_activeJobs;
|
std::set<uint64_t> DNSLookup::_activeJobs;
|
||||||
std::mutex DNSLookup::_activeJobsMutex;
|
std::mutex DNSLookup::_activeJobsMutex;
|
||||||
std::mutex DNSLookup::_resMutex;
|
|
||||||
|
|
||||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
||||||
_hostname(hostname),
|
|
||||||
_port(port),
|
_port(port),
|
||||||
_wait(wait),
|
_wait(wait),
|
||||||
_res(nullptr),
|
_res(nullptr),
|
||||||
_done(false),
|
_done(false),
|
||||||
_id(_nextId++)
|
_id(_nextId++)
|
||||||
{
|
{
|
||||||
|
setHostname(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
DNSLookup::~DNSLookup()
|
DNSLookup::~DNSLookup()
|
||||||
{
|
{
|
||||||
// Remove this job from the active jobs list
|
// Remove this job from the active jobs list
|
||||||
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
||||||
_activeJobs.erase(_id);
|
_activeJobs.erase(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we want hostname to be copied, not passed as a const reference
|
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
||||||
struct addrinfo* DNSLookup::getAddrInfo(std::string hostname,
|
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
@ -81,7 +78,7 @@ namespace ix
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAddrInfo(_hostname, _port, errMsg);
|
return getAddrInfo(getHostname(), _port, errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
|
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
|
||||||
@ -99,7 +96,7 @@ namespace ix
|
|||||||
|
|
||||||
// Record job in the active Job set
|
// Record job in the active Job set
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
||||||
_activeJobs.insert(_id);
|
_activeJobs.insert(_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +104,7 @@ namespace ix
|
|||||||
// Good resource on thread forced termination
|
// Good resource on thread forced termination
|
||||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
||||||
//
|
//
|
||||||
_thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
|
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
|
||||||
_thread.detach();
|
_thread.detach();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
@ -137,8 +134,8 @@ namespace ix
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> rlock(_resMutex);
|
errMsg = getErrMsg();
|
||||||
return _res;
|
return getRes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
||||||
@ -150,21 +147,55 @@ namespace ix
|
|||||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||||
|
|
||||||
// if this isn't an active job, and the control thread is gone
|
// if this isn't an active job, and the control thread is gone
|
||||||
// there is not thing to do, and we don't want to touch the defunct
|
// there is nothing to do, and we don't want to touch the defunct
|
||||||
// object data structure such as _errMsg or _condition
|
// object data structure such as _errMsg or _condition
|
||||||
std::unique_lock<std::mutex> lock(_activeJobsMutex);
|
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
||||||
if (_activeJobs.count(id) == 0)
|
if (_activeJobs.count(id) == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy result into the member variables
|
// Copy result into the member variables
|
||||||
{
|
setRes(res);
|
||||||
std::unique_lock<std::mutex> rlock(_resMutex);
|
setErrMsg(errMsg);
|
||||||
_res = res;
|
|
||||||
}
|
|
||||||
_errMsg = errMsg;
|
|
||||||
_condition.notify_one();
|
_condition.notify_one();
|
||||||
_done = true;
|
_done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DNSLookup::setHostname(const std::string& hostname)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
||||||
|
_hostname = hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& DNSLookup::getHostname()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
||||||
|
return _hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSLookup::setErrMsg(const std::string& errMsg)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||||
|
_errMsg = errMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& DNSLookup::getErrMsg()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
||||||
|
return _errMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DNSLookup::setRes(struct addrinfo* addr)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_resMutex);
|
||||||
|
_res = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct addrinfo* DNSLookup::getRes()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_resMutex);
|
||||||
|
return _res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
struct addrinfo;
|
struct addrinfo;
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class DNSLookup {
|
class DNSLookup
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
DNSLookup(const std::string& hostname,
|
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
||||||
int port,
|
|
||||||
int64_t wait = DNSLookup::kDefaultWait);
|
|
||||||
~DNSLookup();
|
~DNSLookup();
|
||||||
|
|
||||||
struct addrinfo* resolve(std::string& errMsg,
|
struct addrinfo* resolve(std::string& errMsg,
|
||||||
@ -39,18 +37,32 @@ namespace ix
|
|||||||
struct addrinfo* resolveBlocking(std::string& errMsg,
|
struct addrinfo* resolveBlocking(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
|
|
||||||
static struct addrinfo* getAddrInfo(std::string hostname,
|
static struct addrinfo* getAddrInfo(const std::string& hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg);
|
std::string& errMsg);
|
||||||
|
|
||||||
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
||||||
|
|
||||||
|
void setHostname(const std::string& hostname);
|
||||||
|
const std::string& getHostname();
|
||||||
|
|
||||||
|
void setErrMsg(const std::string& errMsg);
|
||||||
|
const std::string& getErrMsg();
|
||||||
|
|
||||||
|
void setRes(struct addrinfo* addr);
|
||||||
|
struct addrinfo* getRes();
|
||||||
|
|
||||||
std::string _hostname;
|
std::string _hostname;
|
||||||
|
std::mutex _hostnameMutex;
|
||||||
int _port;
|
int _port;
|
||||||
|
|
||||||
int64_t _wait;
|
int64_t _wait;
|
||||||
std::string _errMsg;
|
|
||||||
struct addrinfo* _res;
|
struct addrinfo* _res;
|
||||||
static std::mutex _resMutex;
|
std::mutex _resMutex;
|
||||||
|
|
||||||
|
std::string _errMsg;
|
||||||
|
std::mutex _errMsgMutex;
|
||||||
|
|
||||||
std::atomic<bool> _done;
|
std::atomic<bool> _done;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
@ -64,4 +76,4 @@ namespace ix
|
|||||||
|
|
||||||
const static int64_t kDefaultWait;
|
const static int64_t kDefaultWait;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -21,24 +21,98 @@ namespace ix
|
|||||||
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::kPut = "PUT";
|
||||||
|
|
||||||
HttpClient::HttpClient()
|
HttpClient::HttpClient(bool async) : _async(async), _stop(false)
|
||||||
{
|
{
|
||||||
|
if (!_async) return;
|
||||||
|
|
||||||
|
_thread = std::thread(&HttpClient::run, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient::~HttpClient()
|
HttpClient::~HttpClient()
|
||||||
{
|
{
|
||||||
|
if (!_thread.joinable()) return;
|
||||||
|
|
||||||
|
_stop = true;
|
||||||
|
_condition.notify_one();
|
||||||
|
_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::request(
|
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url,
|
||||||
|
const std::string& verb)
|
||||||
|
{
|
||||||
|
auto request = std::make_shared<HttpRequestArgs>();
|
||||||
|
request->url = url;
|
||||||
|
request->verb = verb;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpClient::performRequest(HttpRequestArgsPtr args,
|
||||||
|
const OnResponseCallback& onResponseCallback)
|
||||||
|
{
|
||||||
|
if (!_async) return false;
|
||||||
|
|
||||||
|
// Enqueue the task
|
||||||
|
{
|
||||||
|
// acquire lock
|
||||||
|
std::unique_lock<std::mutex> lock(_queueMutex);
|
||||||
|
|
||||||
|
// add the task
|
||||||
|
_queue.push(std::make_pair(args, onResponseCallback));
|
||||||
|
} // release lock
|
||||||
|
|
||||||
|
// wake up one thread
|
||||||
|
_condition.notify_one();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
HttpRequestArgsPtr args;
|
||||||
|
OnResponseCallback onResponseCallback;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(_queueMutex);
|
||||||
|
|
||||||
|
while (!_stop && _queue.empty())
|
||||||
|
{
|
||||||
|
_condition.wait(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stop) return;
|
||||||
|
|
||||||
|
auto p = _queue.front();
|
||||||
|
_queue.pop();
|
||||||
|
|
||||||
|
args = p.first;
|
||||||
|
onResponseCallback = p.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stop) return;
|
||||||
|
|
||||||
|
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
|
||||||
|
onResponseCallback(response);
|
||||||
|
|
||||||
|
if (_stop) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponsePtr HttpClient::request(
|
||||||
const std::string& url,
|
const std::string& url,
|
||||||
const std::string& verb,
|
const std::string& verb,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
const HttpRequestArgs& args,
|
HttpRequestArgsPtr args,
|
||||||
int redirects)
|
int redirects)
|
||||||
{
|
{
|
||||||
|
// We only have one socket connection, so we cannot
|
||||||
|
// make multiple requests concurrently.
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
uint64_t uploadSize = 0;
|
uint64_t uploadSize = 0;
|
||||||
uint64_t downloadSize = 0;
|
uint64_t downloadSize = 0;
|
||||||
int code = 0;
|
int code = 0;
|
||||||
@ -47,15 +121,14 @@ namespace ix
|
|||||||
|
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
bool websocket = false;
|
|
||||||
|
|
||||||
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot parse url: " << url;
|
ss << "Cannot parse url: " << url;
|
||||||
return std::make_tuple(code, HttpErrorCode_UrlMalformed,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tls = protocol == "https";
|
bool tls = protocol == "https";
|
||||||
@ -64,35 +137,45 @@ namespace ix
|
|||||||
|
|
||||||
if (!_socket)
|
if (!_socket)
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build request string
|
// Build request string
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
ss << verb << " " << path << " HTTP/1.1\r\n";
|
||||||
ss << "Host: " << host << "\r\n";
|
ss << "Host: " << host << "\r\n";
|
||||||
ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n";
|
|
||||||
ss << "Accept: */*" << "\r\n";
|
|
||||||
|
|
||||||
if (args.compress)
|
if (args->compress)
|
||||||
{
|
{
|
||||||
ss << "Accept-Encoding: gzip" << "\r\n";
|
ss << "Accept-Encoding: gzip" << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append extra headers
|
// Append extra headers
|
||||||
for (auto&& it : args.extraHeaders)
|
for (auto&& it : args->extraHeaders)
|
||||||
{
|
{
|
||||||
ss << it.first << ": " << it.second << "\r\n";
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verb == kPost)
|
// Set a default Accept header if none is present
|
||||||
|
if (headers.find("Accept") == headers.end())
|
||||||
|
{
|
||||||
|
ss << "Accept: */*" << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a default User agent if none is present
|
||||||
|
if (headers.find("User-Agent") == headers.end())
|
||||||
|
{
|
||||||
|
ss << "User-Agent: ixwebsocket" << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verb == kPost || verb == kPut)
|
||||||
{
|
{
|
||||||
ss << "Content-Length: " << body.size() << "\r\n";
|
ss << "Content-Length: " << body.size() << "\r\n";
|
||||||
|
|
||||||
// Set default Content-Type if unspecified
|
// Set default Content-Type if unspecified
|
||||||
if (args.extraHeaders.find("Content-Type") == args.extraHeaders.end())
|
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
|
||||||
{
|
{
|
||||||
ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
|
ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
|
||||||
}
|
}
|
||||||
@ -110,23 +193,23 @@ namespace ix
|
|||||||
|
|
||||||
// Make a cancellation object dealing with connection timeout
|
// Make a cancellation object dealing with connection timeout
|
||||||
auto isCancellationRequested =
|
auto isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(args.connectTimeout, requestInitCancellation);
|
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
||||||
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to url: " << url;
|
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
||||||
return std::make_tuple(code, HttpErrorCode_CannotConnect,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotConnect,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a new cancellation object dealing with transfer timeout
|
// Make a new cancellation object dealing with transfer timeout
|
||||||
isCancellationRequested =
|
isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(args.transferTimeout, requestInitCancellation);
|
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
||||||
|
|
||||||
if (args.verbose)
|
if (args->verbose)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Sending " << verb << " request "
|
ss << "Sending " << verb << " request "
|
||||||
@ -143,9 +226,9 @@ namespace ix
|
|||||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot send request");
|
std::string errorMsg("Cannot send request");
|
||||||
return std::make_tuple(code, HttpErrorCode_SendError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::SendError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadSize = req.size();
|
uploadSize = req.size();
|
||||||
@ -157,12 +240,12 @@ namespace ix
|
|||||||
if (!lineValid)
|
if (!lineValid)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot retrieve status line");
|
std::string errorMsg("Cannot retrieve status line");
|
||||||
return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadStatusLine,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.verbose)
|
if (args->verbose)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Status line " << line;
|
ss << "Status line " << line;
|
||||||
@ -172,9 +255,9 @@ namespace ix
|
|||||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot parse response code from status line");
|
std::string errorMsg("Cannot parse response code from status line");
|
||||||
return std::make_tuple(code, HttpErrorCode_MissingStatus,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingStatus,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||||
@ -184,29 +267,29 @@ namespace ix
|
|||||||
if (!headersValid)
|
if (!headersValid)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot parse http headers");
|
std::string errorMsg("Cannot parse http headers");
|
||||||
return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::HeaderParsingError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect ?
|
// Redirect ?
|
||||||
if ((code >= 301 && code <= 308) && args.followRedirects)
|
if ((code >= 301 && code <= 308) && args->followRedirects)
|
||||||
{
|
{
|
||||||
if (headers.find("Location") == headers.end())
|
if (headers.find("Location") == headers.end())
|
||||||
{
|
{
|
||||||
std::string errorMsg("Missing location header for redirect");
|
std::string errorMsg("Missing location header for redirect");
|
||||||
return std::make_tuple(code, HttpErrorCode_MissingLocation,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingLocation,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirects >= args.maxRedirects)
|
if (redirects >= args->maxRedirects)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Too many redirects: " << redirects;
|
ss << "Too many redirects: " << redirects;
|
||||||
return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::TooManyRedirects,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse
|
// Recurse
|
||||||
@ -216,9 +299,9 @@ namespace ix
|
|||||||
|
|
||||||
if (verb == "HEAD")
|
if (verb == "HEAD")
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, HttpErrorCode_Ok,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
||||||
headers, payload, std::string(),
|
headers, payload, std::string(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse response:
|
// Parse response:
|
||||||
@ -232,14 +315,14 @@ namespace ix
|
|||||||
payload.reserve(contentLength);
|
payload.reserve(contentLength);
|
||||||
|
|
||||||
auto chunkResult = _socket->readBytes(contentLength,
|
auto chunkResult = _socket->readBytes(contentLength,
|
||||||
args.onProgressCallback,
|
args->onProgressCallback,
|
||||||
isCancellationRequested);
|
isCancellationRequested);
|
||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
payload += chunkResult.second;
|
payload += chunkResult.second;
|
||||||
}
|
}
|
||||||
@ -255,9 +338,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t chunkSize;
|
uint64_t chunkSize;
|
||||||
@ -265,7 +348,7 @@ namespace ix
|
|||||||
ss << std::hex << line;
|
ss << std::hex << line;
|
||||||
ss >> chunkSize;
|
ss >> chunkSize;
|
||||||
|
|
||||||
if (args.verbose)
|
if (args->verbose)
|
||||||
{
|
{
|
||||||
std::stringstream oss;
|
std::stringstream oss;
|
||||||
oss << "Reading " << chunkSize << " bytes"
|
oss << "Reading " << chunkSize << " bytes"
|
||||||
@ -273,18 +356,18 @@ namespace ix
|
|||||||
log(oss.str(), args);
|
log(oss.str(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
payload.reserve(payload.size() + chunkSize);
|
payload.reserve(payload.size() + (size_t) chunkSize);
|
||||||
|
|
||||||
// Read a chunk
|
// Read a chunk
|
||||||
auto chunkResult = _socket->readBytes(chunkSize,
|
auto chunkResult = _socket->readBytes((size_t) chunkSize,
|
||||||
args.onProgressCallback,
|
args->onProgressCallback,
|
||||||
isCancellationRequested);
|
isCancellationRequested);
|
||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
payload += chunkResult.second;
|
payload += chunkResult.second;
|
||||||
|
|
||||||
@ -293,9 +376,9 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chunkSize == 0) break;
|
if (chunkSize == 0) break;
|
||||||
@ -308,9 +391,9 @@ namespace ix
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot read http body");
|
std::string errorMsg("Cannot read http body");
|
||||||
return std::make_tuple(code, HttpErrorCode_CannotReadBody,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadBody,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadSize = payload.size();
|
downloadSize = payload.size();
|
||||||
@ -322,44 +405,64 @@ namespace ix
|
|||||||
if (!gzipInflate(payload, decompressedPayload))
|
if (!gzipInflate(payload, decompressedPayload))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Error decompressing payload");
|
std::string errorMsg("Error decompressing payload");
|
||||||
return std::make_tuple(code, HttpErrorCode_Gzip,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::Gzip,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_tuple(code, HttpErrorCode_Ok,
|
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
||||||
headers, payload, std::string(),
|
headers, payload, std::string(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::get(const std::string& url,
|
HttpResponsePtr HttpClient::get(const std::string& url,
|
||||||
const HttpRequestArgs& args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kGet, std::string(), args);
|
return request(url, kGet, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::head(const std::string& url,
|
HttpResponsePtr HttpClient::head(const std::string& url,
|
||||||
const HttpRequestArgs& args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kHead, std::string(), args);
|
return request(url, kHead, std::string(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::post(const std::string& url,
|
HttpResponsePtr HttpClient::del(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
HttpRequestArgsPtr args)
|
||||||
const HttpRequestArgs& args)
|
{
|
||||||
|
return request(url, kDel, std::string(), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
||||||
|
const HttpParameters& httpParameters,
|
||||||
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse HttpClient::post(const std::string& url,
|
HttpResponsePtr HttpClient::post(const std::string& url,
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
const HttpRequestArgs& args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
return request(url, kPost, body, args);
|
return request(url, kPost, body, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||||
|
const HttpParameters& httpParameters,
|
||||||
|
HttpRequestArgsPtr args)
|
||||||
|
{
|
||||||
|
return request(url, kPut, serializeHttpParameters(httpParameters), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponsePtr HttpClient::put(const std::string& url,
|
||||||
|
const std::string& body,
|
||||||
|
const HttpRequestArgsPtr args)
|
||||||
|
{
|
||||||
|
return request(url, kPut, body, args);
|
||||||
|
}
|
||||||
|
|
||||||
std::string HttpClient::urlEncode(const std::string& value)
|
std::string HttpClient::urlEncode(const std::string& value)
|
||||||
{
|
{
|
||||||
std::ostringstream escaped;
|
std::ostringstream escaped;
|
||||||
@ -457,11 +560,11 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpClient::log(const std::string& msg,
|
void HttpClient::log(const std::string& msg,
|
||||||
const HttpRequestArgs& args)
|
HttpRequestArgsPtr args)
|
||||||
{
|
{
|
||||||
if (args.logger)
|
if (args->logger)
|
||||||
{
|
{
|
||||||
args.logger(msg);
|
args->logger(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,52 +6,78 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
#include <tuple>
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
enum HttpErrorCode
|
enum class HttpErrorCode : int
|
||||||
{
|
{
|
||||||
HttpErrorCode_Ok = 0,
|
Ok = 0,
|
||||||
HttpErrorCode_CannotConnect = 1,
|
CannotConnect = 1,
|
||||||
HttpErrorCode_Timeout = 2,
|
Timeout = 2,
|
||||||
HttpErrorCode_Gzip = 3,
|
Gzip = 3,
|
||||||
HttpErrorCode_UrlMalformed = 4,
|
UrlMalformed = 4,
|
||||||
HttpErrorCode_CannotCreateSocket = 5,
|
CannotCreateSocket = 5,
|
||||||
HttpErrorCode_SendError = 6,
|
SendError = 6,
|
||||||
HttpErrorCode_ReadError = 7,
|
ReadError = 7,
|
||||||
HttpErrorCode_CannotReadStatusLine = 8,
|
CannotReadStatusLine = 8,
|
||||||
HttpErrorCode_MissingStatus = 9,
|
MissingStatus = 9,
|
||||||
HttpErrorCode_HeaderParsingError = 10,
|
HeaderParsingError = 10,
|
||||||
HttpErrorCode_MissingLocation = 11,
|
MissingLocation = 11,
|
||||||
HttpErrorCode_TooManyRedirects = 12,
|
TooManyRedirects = 12,
|
||||||
HttpErrorCode_ChunkReadError = 13,
|
ChunkReadError = 13,
|
||||||
HttpErrorCode_CannotReadBody = 14
|
CannotReadBody = 14,
|
||||||
|
Invalid = 100
|
||||||
};
|
};
|
||||||
|
|
||||||
using HttpResponse = std::tuple<int, // status
|
struct HttpResponse
|
||||||
HttpErrorCode, // error code
|
{
|
||||||
WebSocketHttpHeaders,
|
int statusCode;
|
||||||
std::string, // payload
|
HttpErrorCode errorCode;
|
||||||
std::string, // error msg
|
WebSocketHttpHeaders headers;
|
||||||
uint64_t, // upload size
|
std::string payload;
|
||||||
uint64_t>; // download size
|
std::string errorMsg;
|
||||||
|
uint64_t uploadSize;
|
||||||
|
uint64_t downloadSize;
|
||||||
|
|
||||||
|
HttpResponse(int s = 0,
|
||||||
|
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||||
|
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||||
|
const std::string& p = std::string(),
|
||||||
|
const std::string& e = std::string(),
|
||||||
|
uint64_t u = 0,
|
||||||
|
uint64_t d = 0)
|
||||||
|
: statusCode(s)
|
||||||
|
, errorCode(c)
|
||||||
|
, headers(h)
|
||||||
|
, payload(p)
|
||||||
|
, errorMsg(e)
|
||||||
|
, uploadSize(u)
|
||||||
|
, downloadSize(d)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||||
using HttpParameters = std::map<std::string, std::string>;
|
using HttpParameters = std::map<std::string, std::string>;
|
||||||
using Logger = std::function<void(const std::string&)>;
|
using Logger = std::function<void(const std::string&)>;
|
||||||
|
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||||
|
|
||||||
struct HttpRequestArgs
|
struct HttpRequestArgs
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string url;
|
||||||
|
std::string verb;
|
||||||
WebSocketHttpHeaders extraHeaders;
|
WebSocketHttpHeaders extraHeaders;
|
||||||
std::string body;
|
std::string body;
|
||||||
int connectTimeout;
|
int connectTimeout;
|
||||||
@ -64,44 +90,72 @@ namespace ix
|
|||||||
OnProgressCallback onProgressCallback;
|
OnProgressCallback onProgressCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpClient {
|
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||||
|
|
||||||
|
class HttpClient
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
HttpClient();
|
HttpClient(bool async = false);
|
||||||
~HttpClient();
|
~HttpClient();
|
||||||
|
|
||||||
HttpResponse get(const std::string& url,
|
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
|
||||||
const HttpRequestArgs& args);
|
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
|
||||||
HttpResponse head(const std::string& url,
|
HttpResponsePtr del(const std::string& url, HttpRequestArgsPtr args);
|
||||||
const HttpRequestArgs& args);
|
|
||||||
|
|
||||||
HttpResponse post(const std::string& url,
|
HttpResponsePtr post(const std::string& url,
|
||||||
const HttpParameters& httpParameters,
|
const HttpParameters& httpParameters,
|
||||||
const HttpRequestArgs& args);
|
HttpRequestArgsPtr args);
|
||||||
HttpResponse post(const std::string& url,
|
HttpResponsePtr post(const std::string& url,
|
||||||
const std::string& body,
|
|
||||||
const HttpRequestArgs& args);
|
|
||||||
|
|
||||||
private:
|
|
||||||
HttpResponse request(const std::string& url,
|
|
||||||
const std::string& verb,
|
|
||||||
const std::string& body,
|
const std::string& body,
|
||||||
const HttpRequestArgs& args,
|
HttpRequestArgsPtr args);
|
||||||
int redirects = 0);
|
|
||||||
|
HttpResponsePtr put(const std::string& url,
|
||||||
|
const HttpParameters& httpParameters,
|
||||||
|
HttpRequestArgsPtr args);
|
||||||
|
HttpResponsePtr put(const std::string& url,
|
||||||
|
const std::string& body,
|
||||||
|
HttpRequestArgsPtr args);
|
||||||
|
|
||||||
|
HttpResponsePtr request(const std::string& url,
|
||||||
|
const std::string& verb,
|
||||||
|
const std::string& body,
|
||||||
|
HttpRequestArgsPtr args,
|
||||||
|
int redirects = 0);
|
||||||
|
|
||||||
|
// Async API
|
||||||
|
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
||||||
|
const std::string& verb = HttpClient::kGet);
|
||||||
|
|
||||||
|
bool performRequest(HttpRequestArgsPtr request,
|
||||||
|
const OnResponseCallback& onResponseCallback);
|
||||||
|
|
||||||
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
||||||
|
|
||||||
std::string urlEncode(const std::string& value);
|
std::string urlEncode(const std::string& value);
|
||||||
|
|
||||||
void log(const std::string& msg, const HttpRequestArgs& args);
|
|
||||||
|
|
||||||
bool gzipInflate(
|
|
||||||
const std::string& in,
|
|
||||||
std::string& out);
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> _socket;
|
|
||||||
|
|
||||||
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 kPut;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void log(const std::string& msg, HttpRequestArgsPtr args);
|
||||||
|
|
||||||
|
bool gzipInflate(const std::string& in, std::string& out);
|
||||||
|
|
||||||
|
// Async API background thread runner
|
||||||
|
void run();
|
||||||
|
|
||||||
|
// Async API
|
||||||
|
bool _async;
|
||||||
|
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
||||||
|
mutable std::mutex _queueMutex;
|
||||||
|
std::condition_variable _condition;
|
||||||
|
std::atomic<bool> _stop;
|
||||||
|
std::thread _thread;
|
||||||
|
|
||||||
|
std::shared_ptr<Socket> _socket;
|
||||||
|
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
39
ixwebsocket/IXNetSystem.cpp
Normal file
39
ixwebsocket/IXNetSystem.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* IXNetSystem.cpp
|
||||||
|
* Author: Korchynskyi Dmytro
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXNetSystem.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
bool initNetSystem()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
WORD wVersionRequested;
|
||||||
|
WSADATA wsaData;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
|
||||||
|
wVersionRequested = MAKEWORD(2, 2);
|
||||||
|
|
||||||
|
err = WSAStartup(wVersionRequested, &wsaData);
|
||||||
|
|
||||||
|
return err == 0;
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uninitNetSystem()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
int err = WSACleanup();
|
||||||
|
|
||||||
|
return err == 0;
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
@ -7,19 +7,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# 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>
|
||||||
#else
|
#else
|
||||||
# include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
# include <errno.h>
|
#include <errno.h>
|
||||||
# include <netdb.h>
|
#include <netdb.h>
|
||||||
# include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
# include <sys/select.h>
|
#include <sys/select.h>
|
||||||
# include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
# include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
# include <sys/time.h>
|
#include <sys/time.h>
|
||||||
# include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
bool initNetSystem();
|
||||||
|
bool uninitNetSystem();
|
||||||
|
} // namespace ix
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SelectInterrupt {
|
class SelectInterrupt
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
SelectInterrupt();
|
SelectInterrupt();
|
||||||
virtual ~SelectInterrupt();
|
virtual ~SelectInterrupt();
|
||||||
@ -23,6 +24,4 @@ namespace ix
|
|||||||
virtual uint64_t read();
|
virtual uint64_t read();
|
||||||
virtual int getFd() const;
|
virtual int getFd() const;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
#include "IXSelectInterrupt.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SelectInterruptEventFd : public SelectInterrupt {
|
class SelectInterruptEventFd final : public SelectInterrupt
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
SelectInterruptEventFd();
|
SelectInterruptEventFd();
|
||||||
virtual ~SelectInterruptEventFd();
|
virtual ~SelectInterruptEventFd();
|
||||||
@ -28,5 +28,4 @@ namespace ix
|
|||||||
private:
|
private:
|
||||||
int _eventfd;
|
int _eventfd;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
@ -12,4 +12,4 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class SelectInterrupt;
|
class SelectInterrupt;
|
||||||
std::shared_ptr<SelectInterrupt> createSelectInterrupt();
|
std::shared_ptr<SelectInterrupt> createSelectInterrupt();
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -40,6 +40,8 @@ namespace ix
|
|||||||
|
|
||||||
bool SelectInterruptPipe::init(std::string& errorMsg)
|
bool SelectInterruptPipe::init(std::string& errorMsg)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||||
|
|
||||||
// calling init twice is a programming error
|
// calling init twice is a programming error
|
||||||
assert(_fildes[kPipeReadIndex] == -1);
|
assert(_fildes[kPipeReadIndex] == -1);
|
||||||
assert(_fildes[kPipeWriteIndex] == -1);
|
assert(_fildes[kPipeWriteIndex] == -1);
|
||||||
@ -108,6 +110,8 @@ namespace ix
|
|||||||
|
|
||||||
bool SelectInterruptPipe::notify(uint64_t value)
|
bool SelectInterruptPipe::notify(uint64_t value)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||||
|
|
||||||
int fd = _fildes[kPipeWriteIndex];
|
int fd = _fildes[kPipeWriteIndex];
|
||||||
if (fd == -1) return false;
|
if (fd == -1) return false;
|
||||||
|
|
||||||
@ -118,6 +122,8 @@ namespace ix
|
|||||||
// TODO: return max uint64_t for errors ?
|
// TODO: return max uint64_t for errors ?
|
||||||
uint64_t SelectInterruptPipe::read()
|
uint64_t SelectInterruptPipe::read()
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||||
|
|
||||||
int fd = _fildes[kPipeReadIndex];
|
int fd = _fildes[kPipeReadIndex];
|
||||||
|
|
||||||
uint64_t value = 0;
|
uint64_t value = 0;
|
||||||
@ -133,6 +139,8 @@ namespace ix
|
|||||||
|
|
||||||
int SelectInterruptPipe::getFd() const
|
int SelectInterruptPipe::getFd() const
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_fildesMutex);
|
||||||
|
|
||||||
return _fildes[kPipeReadIndex];
|
return _fildes[kPipeReadIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
#include "IXSelectInterrupt.h"
|
||||||
|
#include <mutex>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SelectInterruptPipe : public SelectInterrupt {
|
class SelectInterruptPipe final : public SelectInterrupt
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
SelectInterruptPipe();
|
SelectInterruptPipe();
|
||||||
virtual ~SelectInterruptPipe();
|
virtual ~SelectInterruptPipe();
|
||||||
@ -30,10 +31,10 @@ namespace ix
|
|||||||
// happens between a control thread and a background thread, which is
|
// happens between a control thread and a background thread, which is
|
||||||
// blocked on select.
|
// blocked on select.
|
||||||
int _fildes[2];
|
int _fildes[2];
|
||||||
|
mutable std::mutex _fildesMutex;
|
||||||
|
|
||||||
// Used to identify the read/write idx
|
// Used to identify the read/write idx
|
||||||
static const int kPipeReadIndex;
|
static const int kPipeReadIndex;
|
||||||
static const int kPipeWriteIndex;
|
static const int kPipeWriteIndex;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
@ -10,4 +10,3 @@ namespace ix
|
|||||||
{
|
{
|
||||||
void setThreadName(const std::string& name);
|
void setThreadName(const std::string& name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#ifdef min
|
#ifdef min
|
||||||
#undef min
|
#undef min
|
||||||
@ -45,14 +44,9 @@ namespace ix
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::poll(int timeoutSecs)
|
PollResultType Socket::poll(int timeoutMs)
|
||||||
{
|
{
|
||||||
if (_sockfd == -1)
|
return isReadyToRead(timeoutMs);
|
||||||
{
|
|
||||||
return PollResultType::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isReadyToRead(1000 * timeoutSecs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::select(bool readyToRead, int timeoutMs)
|
PollResultType Socket::select(bool readyToRead, int timeoutMs)
|
||||||
@ -63,7 +57,10 @@ namespace ix
|
|||||||
FD_ZERO(&wfds);
|
FD_ZERO(&wfds);
|
||||||
|
|
||||||
fd_set* fds = (readyToRead) ? &rfds : & wfds;
|
fd_set* fds = (readyToRead) ? &rfds : & wfds;
|
||||||
FD_SET(_sockfd, fds);
|
if (_sockfd != -1)
|
||||||
|
{
|
||||||
|
FD_SET(_sockfd, fds);
|
||||||
|
}
|
||||||
|
|
||||||
// File descriptor used to interrupt select when needed
|
// File descriptor used to interrupt select when needed
|
||||||
int interruptFd = _selectInterrupt->getFd();
|
int interruptFd = _selectInterrupt->getFd();
|
||||||
@ -74,7 +71,7 @@ namespace ix
|
|||||||
|
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = timeoutMs / 1000;
|
timeout.tv_sec = timeoutMs / 1000;
|
||||||
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
|
timeout.tv_usec = 1000 * (timeoutMs % 1000);
|
||||||
|
|
||||||
// Compute the highest fd.
|
// Compute the highest fd.
|
||||||
int sockfd = _sockfd;
|
int sockfd = _sockfd;
|
||||||
@ -119,18 +116,28 @@ namespace ix
|
|||||||
|
|
||||||
PollResultType Socket::isReadyToRead(int timeoutMs)
|
PollResultType Socket::isReadyToRead(int timeoutMs)
|
||||||
{
|
{
|
||||||
|
if (_sockfd == -1)
|
||||||
|
{
|
||||||
|
return PollResultType::Error;
|
||||||
|
}
|
||||||
|
|
||||||
bool readyToRead = true;
|
bool readyToRead = true;
|
||||||
return select(readyToRead, timeoutMs);
|
return select(readyToRead, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
||||||
{
|
{
|
||||||
|
if (_sockfd == -1)
|
||||||
|
{
|
||||||
|
return PollResultType::Error;
|
||||||
|
}
|
||||||
|
|
||||||
bool readyToRead = false;
|
bool readyToRead = false;
|
||||||
return select(readyToRead, timeoutMs);
|
return select(readyToRead, timeoutMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wake up from poll/select by writing to the pipe which is watched by select
|
// Wake up from poll/select by writing to the pipe which is watched by select
|
||||||
bool Socket::wakeUpFromPoll(uint8_t wakeUpCode)
|
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
|
||||||
{
|
{
|
||||||
return _selectInterrupt->notify(wakeUpCode);
|
return _selectInterrupt->notify(wakeUpCode);
|
||||||
}
|
}
|
||||||
@ -160,8 +167,6 @@ namespace ix
|
|||||||
|
|
||||||
ssize_t Socket::send(char* buffer, size_t length)
|
ssize_t Socket::send(char* buffer, size_t length)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
|
||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
#ifdef MSG_NOSIGNAL
|
#ifdef MSG_NOSIGNAL
|
||||||
flags = MSG_NOSIGNAL;
|
flags = MSG_NOSIGNAL;
|
||||||
@ -177,8 +182,6 @@ namespace ix
|
|||||||
|
|
||||||
ssize_t Socket::recv(void* buffer, size_t length)
|
ssize_t Socket::recv(void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
|
||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
#ifdef MSG_NOSIGNAL
|
#ifdef MSG_NOSIGNAL
|
||||||
flags = MSG_NOSIGNAL;
|
flags = MSG_NOSIGNAL;
|
||||||
@ -189,11 +192,27 @@ namespace ix
|
|||||||
|
|
||||||
int Socket::getErrno()
|
int Socket::getErrno()
|
||||||
{
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return WSAGetLastError();
|
err = WSAGetLastError();
|
||||||
#else
|
#else
|
||||||
return errno;
|
err = errno;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::isWaitNeeded()
|
||||||
|
{
|
||||||
|
int err = getErrno();
|
||||||
|
|
||||||
|
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Socket::closeSocket(int fd)
|
void Socket::closeSocket(int fd)
|
||||||
@ -228,8 +247,7 @@ namespace ix
|
|||||||
return ret == len;
|
return ret == len;
|
||||||
}
|
}
|
||||||
// There is possibly something to be writen, try again
|
// There is possibly something to be writen, try again
|
||||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
else if (ret < 0 && Socket::isWaitNeeded())
|
||||||
getErrno() == EAGAIN))
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -257,8 +275,7 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// There is possibly something to be read, try again
|
// There is possibly something to be read, try again
|
||||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
else if (ret < 0 && Socket::isWaitNeeded())
|
||||||
getErrno() == EAGAIN))
|
|
||||||
{
|
{
|
||||||
// Wait with a 1ms timeout until the socket is ready to read.
|
// Wait with a 1ms timeout until the socket is ready to read.
|
||||||
// This way we are not busy looping
|
// This way we are not busy looping
|
||||||
@ -317,13 +334,12 @@ namespace ix
|
|||||||
size_t size = std::min(kChunkSize, length - output.size());
|
size_t size = std::min(kChunkSize, length - output.size());
|
||||||
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
||||||
|
|
||||||
if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
|
if (ret <= 0 && !Socket::isWaitNeeded())
|
||||||
getErrno() != EAGAIN))
|
|
||||||
{
|
{
|
||||||
// Error
|
// Error
|
||||||
return std::make_pair(false, std::string());
|
return std::make_pair(false, std::string());
|
||||||
}
|
}
|
||||||
else if (ret > 0)
|
else
|
||||||
{
|
{
|
||||||
output.insert(output.end(),
|
output.insert(output.end(),
|
||||||
_readBuffer.begin(),
|
_readBuffer.begin(),
|
||||||
|
@ -6,16 +6,30 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <vector>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#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"
|
||||||
@ -35,17 +49,16 @@ namespace ix
|
|||||||
CloseRequest = 5
|
CloseRequest = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
class Socket {
|
class Socket
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Socket(int fd = -1);
|
Socket(int fd = -1);
|
||||||
virtual ~Socket();
|
virtual ~Socket();
|
||||||
bool init(std::string& errorMsg);
|
bool init(std::string& errorMsg);
|
||||||
|
|
||||||
void configure();
|
|
||||||
|
|
||||||
// Functions to check whether there is activity on the socket
|
// Functions to check whether there is activity on the socket
|
||||||
PollResultType poll(int timeoutSecs = kDefaultPollTimeout);
|
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
|
||||||
bool wakeUpFromPoll(uint8_t wakeUpCode);
|
bool wakeUpFromPoll(uint64_t wakeUpCode);
|
||||||
|
|
||||||
PollResultType isReadyToWrite(int timeoutMs);
|
PollResultType isReadyToWrite(int timeoutMs);
|
||||||
PollResultType isReadyToRead(int timeoutMs);
|
PollResultType isReadyToRead(int timeoutMs);
|
||||||
@ -63,27 +76,23 @@ namespace ix
|
|||||||
|
|
||||||
// Blocking and cancellable versions, working with socket that can be set
|
// Blocking and cancellable versions, working with socket that can be set
|
||||||
// to non blocking mode. Used during HTTP upgrade.
|
// to non blocking mode. Used during HTTP upgrade.
|
||||||
bool readByte(void* buffer,
|
bool readByte(void* buffer, const CancellationRequest& isCancellationRequested);
|
||||||
const CancellationRequest& isCancellationRequested);
|
bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested);
|
||||||
bool writeBytes(const std::string& str,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
std::pair<bool, std::string> readLine(
|
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
|
||||||
const CancellationRequest& isCancellationRequested);
|
std::pair<bool, std::string> readBytes(size_t length,
|
||||||
std::pair<bool, std::string> readBytes(
|
const OnProgressCallback& onProgressCallback,
|
||||||
size_t length,
|
const CancellationRequest& isCancellationRequested);
|
||||||
const OnProgressCallback& onProgressCallback,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
static int getErrno();
|
static int getErrno();
|
||||||
|
static bool isWaitNeeded();
|
||||||
|
static void closeSocket(int fd);
|
||||||
|
|
||||||
// Used as special codes for pipe communication
|
// Used as special codes for pipe communication
|
||||||
static const uint64_t kSendRequest;
|
static const uint64_t kSendRequest;
|
||||||
static const uint64_t kCloseRequest;
|
static const uint64_t kCloseRequest;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeSocket(int fd);
|
|
||||||
|
|
||||||
std::atomic<int> _sockfd;
|
std::atomic<int> _sockfd;
|
||||||
std::mutex _socketMutex;
|
std::mutex _socketMutex;
|
||||||
|
|
||||||
@ -99,4 +108,4 @@ namespace ix
|
|||||||
|
|
||||||
std::shared_ptr<SelectInterrupt> _selectInterrupt;
|
std::shared_ptr<SelectInterrupt> _selectInterrupt;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -20,8 +20,6 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#define socketerrno errno
|
#define socketerrno errno
|
||||||
|
|
||||||
|
@ -6,17 +6,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
#include <Security/Security.h>
|
|
||||||
#include <Security/SecureTransport.h>
|
#include <Security/SecureTransport.h>
|
||||||
|
#include <Security/Security.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketAppleSSL : public Socket
|
class SocketAppleSSL final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketAppleSSL(int fd = -1);
|
SocketAppleSSL(int fd = -1);
|
||||||
@ -34,7 +32,7 @@ namespace ix
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
SSLContextRef _sslContext;
|
SSLContextRef _sslContext;
|
||||||
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
|
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXDNSLookup.h"
|
#include "IXDNSLookup.h"
|
||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -18,18 +19,6 @@
|
|||||||
# include <linux/tcp.h>
|
# include <linux/tcp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void closeSocket(int fd)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
closesocket(fd);
|
|
||||||
#else
|
|
||||||
::close(fd);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@ -56,11 +45,12 @@ namespace ix
|
|||||||
// block us for too long
|
// block us for too long
|
||||||
SocketConnect::configure(fd);
|
SocketConnect::configure(fd);
|
||||||
|
|
||||||
if (::connect(fd, address->ai_addr, address->ai_addrlen) == -1
|
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
|
||||||
&& errno != EINPROGRESS)
|
|
||||||
|
if (res == -1 && !Socket::isWaitNeeded())
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
errMsg = strerror(Socket::getErrno());
|
||||||
errMsg = strerror(errno);
|
Socket::closeSocket(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,15 +58,17 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
Socket::closeSocket(fd);
|
||||||
errMsg = "Cancelled";
|
errMsg = "Cancelled";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use select to check the status of the new connection
|
// On Linux the timeout needs to be re-initialized everytime
|
||||||
|
// http://man7.org/linux/man-pages/man2/select.2.html
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = 0;
|
timeout.tv_sec = 0;
|
||||||
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
||||||
|
|
||||||
fd_set wfds;
|
fd_set wfds;
|
||||||
fd_set efds;
|
fd_set efds;
|
||||||
|
|
||||||
@ -85,11 +77,13 @@ namespace ix
|
|||||||
FD_ZERO(&efds);
|
FD_ZERO(&efds);
|
||||||
FD_SET(fd, &efds);
|
FD_SET(fd, &efds);
|
||||||
|
|
||||||
if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
|
// Use select to check the status of the new connection
|
||||||
(errno == EBADF || errno == EINVAL))
|
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
|
||||||
|
|
||||||
|
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
Socket::closeSocket(fd);
|
||||||
errMsg = std::string("Connect error, select error: ") + strerror(errno);
|
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +104,7 @@ namespace ix
|
|||||||
optval != 0)
|
optval != 0)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
Socket::closeSocket(fd);
|
||||||
errMsg = strerror(optval);
|
errMsg = strerror(optval);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -121,7 +115,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSocket(fd);
|
Socket::closeSocket(fd);
|
||||||
errMsg = "connect timed out after 60 seconds";
|
errMsg = "connect timed out after 60 seconds";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct addrinfo;
|
struct addrinfo;
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketConnect {
|
class SocketConnect
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
static int connect(const std::string& hostname,
|
static int connect(const std::string& hostname,
|
||||||
int port,
|
int port,
|
||||||
@ -24,9 +24,8 @@ namespace ix
|
|||||||
static void configure(int sockfd);
|
static void configure(int sockfd);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int connectToAddress(const struct addrinfo *address,
|
static int connectToAddress(const struct addrinfo* address,
|
||||||
std::string& errMsg,
|
std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
@ -8,11 +8,13 @@
|
|||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
#ifdef IXWEBSOCKET_USE_TLS
|
||||||
|
|
||||||
# ifdef __APPLE__
|
# ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||||
|
# include <ixwebsocket/IXSocketMbedTLS.h>
|
||||||
|
# elif __APPLE__
|
||||||
# include <ixwebsocket/IXSocketAppleSSL.h>
|
# include <ixwebsocket/IXSocketAppleSSL.h>
|
||||||
# elif defined(_WIN32)
|
# elif defined(_WIN32)
|
||||||
# include <ixwebsocket/IXSocketSChannel.h>
|
# include <ixwebsocket/IXSocketSChannel.h>
|
||||||
# else
|
# elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||||
# include <ixwebsocket/IXSocketOpenSSL.h>
|
# include <ixwebsocket/IXSocketOpenSSL.h>
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
@ -37,7 +39,9 @@ namespace ix
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
#ifdef IXWEBSOCKET_USE_TLS
|
||||||
# ifdef __APPLE__
|
# if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||||
|
socket = std::make_shared<SocketMbedTLS>();
|
||||||
|
# elif defined(__APPLE__)
|
||||||
socket = std::make_shared<SocketAppleSSL>();
|
socket = std::make_shared<SocketAppleSSL>();
|
||||||
# elif defined(_WIN32)
|
# elif defined(_WIN32)
|
||||||
socket = std::make_shared<SocketSChannel>();
|
socket = std::make_shared<SocketSChannel>();
|
||||||
|
@ -13,9 +13,7 @@
|
|||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class Socket;
|
class Socket;
|
||||||
std::shared_ptr<Socket> createSocket(bool tls,
|
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg);
|
||||||
std::string& errorMsg);
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> createSocket(int fd,
|
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
|
||||||
std::string& errorMsg);
|
} // namespace ix
|
||||||
}
|
|
||||||
|
161
ixwebsocket/IXSocketMbedTLS.cpp
Normal file
161
ixwebsocket/IXSocketMbedTLS.cpp
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketMbedTLS.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Some code taken from
|
||||||
|
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
||||||
|
* and mini_client.c example from mbedtls
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXSocketMbedTLS.h"
|
||||||
|
#include "IXSocketConnect.h"
|
||||||
|
#include "IXNetSystem.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
SocketMbedTLS::~SocketMbedTLS()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
|
||||||
|
{
|
||||||
|
mbedtls_ssl_init(&_ssl);
|
||||||
|
mbedtls_ssl_config_init(&_conf);
|
||||||
|
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
||||||
|
|
||||||
|
const char *pers = "IXSocketMbedTLS";
|
||||||
|
|
||||||
|
mbedtls_entropy_init(&_entropy);
|
||||||
|
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
|
||||||
|
mbedtls_entropy_func,
|
||||||
|
&_entropy,
|
||||||
|
(const unsigned char *) pers,
|
||||||
|
strlen(pers)) != 0)
|
||||||
|
{
|
||||||
|
errMsg = "Setting entropy seed failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mbedtls_ssl_config_defaults(&_conf,
|
||||||
|
MBEDTLS_SSL_IS_CLIENT,
|
||||||
|
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||||
|
MBEDTLS_SSL_PRESET_DEFAULT ) != 0)
|
||||||
|
{
|
||||||
|
errMsg = "Setting config default failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
|
||||||
|
|
||||||
|
// FIXME: cert verification is disabled
|
||||||
|
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||||
|
|
||||||
|
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
|
||||||
|
{
|
||||||
|
errMsg = "SSL setup failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||||
|
{
|
||||||
|
errMsg = "SNI setup failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketMbedTLS::connect(const std::string& host,
|
||||||
|
int port,
|
||||||
|
std::string& errMsg,
|
||||||
|
const CancellationRequest& isCancellationRequested)
|
||||||
|
{
|
||||||
|
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||||
|
if (_sockfd == -1) return false;
|
||||||
|
if (!init(host, errMsg)) return false;
|
||||||
|
|
||||||
|
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||||
|
|
||||||
|
int res;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
res = mbedtls_ssl_handshake(&_ssl);
|
||||||
|
}
|
||||||
|
while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||||
|
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
char buf[256];
|
||||||
|
mbedtls_strerror(res, buf, sizeof(buf));
|
||||||
|
|
||||||
|
errMsg = "error in handshake : ";
|
||||||
|
errMsg += buf;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketMbedTLS::close()
|
||||||
|
{
|
||||||
|
mbedtls_ssl_free(&_ssl);
|
||||||
|
mbedtls_ssl_config_free(&_conf);
|
||||||
|
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
||||||
|
mbedtls_entropy_free(&_entropy);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
|
||||||
|
{
|
||||||
|
ssize_t sent = 0;
|
||||||
|
|
||||||
|
while (nbyte > 0)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
|
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
||||||
|
|
||||||
|
if (res > 0) {
|
||||||
|
nbyte -= res;
|
||||||
|
sent += res;
|
||||||
|
} else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||||
|
errno = EWOULDBLOCK;
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t SocketMbedTLS::send(const std::string& buffer)
|
||||||
|
{
|
||||||
|
return send((char*)&buffer[0], buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
|
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
|
||||||
|
|
||||||
|
if (res > 0)
|
||||||
|
{
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||||
|
{
|
||||||
|
errno = EWOULDBLOCK;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
47
ixwebsocket/IXSocketMbedTLS.h
Normal file
47
ixwebsocket/IXSocketMbedTLS.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketMbedTLS.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXSocket.h"
|
||||||
|
#include <mbedtls/ctr_drbg.h>
|
||||||
|
#include <mbedtls/debug.h>
|
||||||
|
#include <mbedtls/entropy.h>
|
||||||
|
#include <mbedtls/error.h>
|
||||||
|
#include <mbedtls/net.h>
|
||||||
|
#include <mbedtls/platform.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class SocketMbedTLS final : public Socket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SocketMbedTLS() = default;
|
||||||
|
~SocketMbedTLS();
|
||||||
|
|
||||||
|
virtual bool connect(const std::string& host,
|
||||||
|
int port,
|
||||||
|
std::string& errMsg,
|
||||||
|
const CancellationRequest& isCancellationRequested) final;
|
||||||
|
virtual void close() final;
|
||||||
|
|
||||||
|
virtual ssize_t send(char* buffer, size_t length) final;
|
||||||
|
virtual ssize_t send(const std::string& buffer) final;
|
||||||
|
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
mbedtls_ssl_context _ssl;
|
||||||
|
mbedtls_ssl_config _conf;
|
||||||
|
mbedtls_entropy_context _entropy;
|
||||||
|
mbedtls_ctr_drbg_context _ctr_drbg;
|
||||||
|
|
||||||
|
std::mutex _mutex;
|
||||||
|
|
||||||
|
bool init(const std::string& host, std::string& errMsg);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ix
|
@ -10,7 +10,6 @@
|
|||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
|
||||||
@ -241,7 +240,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No wait support
|
|
||||||
bool SocketOpenSSL::connect(const std::string& host,
|
bool SocketOpenSSL::connect(const std::string& host,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg,
|
std::string& errMsg,
|
||||||
@ -362,7 +360,6 @@ namespace ix
|
|||||||
return send((char*)&buffer[0], buffer.size());
|
return send((char*)&buffer[0], buffer.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// No wait support
|
|
||||||
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
|
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -6,20 +6,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
|
#include <mutex>
|
||||||
#include <openssl/bio.h>
|
#include <openssl/bio.h>
|
||||||
#include <openssl/hmac.h>
|
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/hmac.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketOpenSSL : public Socket
|
class SocketOpenSSL final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketOpenSSL(int fd = -1);
|
SocketOpenSSL(int fd = -1);
|
||||||
@ -40,18 +38,16 @@ namespace ix
|
|||||||
std::string getSSLError(int ret);
|
std::string getSSLError(int ret);
|
||||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||||
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
|
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
|
||||||
bool openSSLCheckServerCert(SSL *ssl,
|
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
|
||||||
const std::string& hostname,
|
bool checkHost(const std::string& host, const char* pattern);
|
||||||
std::string& errMsg);
|
|
||||||
bool checkHost(const std::string& host, const char *pattern);
|
|
||||||
|
|
||||||
SSL* _ssl_connection;
|
SSL* _ssl_connection;
|
||||||
SSL_CTX* _ssl_context;
|
SSL_CTX* _ssl_context;
|
||||||
const SSL_METHOD* _ssl_method;
|
const SSL_METHOD* _ssl_method;
|
||||||
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
||||||
|
|
||||||
static std::once_flag _openSSLInitFlag;
|
static std::once_flag _openSSLInitFlag;
|
||||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
# include <ws2def.h>
|
# include <ws2def.h>
|
||||||
# include <WS2tcpip.h>
|
# include <WS2tcpip.h>
|
||||||
# include <schannel.h>
|
# include <schannel.h>
|
||||||
//# include <sslsock.h>
|
|
||||||
# include <io.h>
|
# include <io.h>
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
@ -10,15 +10,13 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketSChannel : public Socket
|
class SocketSChannel final : public Socket
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SocketSChannel();
|
SocketSChannel();
|
||||||
~SocketSChannel();
|
~SocketSChannel();
|
||||||
|
|
||||||
virtual bool connect(const std::string& host,
|
virtual bool connect(const std::string& host, int port, std::string& errMsg) final;
|
||||||
int port,
|
|
||||||
std::string& errMsg) final;
|
|
||||||
virtual void close() final;
|
virtual void close() final;
|
||||||
|
|
||||||
// The important override
|
// The important override
|
||||||
@ -31,4 +29,4 @@ namespace ix
|
|||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -30,7 +30,9 @@ namespace ix
|
|||||||
_host(host),
|
_host(host),
|
||||||
_backlog(backlog),
|
_backlog(backlog),
|
||||||
_maxConnections(maxConnections),
|
_maxConnections(maxConnections),
|
||||||
|
_serverFd(-1),
|
||||||
_stop(false),
|
_stop(false),
|
||||||
|
_stopGc(false),
|
||||||
_connectionStateFactory(&ConnectionState::createConnectionState)
|
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -77,7 +79,7 @@ namespace ix
|
|||||||
<< "at address " << _host << ":" << _port
|
<< "at address " << _host << ":" << _port
|
||||||
<< " : " << strerror(Socket::getErrno());
|
<< " : " << strerror(Socket::getErrno());
|
||||||
|
|
||||||
::close(_serverFd);
|
Socket::closeSocket(_serverFd);
|
||||||
return std::make_pair(false, ss.str());
|
return std::make_pair(false, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ namespace ix
|
|||||||
<< "at address " << _host << ":" << _port
|
<< "at address " << _host << ":" << _port
|
||||||
<< " : " << strerror(Socket::getErrno());
|
<< " : " << strerror(Socket::getErrno());
|
||||||
|
|
||||||
::close(_serverFd);
|
Socket::closeSocket(_serverFd);
|
||||||
return std::make_pair(false, ss.str());
|
return std::make_pair(false, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +117,7 @@ namespace ix
|
|||||||
<< "at address " << _host << ":" << _port
|
<< "at address " << _host << ":" << _port
|
||||||
<< " : " << strerror(Socket::getErrno());
|
<< " : " << strerror(Socket::getErrno());
|
||||||
|
|
||||||
::close(_serverFd);
|
Socket::closeSocket(_serverFd);
|
||||||
return std::make_pair(false, ss.str());
|
return std::make_pair(false, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,9 +126,15 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::start()
|
void SocketServer::start()
|
||||||
{
|
{
|
||||||
if (_thread.joinable()) return; // we've already been started
|
if (!_thread.joinable())
|
||||||
|
{
|
||||||
|
_thread = std::thread(&SocketServer::run, this);
|
||||||
|
}
|
||||||
|
|
||||||
_thread = std::thread(&SocketServer::run, this);
|
if (!_gcThread.joinable())
|
||||||
|
{
|
||||||
|
_gcThread = std::thread(&SocketServer::runGC, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketServer::wait()
|
void SocketServer::wait()
|
||||||
@ -142,24 +150,24 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::stop()
|
void SocketServer::stop()
|
||||||
{
|
{
|
||||||
while (true)
|
// Stop accepting connections, and close the 'accept' thread
|
||||||
|
if (_thread.joinable())
|
||||||
{
|
{
|
||||||
if (closeTerminatedThreads()) break;
|
_stop = true;
|
||||||
|
_thread.join();
|
||||||
// wait 10ms and try again later.
|
_stop = false;
|
||||||
// we could have a timeout, but if we exit of here
|
|
||||||
// we leaked threads, it is quite bad.
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_thread.joinable()) return; // nothing to do
|
// Join all threads and make sure that all connections are terminated
|
||||||
|
if (_gcThread.joinable())
|
||||||
_stop = true;
|
{
|
||||||
_thread.join();
|
_stopGc = true;
|
||||||
_stop = false;
|
_gcThread.join();
|
||||||
|
_stopGc = false;
|
||||||
|
}
|
||||||
|
|
||||||
_conditionVariable.notify_one();
|
_conditionVariable.notify_one();
|
||||||
::close(_serverFd);
|
Socket::closeSocket(_serverFd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketServer::setConnectionStateFactory(
|
void SocketServer::setConnectionStateFactory(
|
||||||
@ -175,7 +183,7 @@ namespace ix
|
|||||||
// field becomes true, and we can use that to know that we can join that thread
|
// field becomes true, and we can use that to know that we can join that thread
|
||||||
// and remove it from our _connectionsThreads data structure (a list).
|
// and remove it from our _connectionsThreads data structure (a list).
|
||||||
//
|
//
|
||||||
bool SocketServer::closeTerminatedThreads()
|
void SocketServer::closeTerminatedThreads()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
auto it = _connectionsThreads.begin();
|
auto it = _connectionsThreads.begin();
|
||||||
@ -195,8 +203,6 @@ namespace ix
|
|||||||
if (thread.joinable()) thread.join();
|
if (thread.joinable()) thread.join();
|
||||||
it = _connectionsThreads.erase(it);
|
it = _connectionsThreads.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _connectionsThreads.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SocketServer::run()
|
void SocketServer::run()
|
||||||
@ -208,12 +214,6 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (_stop) return;
|
if (_stop) return;
|
||||||
|
|
||||||
// Garbage collection to shutdown/join threads for closed connections.
|
|
||||||
// We could run this in its own thread, so that we dont need to accept
|
|
||||||
// a new connection to close a thread.
|
|
||||||
// We could also use a condition variable to be notify when we need to do this
|
|
||||||
closeTerminatedThreads();
|
|
||||||
|
|
||||||
// Use select to check whether a new connection is in progress
|
// Use select to check whether a new connection is in progress
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
@ -242,17 +242,18 @@ namespace ix
|
|||||||
// Accept a connection.
|
// Accept a connection.
|
||||||
struct sockaddr_in client; // client address information
|
struct sockaddr_in client; // client address information
|
||||||
int clientFd; // socket connected to client
|
int clientFd; // socket connected to client
|
||||||
socklen_t addressLen = sizeof(socklen_t);
|
socklen_t addressLen = sizeof(client);
|
||||||
memset(&client, 0, sizeof(client));
|
memset(&client, 0, sizeof(client));
|
||||||
|
|
||||||
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
|
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
|
||||||
{
|
{
|
||||||
if (Socket::getErrno() != EWOULDBLOCK)
|
if (!Socket::isWaitNeeded())
|
||||||
{
|
{
|
||||||
// FIXME: that error should be propagated
|
// FIXME: that error should be propagated
|
||||||
|
int err = Socket::getErrno();
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "SocketServer::run() error accepting connection: "
|
ss << "SocketServer::run() error accepting connection: "
|
||||||
<< strerror(Socket::getErrno());
|
<< err << ", " << strerror(err);
|
||||||
logError(ss.str());
|
logError(ss.str());
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@ -266,7 +267,7 @@ namespace ix
|
|||||||
<< "Not accepting connection";
|
<< "Not accepting connection";
|
||||||
logError(ss.str());
|
logError(ss.str());
|
||||||
|
|
||||||
::close(clientFd);
|
Socket::closeSocket(clientFd);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -280,7 +281,7 @@ namespace ix
|
|||||||
if (_stop) return;
|
if (_stop) return;
|
||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
// Launch the handleConnection work asynchronously in its own thread.
|
||||||
std::lock_guard<std::mutex> lock(_conditionVariableMutex);
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
_connectionsThreads.push_back(std::make_pair(
|
_connectionsThreads.push_back(std::make_pair(
|
||||||
connectionState,
|
connectionState,
|
||||||
std::thread(&SocketServer::handleConnection,
|
std::thread(&SocketServer::handleConnection,
|
||||||
@ -289,5 +290,30 @@ namespace ix
|
|||||||
connectionState)));
|
connectionState)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t SocketServer::getConnectionsThreadsCount()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||||
|
return _connectionsThreads.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketServer::runGC()
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// Garbage collection to shutdown/join threads for closed connections.
|
||||||
|
closeTerminatedThreads();
|
||||||
|
|
||||||
|
// We quit this thread if all connections are closed and we received
|
||||||
|
// a stop request by setting _stopGc to true.
|
||||||
|
if (_stopGc && getConnectionsThreadsCount() == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep a little bit then keep cleaning up
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,28 +7,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXConnectionState.h"
|
#include "IXConnectionState.h"
|
||||||
|
|
||||||
#include <utility> // pair
|
|
||||||
#include <string>
|
|
||||||
#include <set>
|
|
||||||
#include <thread>
|
|
||||||
#include <list>
|
|
||||||
#include <mutex>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility> // pair
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class SocketServer {
|
class SocketServer
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
|
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
|
||||||
|
|
||||||
// Each connection is handled by its own worker thread.
|
// Each connection is handled by its own worker thread.
|
||||||
// We use a list as we only care about remove and append operations.
|
// We use a list as we only care about remove and append operations.
|
||||||
using ConnectionThreads = std::list<std::pair<std::shared_ptr<ConnectionState>,
|
using ConnectionThreads =
|
||||||
std::thread>>;
|
std::list<std::pair<std::shared_ptr<ConnectionState>, std::thread>>;
|
||||||
|
|
||||||
SocketServer(int port = SocketServer::kDefaultPort,
|
SocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
@ -52,7 +52,6 @@ namespace ix
|
|||||||
void wait();
|
void wait();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
void logError(const std::string& str);
|
void logError(const std::string& str);
|
||||||
void logInfo(const std::string& str);
|
void logInfo(const std::string& str);
|
||||||
@ -74,6 +73,12 @@ namespace ix
|
|||||||
// background thread to wait for incoming connections
|
// background thread to wait for incoming connections
|
||||||
std::atomic<bool> _stop;
|
std::atomic<bool> _stop;
|
||||||
std::thread _thread;
|
std::thread _thread;
|
||||||
|
void run();
|
||||||
|
|
||||||
|
// background thread to cleanup (join) terminated threads
|
||||||
|
std::atomic<bool> _stopGc;
|
||||||
|
std::thread _gcThread;
|
||||||
|
void runGC();
|
||||||
|
|
||||||
// the list of (connectionState, threads) for each connections
|
// the list of (connectionState, threads) for each connections
|
||||||
ConnectionThreads _connectionsThreads;
|
ConnectionThreads _connectionsThreads;
|
||||||
@ -87,13 +92,11 @@ namespace ix
|
|||||||
// the factory to create ConnectionState objects
|
// the factory to create ConnectionState objects
|
||||||
ConnectionStateFactory _connectionStateFactory;
|
ConnectionStateFactory _connectionStateFactory;
|
||||||
|
|
||||||
// Methods
|
virtual void handleConnection(int fd, std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||||
void run();
|
|
||||||
virtual void handleConnection(int fd,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
virtual size_t getConnectedClientsCount() = 0;
|
||||||
|
|
||||||
// Returns true if all connection threads are joined
|
// Returns true if all connection threads are joined
|
||||||
bool closeTerminatedThreads();
|
void closeTerminatedThreads();
|
||||||
|
size_t getConnectionsThreadsCount();
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -5,43 +5,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
|
#include "LUrlParser.h"
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
//
|
|
||||||
// The only difference between those 2 regex is the protocol
|
|
||||||
//
|
|
||||||
std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
|
||||||
std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
|
|
||||||
|
|
||||||
bool UrlParser::parse(const std::string& url,
|
bool UrlParser::parse(const std::string& url,
|
||||||
std::string& protocol,
|
std::string& protocol,
|
||||||
std::string& host,
|
std::string& host,
|
||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port,
|
int& port)
|
||||||
bool websocket)
|
|
||||||
{
|
{
|
||||||
std::cmatch what;
|
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
||||||
if (!regex_match(url.c_str(), what,
|
|
||||||
websocket ? _webSocketRegex : _httpRegex))
|
if (!res.IsValid())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string portStr;
|
protocol = res.m_Scheme;
|
||||||
|
host = res.m_Host;
|
||||||
|
path = res.m_Path;
|
||||||
|
query = res.m_Query;
|
||||||
|
|
||||||
protocol = std::string(what[1].first, what[1].second);
|
if (!res.GetPort(&port))
|
||||||
host = std::string(what[2].first, what[2].second);
|
|
||||||
portStr = std::string(what[3].first, what[3].second);
|
|
||||||
path = std::string(what[4].first, what[4].second);
|
|
||||||
query = std::string(what[5].first, what[5].second);
|
|
||||||
|
|
||||||
if (portStr.empty())
|
|
||||||
{
|
{
|
||||||
if (protocol == "ws" || protocol == "http")
|
if (protocol == "ws" || protocol == "http")
|
||||||
{
|
{
|
||||||
@ -58,12 +45,6 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << portStr;
|
|
||||||
ss >> port;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.empty())
|
if (path.empty())
|
||||||
{
|
{
|
||||||
@ -83,22 +64,4 @@ namespace ix
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrlParser::printUrl(const std::string& url, bool websocket)
|
|
||||||
{
|
|
||||||
std::string protocol, host, path, query;
|
|
||||||
int port {0};
|
|
||||||
|
|
||||||
if (!parse(url, protocol, host, path, query, port, websocket))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "[" << url << "]" << std::endl;
|
|
||||||
std::cout << protocol << std::endl;
|
|
||||||
std::cout << host << std::endl;
|
|
||||||
std::cout << port << std::endl;
|
|
||||||
std::cout << path << std::endl;
|
|
||||||
std::cout << query << std::endl;
|
|
||||||
std::cout << "-------------------------------" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -19,13 +18,6 @@ namespace ix
|
|||||||
std::string& host,
|
std::string& host,
|
||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port,
|
int& port);
|
||||||
bool websocket);
|
|
||||||
|
|
||||||
static void printUrl(const std::string& url, bool websocket);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::regex _httpRegex;
|
|
||||||
static std::regex _webSocketRegex;
|
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -8,22 +8,26 @@
|
|||||||
#include "IXSetThreadName.h"
|
#include "IXSetThreadName.h"
|
||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
uint64_t calculateRetryWaitMilliseconds(uint64_t retry_count)
|
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
|
||||||
{
|
{
|
||||||
// This will overflow quite fast for large value of retry_count
|
uint64_t wait_time;
|
||||||
// and will become 0, in which case the wait time will be none
|
|
||||||
// and we'll be constantly retrying to connect.
|
|
||||||
uint64_t wait_time = ((uint64_t) std::pow(2, retry_count) * 100L);
|
|
||||||
|
|
||||||
// cap the wait time to 10s, or to retry_count == 10 for which wait_time > 10s
|
if (retry_count <= 6)
|
||||||
uint64_t tenSeconds = 10 * 1000;
|
{
|
||||||
return (wait_time > tenSeconds || retry_count > 10) ? tenSeconds : wait_time;
|
// max wait_time is 6400 ms (2 ^ 6 = 64)
|
||||||
|
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wait_time = 10 * 1000; // 10 sec
|
||||||
|
}
|
||||||
|
|
||||||
|
return wait_time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +51,7 @@ namespace ix
|
|||||||
_ws.setOnCloseCallback(
|
_ws.setOnCloseCallback(
|
||||||
[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(WebSocket_MessageType_Close, "", wireSize,
|
_onMessageCallback(WebSocketMessageType::Close, "", wireSize,
|
||||||
WebSocketErrorInfo(), WebSocketOpenInfo(),
|
WebSocketErrorInfo(), WebSocketOpenInfo(),
|
||||||
WebSocketCloseInfo(code, reason, remote));
|
WebSocketCloseInfo(code, reason, remote));
|
||||||
}
|
}
|
||||||
@ -138,26 +142,19 @@ namespace ix
|
|||||||
_thread = std::thread(&WebSocket::run, this);
|
_thread = std::thread(&WebSocket::run, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::stop()
|
void WebSocket::stop(uint16_t code,
|
||||||
|
const std::string& reason)
|
||||||
{
|
{
|
||||||
bool automaticReconnection = _automaticReconnection;
|
close(code, reason);
|
||||||
|
|
||||||
// This value needs to be forced when shutting down, it is restored later
|
if (_thread.joinable())
|
||||||
_automaticReconnection = false;
|
|
||||||
|
|
||||||
close();
|
|
||||||
|
|
||||||
if (!_thread.joinable())
|
|
||||||
{
|
{
|
||||||
_automaticReconnection = automaticReconnection;
|
// wait until working thread will exit
|
||||||
return;
|
// it will exit after close operation is finished
|
||||||
|
_stop = true;
|
||||||
|
_thread.join();
|
||||||
|
_stop = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_stop = true;
|
|
||||||
_thread.join();
|
|
||||||
_stop = false;
|
|
||||||
|
|
||||||
_automaticReconnection = automaticReconnection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketInitResult WebSocket::connect(int timeoutSecs)
|
WebSocketInitResult WebSocket::connect(int timeoutSecs)
|
||||||
@ -176,7 +173,7 @@ namespace ix
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
|
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
WebSocketOpenInfo(status.uri, status.headers),
|
WebSocketOpenInfo(status.uri, status.headers),
|
||||||
WebSocketCloseInfo());
|
WebSocketCloseInfo());
|
||||||
@ -199,7 +196,7 @@ namespace ix
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageCallback(WebSocket_MessageType_Open, "", 0,
|
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
||||||
WebSocketErrorInfo(),
|
WebSocketErrorInfo(),
|
||||||
WebSocketOpenInfo(status.uri, status.headers),
|
WebSocketOpenInfo(status.uri, status.headers),
|
||||||
WebSocketCloseInfo());
|
WebSocketCloseInfo());
|
||||||
@ -208,73 +205,103 @@ namespace ix
|
|||||||
|
|
||||||
bool WebSocket::isConnected() const
|
bool WebSocket::isConnected() const
|
||||||
{
|
{
|
||||||
return getReadyState() == WebSocket_ReadyState_Open;
|
return getReadyState() == ReadyState::Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocket::isClosing() const
|
bool WebSocket::isClosing() const
|
||||||
{
|
{
|
||||||
return getReadyState() == WebSocket_ReadyState_Closing;
|
return getReadyState() == ReadyState::Closing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::close()
|
void WebSocket::close(uint16_t code,
|
||||||
|
const std::string& reason)
|
||||||
{
|
{
|
||||||
_ws.close();
|
_ws.close(code, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::reconnectPerpetuallyIfDisconnected()
|
void WebSocket::checkConnection(bool firstConnectionAttempt)
|
||||||
{
|
{
|
||||||
uint64_t retries = 0;
|
|
||||||
WebSocketErrorInfo connectErr;
|
|
||||||
ix::WebSocketInitResult status;
|
|
||||||
using millis = std::chrono::duration<double, std::milli>;
|
using millis = std::chrono::duration<double, std::milli>;
|
||||||
millis duration;
|
|
||||||
|
|
||||||
|
uint32_t retries = 0;
|
||||||
|
millis duration(0);
|
||||||
|
|
||||||
|
// Try to connect perpertually
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isConnected() || isClosing() || _stop || !_automaticReconnection)
|
if (isConnected() || isClosing() || _stop)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = connect(_handshakeTimeoutSecs);
|
if (!firstConnectionAttempt && !_automaticReconnection)
|
||||||
|
|
||||||
if (!status.success && !_stop)
|
|
||||||
{
|
{
|
||||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
// Do not attempt to reconnect
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
connectErr.retries = retries;
|
firstConnectionAttempt = false;
|
||||||
connectErr.wait_time = duration.count();
|
|
||||||
connectErr.reason = status.errorStr;
|
// Only sleep if we are retrying
|
||||||
|
if (duration.count() > 0)
|
||||||
|
{
|
||||||
|
// to do: make sleeping conditional
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to connect synchronously
|
||||||
|
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
|
||||||
|
|
||||||
|
if (!status.success)
|
||||||
|
{
|
||||||
|
WebSocketErrorInfo connectErr;
|
||||||
|
|
||||||
|
if (_automaticReconnection)
|
||||||
|
{
|
||||||
|
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
||||||
|
|
||||||
|
connectErr.wait_time = duration.count();
|
||||||
|
connectErr.retries = retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectErr.reason = status.errorStr;
|
||||||
connectErr.http_status = status.http_status;
|
connectErr.http_status = status.http_status;
|
||||||
_onMessageCallback(WebSocket_MessageType_Error, "", 0,
|
|
||||||
|
_onMessageCallback(WebSocketMessageType::Error, "", 0,
|
||||||
connectErr, WebSocketOpenInfo(),
|
connectErr, WebSocketOpenInfo(),
|
||||||
WebSocketCloseInfo());
|
WebSocketCloseInfo());
|
||||||
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::run()
|
void WebSocket::run()
|
||||||
{
|
{
|
||||||
setThreadName(_url);
|
setThreadName(getUrl());
|
||||||
|
|
||||||
|
bool firstConnectionAttempt = true;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// 1. Make sure we are always connected
|
// 1. Make sure we are always connected
|
||||||
reconnectPerpetuallyIfDisconnected();
|
checkConnection(firstConnectionAttempt);
|
||||||
|
|
||||||
if (_stop) return;
|
firstConnectionAttempt = false;
|
||||||
|
|
||||||
|
// if here we are closed then checkConnection was not able to connect
|
||||||
|
if (getReadyState() == ReadyState::Closed)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can avoid to poll if we want to stop and are not closing
|
||||||
|
if (_stop && !isClosing()) break;
|
||||||
|
|
||||||
// 2. Poll to see if there's any new data available
|
// 2. Poll to see if there's any new data available
|
||||||
_ws.poll();
|
WebSocketTransport::PollResult pollResult = _ws.poll();
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// 3. Dispatch the incoming messages
|
// 3. Dispatch the incoming messages
|
||||||
_ws.dispatch(
|
_ws.dispatch(
|
||||||
|
pollResult,
|
||||||
[this](const std::string& msg,
|
[this](const std::string& msg,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
bool decompressionError,
|
bool decompressionError,
|
||||||
@ -283,24 +310,25 @@ namespace ix
|
|||||||
WebSocketMessageType webSocketMessageType;
|
WebSocketMessageType webSocketMessageType;
|
||||||
switch (messageKind)
|
switch (messageKind)
|
||||||
{
|
{
|
||||||
case WebSocketTransport::MSG:
|
default:
|
||||||
|
case WebSocketTransport::MessageKind::MSG:
|
||||||
{
|
{
|
||||||
webSocketMessageType = WebSocket_MessageType_Message;
|
webSocketMessageType = WebSocketMessageType::Message;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case WebSocketTransport::PING:
|
case WebSocketTransport::MessageKind::PING:
|
||||||
{
|
{
|
||||||
webSocketMessageType = WebSocket_MessageType_Ping;
|
webSocketMessageType = WebSocketMessageType::Ping;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case WebSocketTransport::PONG:
|
case WebSocketTransport::MessageKind::PONG:
|
||||||
{
|
{
|
||||||
webSocketMessageType = WebSocket_MessageType_Pong;
|
webSocketMessageType = WebSocketMessageType::Pong;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case WebSocketTransport::FRAGMENT:
|
case WebSocketTransport::MessageKind::FRAGMENT:
|
||||||
{
|
{
|
||||||
webSocketMessageType = WebSocket_MessageType_Fragment;
|
webSocketMessageType = WebSocketMessageType::Fragment;
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,11 +341,6 @@ namespace ix
|
|||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 4. In blocking mode, getting out of this function is triggered by
|
|
||||||
// an explicit disconnection from the callback, or by the remote end
|
|
||||||
// closing the connection, ie isConnected() == false.
|
|
||||||
if (!_thread.joinable() && !isConnected() && !_automaticReconnection) return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,10 +367,10 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::send(const std::string& text,
|
WebSocketSendInfo WebSocket::send(const std::string& data,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
|
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
||||||
@ -410,11 +433,11 @@ namespace ix
|
|||||||
{
|
{
|
||||||
switch (_ws.getReadyState())
|
switch (_ws.getReadyState())
|
||||||
{
|
{
|
||||||
case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
|
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
|
||||||
case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
|
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
|
||||||
case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
|
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
|
||||||
case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
|
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
|
||||||
default: return WebSocket_ReadyState_Closed;
|
default: return ReadyState::Closed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,11 +445,11 @@ namespace ix
|
|||||||
{
|
{
|
||||||
switch (readyState)
|
switch (readyState)
|
||||||
{
|
{
|
||||||
case WebSocket_ReadyState_Open: return "OPEN";
|
case ReadyState::Open : return "OPEN";
|
||||||
case WebSocket_ReadyState_Connecting: return "CONNECTING";
|
case ReadyState::Connecting: return "CONNECTING";
|
||||||
case WebSocket_ReadyState_Closing: return "CLOSING";
|
case ReadyState::Closing : return "CLOSING";
|
||||||
case WebSocket_ReadyState_Closed: return "CLOSED";
|
case ReadyState::Closed : return "CLOSED";
|
||||||
default: return "CLOSED";
|
default: return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,6 +463,11 @@ namespace ix
|
|||||||
_automaticReconnection = false;
|
_automaticReconnection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebSocket::isAutomaticReconnectionEnabled() const
|
||||||
|
{
|
||||||
|
return _automaticReconnection;
|
||||||
|
}
|
||||||
|
|
||||||
size_t WebSocket::bufferedAmount() const
|
size_t WebSocket::bufferedAmount() const
|
||||||
{
|
{
|
||||||
return _ws.bufferedAmount();
|
return _ws.bufferedAmount();
|
||||||
|
@ -9,38 +9,38 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXProgressCallback.h"
|
||||||
|
#include "IXWebSocketCloseConstants.h"
|
||||||
|
#include "IXWebSocketErrorInfo.h"
|
||||||
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
|
#include "IXWebSocketSendInfo.h"
|
||||||
|
#include "IXWebSocketTransport.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#include "IXWebSocketTransport.h"
|
|
||||||
#include "IXWebSocketErrorInfo.h"
|
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXProgressCallback.h"
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
|
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
|
||||||
enum ReadyState
|
enum class ReadyState
|
||||||
{
|
{
|
||||||
WebSocket_ReadyState_Connecting = 0,
|
Connecting = 0,
|
||||||
WebSocket_ReadyState_Open = 1,
|
Open = 1,
|
||||||
WebSocket_ReadyState_Closing = 2,
|
Closing = 2,
|
||||||
WebSocket_ReadyState_Closed = 3
|
Closed = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WebSocketMessageType
|
enum class WebSocketMessageType
|
||||||
{
|
{
|
||||||
WebSocket_MessageType_Message = 0,
|
Message = 0,
|
||||||
WebSocket_MessageType_Open = 1,
|
Open = 1,
|
||||||
WebSocket_MessageType_Close = 2,
|
Close = 2,
|
||||||
WebSocket_MessageType_Error = 3,
|
Error = 3,
|
||||||
WebSocket_MessageType_Ping = 4,
|
Ping = 4,
|
||||||
WebSocket_MessageType_Pong = 5,
|
Pong = 5,
|
||||||
WebSocket_MessageType_Fragment = 6
|
Fragment = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebSocketOpenInfo
|
struct WebSocketOpenInfo
|
||||||
@ -63,9 +63,7 @@ namespace ix
|
|||||||
std::string reason;
|
std::string reason;
|
||||||
bool remote;
|
bool remote;
|
||||||
|
|
||||||
WebSocketCloseInfo(uint16_t c = 0,
|
WebSocketCloseInfo(uint16_t c = 0, const std::string& r = std::string(), bool rem = false)
|
||||||
const std::string& r = std::string(),
|
|
||||||
bool rem = false)
|
|
||||||
: code(c)
|
: code(c)
|
||||||
, reason(r)
|
, reason(r)
|
||||||
, remote(rem)
|
, remote(rem)
|
||||||
@ -90,8 +88,8 @@ namespace ix
|
|||||||
~WebSocket();
|
~WebSocket();
|
||||||
|
|
||||||
void setUrl(const std::string& url);
|
void setUrl(const std::string& url);
|
||||||
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
void setPerMessageDeflateOptions(
|
||||||
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||||
void setHeartBeatPeriod(int heartBeatPeriodSecs);
|
void setHeartBeatPeriod(int heartBeatPeriodSecs);
|
||||||
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
|
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
|
||||||
void setPingTimeout(int pingTimeoutSecs);
|
void setPingTimeout(int pingTimeoutSecs);
|
||||||
@ -100,24 +98,31 @@ namespace ix
|
|||||||
|
|
||||||
// Run asynchronously, by calling start and stop.
|
// Run asynchronously, by calling start and stop.
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
|
||||||
|
// stop is synchronous
|
||||||
|
void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
||||||
|
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
||||||
|
|
||||||
// Run in blocking mode, by connecting first manually, and then calling run.
|
// Run in blocking mode, by connecting first manually, and then calling run.
|
||||||
WebSocketInitResult connect(int timeoutSecs);
|
WebSocketInitResult connect(int timeoutSecs);
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
WebSocketSendInfo send(const std::string& text,
|
// send binary data
|
||||||
|
WebSocketSendInfo send(const std::string& data,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo sendText(const std::string& text,
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
WebSocketSendInfo ping(const std::string& text);
|
||||||
void close();
|
|
||||||
|
void close(uint16_t code = 1000, const std::string& reason = "Normal closure");
|
||||||
|
|
||||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
||||||
static void resetTrafficTrackerCallback();
|
static void resetTrafficTrackerCallback();
|
||||||
|
|
||||||
ReadyState getReadyState() const;
|
ReadyState getReadyState() const;
|
||||||
|
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 getHeartBeatPeriod() const;
|
int getHeartBeatPeriod() const;
|
||||||
@ -127,21 +132,19 @@ namespace ix
|
|||||||
|
|
||||||
void enableAutomaticReconnection();
|
void enableAutomaticReconnection();
|
||||||
void disableAutomaticReconnection();
|
void disableAutomaticReconnection();
|
||||||
|
bool isAutomaticReconnectionEnabled() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
WebSocketSendInfo sendMessage(const std::string& text,
|
WebSocketSendInfo sendMessage(const std::string& text,
|
||||||
SendMessageKind sendMessageKind,
|
SendMessageKind sendMessageKind,
|
||||||
const OnProgressCallback& callback = nullptr);
|
const OnProgressCallback& callback = nullptr);
|
||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
bool isClosing() const;
|
bool isClosing() const;
|
||||||
void reconnectPerpetuallyIfDisconnected();
|
void checkConnection(bool firstConnectionAttempt);
|
||||||
std::string readyStateToString(ReadyState readyState);
|
|
||||||
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
||||||
|
|
||||||
// Server
|
// Server
|
||||||
void setSocketFileDescriptor(int fd);
|
|
||||||
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
|
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
|
||||||
|
|
||||||
WebSocketTransport _ws;
|
WebSocketTransport _ws;
|
||||||
@ -173,4 +176,4 @@ namespace ix
|
|||||||
|
|
||||||
friend class WebSocketServer;
|
friend class WebSocketServer;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
23
ixwebsocket/IXWebSocketCloseConstants.cpp
Normal file
23
ixwebsocket/IXWebSocketCloseConstants.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketCloseConstants.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXWebSocketCloseConstants.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
|
||||||
|
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
|
||||||
|
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
|
||||||
|
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
|
||||||
|
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
|
||||||
|
|
||||||
|
const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
|
||||||
|
const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error");
|
||||||
|
const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
|
||||||
|
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
|
||||||
|
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
|
||||||
|
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
|
||||||
|
}
|
29
ixwebsocket/IXWebSocketCloseConstants.h
Normal file
29
ixwebsocket/IXWebSocketCloseConstants.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketCloseConstants.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
struct WebSocketCloseConstants
|
||||||
|
{
|
||||||
|
static const uint16_t kNormalClosureCode;
|
||||||
|
static const uint16_t kInternalErrorCode;
|
||||||
|
static const uint16_t kAbnormalCloseCode;
|
||||||
|
static const uint16_t kProtocolErrorCode;
|
||||||
|
static const uint16_t kNoStatusCodeErrorCode;
|
||||||
|
|
||||||
|
static const std::string kNormalClosureMessage;
|
||||||
|
static const std::string kInternalErrorMessage;
|
||||||
|
static const std::string kAbnormalCloseMessage;
|
||||||
|
static const std::string kPingTimeoutMessage;
|
||||||
|
static const std::string kProtocolErrorMessage;
|
||||||
|
static const std::string kNoStatusCodeErrorMessage;
|
||||||
|
};
|
||||||
|
} // namespace ix
|
@ -12,10 +12,10 @@ namespace ix
|
|||||||
{
|
{
|
||||||
struct WebSocketErrorInfo
|
struct WebSocketErrorInfo
|
||||||
{
|
{
|
||||||
uint64_t retries;
|
uint32_t retries = 0;
|
||||||
double wait_time;
|
double wait_time = 0;
|
||||||
int http_status;
|
int http_status = 0;
|
||||||
std::string reason;
|
std::string reason;
|
||||||
bool decompressionError;
|
bool decompressionError = false;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -10,9 +10,7 @@
|
|||||||
|
|
||||||
#include "libwshandshake.hpp"
|
#include "libwshandshake.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <regex>
|
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -244,7 +242,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
char output[29] = {};
|
char output[29] = {};
|
||||||
WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
|
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
|
||||||
if (std::string(output) != headers["sec-websocket-accept"])
|
if (std::string(output) != headers["sec-websocket-accept"])
|
||||||
{
|
{
|
||||||
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
||||||
@ -350,7 +348,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
char output[29] = {};
|
char output[29] = {};
|
||||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
|
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||||
|
@ -7,15 +7,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
#include "IXWebSocketPerMessageDeflate.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
#include "IXSocket.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -42,7 +41,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class WebSocketHandshake {
|
class WebSocketHandshake
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
|
WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
|
||||||
std::shared_ptr<Socket> _socket,
|
std::shared_ptr<Socket> _socket,
|
||||||
@ -56,8 +56,7 @@ namespace ix
|
|||||||
int port,
|
int port,
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
|
|
||||||
WebSocketInitResult serverHandshake(int fd,
|
WebSocketInitResult serverHandshake(int fd, int timeoutSecs);
|
||||||
int timeoutSecs);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string genRandomString(const int len);
|
std::string genRandomString(const int len);
|
||||||
@ -75,4 +74,4 @@ namespace ix
|
|||||||
WebSocketPerMessageDeflateOptions& _perMessageDeflateOptions;
|
WebSocketPerMessageDeflateOptions& _perMessageDeflateOptions;
|
||||||
std::atomic<bool>& _enablePerMessageDeflate;
|
std::atomic<bool>& _enablePerMessageDeflate;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -7,10 +7,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -21,15 +20,14 @@ namespace ix
|
|||||||
// Case Insensitive compare_less binary function
|
// Case Insensitive compare_less binary function
|
||||||
struct NocaseCompare
|
struct NocaseCompare
|
||||||
{
|
{
|
||||||
bool operator() (const unsigned char& c1, const unsigned char& c2) const;
|
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator() (const std::string & s1, const std::string & s2) const;
|
bool operator()(const std::string& s1, const std::string& s2) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
||||||
|
|
||||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
||||||
std::shared_ptr<Socket> socket,
|
std::shared_ptr<Socket> socket, const CancellationRequest& isCancellationRequested);
|
||||||
const CancellationRequest& isCancellationRequested);
|
} // namespace ix
|
||||||
}
|
|
||||||
|
121
ixwebsocket/IXWebSocketMessageQueue.cpp
Normal file
121
ixwebsocket/IXWebSocketMessageQueue.cpp
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketMessageQueue.cpp
|
||||||
|
* Author: Korchynskyi Dmytro
|
||||||
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXWebSocketMessageQueue.h"
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
|
||||||
|
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
|
||||||
|
{
|
||||||
|
bindWebsocket(websocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketMessageQueue::~WebSocketMessageQueue()
|
||||||
|
{
|
||||||
|
if (!_messages.empty())
|
||||||
|
{
|
||||||
|
// not handled all messages
|
||||||
|
}
|
||||||
|
|
||||||
|
bindWebsocket(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket)
|
||||||
|
{
|
||||||
|
if (_websocket == websocket) return;
|
||||||
|
|
||||||
|
// unbind old
|
||||||
|
if (_websocket)
|
||||||
|
{
|
||||||
|
// set dummy callback just to avoid crash
|
||||||
|
_websocket->setOnMessageCallback([](
|
||||||
|
WebSocketMessageType,
|
||||||
|
const std::string&,
|
||||||
|
size_t,
|
||||||
|
const WebSocketErrorInfo&,
|
||||||
|
const WebSocketOpenInfo&,
|
||||||
|
const WebSocketCloseInfo&)
|
||||||
|
{});
|
||||||
|
}
|
||||||
|
|
||||||
|
_websocket = websocket;
|
||||||
|
|
||||||
|
// bind new
|
||||||
|
if (_websocket)
|
||||||
|
{
|
||||||
|
_websocket->setOnMessageCallback([this](
|
||||||
|
WebSocketMessageType type,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const WebSocketErrorInfo& errorInfo,
|
||||||
|
const WebSocketOpenInfo& openInfo,
|
||||||
|
const WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
MessagePtr message(new Message());
|
||||||
|
|
||||||
|
message->type = type;
|
||||||
|
message->str = str;
|
||||||
|
message->wireSize = wireSize;
|
||||||
|
message->errorInfo = errorInfo;
|
||||||
|
message->openInfo = openInfo;
|
||||||
|
message->closeInfo = closeInfo;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_messagesMutex);
|
||||||
|
_messages.emplace_back(std::move(message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback)
|
||||||
|
{
|
||||||
|
_onMessageUserCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback)
|
||||||
|
{
|
||||||
|
_onMessageUserCallback = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage()
|
||||||
|
{
|
||||||
|
MessagePtr message;
|
||||||
|
std::lock_guard<std::mutex> lock(_messagesMutex);
|
||||||
|
|
||||||
|
if (!_messages.empty())
|
||||||
|
{
|
||||||
|
message = std::move(_messages.front());
|
||||||
|
_messages.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketMessageQueue::poll(int count)
|
||||||
|
{
|
||||||
|
if (!_onMessageUserCallback)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MessagePtr message;
|
||||||
|
|
||||||
|
while (count > 0 && (message = popMessage()))
|
||||||
|
{
|
||||||
|
_onMessageUserCallback(
|
||||||
|
message->type,
|
||||||
|
message->str,
|
||||||
|
message->wireSize,
|
||||||
|
message->errorInfo,
|
||||||
|
message->openInfo,
|
||||||
|
message->closeInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
ixwebsocket/IXWebSocketMessageQueue.h
Normal file
53
ixwebsocket/IXWebSocketMessageQueue.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketMessageQueue.h
|
||||||
|
* Author: Korchynskyi Dmytro
|
||||||
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXWebSocket.h"
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// A helper class to dispatch websocket message callbacks in your thread.
|
||||||
|
//
|
||||||
|
class WebSocketMessageQueue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebSocketMessageQueue(WebSocket* websocket = nullptr);
|
||||||
|
~WebSocketMessageQueue();
|
||||||
|
|
||||||
|
void bindWebsocket(WebSocket* websocket);
|
||||||
|
|
||||||
|
void setOnMessageCallback(const OnMessageCallback& callback);
|
||||||
|
void setOnMessageCallback(OnMessageCallback&& callback);
|
||||||
|
|
||||||
|
void poll(int count = 512);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct Message
|
||||||
|
{
|
||||||
|
WebSocketMessageType type;
|
||||||
|
std::string str;
|
||||||
|
size_t wireSize;
|
||||||
|
WebSocketErrorInfo errorInfo;
|
||||||
|
WebSocketOpenInfo openInfo;
|
||||||
|
WebSocketCloseInfo closeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
using MessagePtr = std::shared_ptr<Message>;
|
||||||
|
|
||||||
|
MessagePtr popMessage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
WebSocket* _websocket = nullptr;
|
||||||
|
OnMessageCallback _onMessageUserCallback;
|
||||||
|
std::mutex _messagesMutex;
|
||||||
|
std::list<MessagePtr> _messages;
|
||||||
|
};
|
||||||
|
} // namespace ix
|
@ -34,8 +34,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -57,4 +57,4 @@ namespace ix
|
|||||||
std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
|
std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
|
||||||
std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
|
std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include "IXWebSocketPerMessageDeflateCodec.h"
|
#include "IXWebSocketPerMessageDeflateCodec.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -46,5 +46,4 @@ namespace ix
|
|||||||
z_stream _inflateState;
|
z_stream _inflateState;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace ix
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ namespace ix
|
|||||||
WebSocketPerMessageDeflateOptions(std::string extension);
|
WebSocketPerMessageDeflateOptions(std::string extension);
|
||||||
|
|
||||||
std::string generateHeader();
|
std::string generateHeader();
|
||||||
std::string parseHeader();
|
|
||||||
bool enabled() const;
|
bool enabled() const;
|
||||||
bool getClientNoContextTakeover() const;
|
bool getClientNoContextTakeover() const;
|
||||||
bool getServerNoContextTakeover() const;
|
bool getServerNoContextTakeover() const;
|
||||||
@ -43,4 +42,4 @@ namespace ix
|
|||||||
int _clientMaxWindowBits;
|
int _clientMaxWindowBits;
|
||||||
int _serverMaxWindowBits;
|
int _serverMaxWindowBits;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -6,9 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
struct WebSocketSendInfo
|
struct WebSocketSendInfo
|
||||||
@ -18,8 +15,7 @@ namespace ix
|
|||||||
size_t payloadSize;
|
size_t payloadSize;
|
||||||
size_t wireSize;
|
size_t wireSize;
|
||||||
|
|
||||||
WebSocketSendInfo(bool s = false, bool c = false,
|
WebSocketSendInfo(bool s = false, bool c = false, size_t p = 0, size_t w = 0)
|
||||||
size_t p = 0, size_t w = 0)
|
|
||||||
: success(s)
|
: success(s)
|
||||||
, compressionError(c)
|
, compressionError(c)
|
||||||
, payloadSize(p)
|
, payloadSize(p)
|
||||||
@ -28,4 +24,4 @@ namespace ix
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -6,24 +6,24 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <utility> // pair
|
#include "IXSocketServer.h"
|
||||||
#include <string>
|
#include "IXWebSocket.h"
|
||||||
#include <set>
|
#include <condition_variable>
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <condition_variable>
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
#include "IXWebSocket.h"
|
#include <string>
|
||||||
#include "IXSocketServer.h"
|
#include <thread>
|
||||||
|
#include <utility> // pair
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
using OnConnectionCallback =
|
||||||
std::shared_ptr<ConnectionState>)>;
|
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
class WebSocketServer : public SocketServer {
|
class WebSocketServer final : public SocketServer
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
@ -59,4 +59,4 @@ namespace ix
|
|||||||
std::shared_ptr<ConnectionState> connectionState) final;
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
@ -45,21 +45,24 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
|
||||||
int greatestCommonDivisor (int a, int b) {
|
namespace
|
||||||
while (b != 0)
|
{
|
||||||
{
|
int greatestCommonDivisor(int a, int b)
|
||||||
int t = b;
|
{
|
||||||
b = a % b;
|
while (b != 0)
|
||||||
a = t;
|
{
|
||||||
}
|
int t = b;
|
||||||
|
b = a % b;
|
||||||
|
a = t;
|
||||||
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -68,28 +71,24 @@ namespace ix
|
|||||||
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
||||||
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
const int WebSocketTransport::kDefaultPingTimeoutSecs(-1);
|
||||||
const bool WebSocketTransport::kDefaultEnablePong(true);
|
const bool WebSocketTransport::kDefaultEnablePong(true);
|
||||||
|
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
|
||||||
constexpr size_t WebSocketTransport::kChunkSize;
|
constexpr size_t WebSocketTransport::kChunkSize;
|
||||||
const uint16_t WebSocketTransport::kInternalErrorCode(1011);
|
|
||||||
const uint16_t WebSocketTransport::kAbnormalCloseCode(1006);
|
|
||||||
const uint16_t WebSocketTransport::kProtocolErrorCode(1002);
|
|
||||||
const std::string WebSocketTransport::kInternalErrorMessage("Internal error");
|
|
||||||
const std::string WebSocketTransport::kAbnormalCloseMessage("Abnormal closure");
|
|
||||||
const std::string WebSocketTransport::kPingTimeoutMessage("Ping timeout");
|
|
||||||
const std::string WebSocketTransport::kProtocolErrorMessage("Protocol error");
|
|
||||||
|
|
||||||
WebSocketTransport::WebSocketTransport() :
|
WebSocketTransport::WebSocketTransport() :
|
||||||
_useMask(true),
|
_useMask(true),
|
||||||
_readyState(CLOSED),
|
_readyState(ReadyState::CLOSED),
|
||||||
_closeCode(kInternalErrorCode),
|
_closeCode(WebSocketCloseConstants::kInternalErrorCode),
|
||||||
_closeReason(kInternalErrorMessage),
|
_closeReason(WebSocketCloseConstants::kInternalErrorMessage),
|
||||||
_closeWireSize(0),
|
_closeWireSize(0),
|
||||||
_closeRemote(false),
|
_closeRemote(false),
|
||||||
_enablePerMessageDeflate(false),
|
_enablePerMessageDeflate(false),
|
||||||
_requestInitCancellation(false),
|
_requestInitCancellation(false),
|
||||||
|
_closingTimePoint(std::chrono::steady_clock::now()),
|
||||||
_enablePong(kDefaultEnablePong),
|
_enablePong(kDefaultEnablePong),
|
||||||
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
||||||
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
|
||||||
_pingIntervalOrTimeoutGCDSecs(-1),
|
_pingIntervalOrTimeoutGCDSecs(-1),
|
||||||
|
_nextGCDTimePoint(std::chrono::steady_clock::now()),
|
||||||
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
|
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
|
||||||
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
|
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
|
||||||
{
|
{
|
||||||
@ -131,18 +130,19 @@ namespace ix
|
|||||||
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
|
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
|
||||||
int timeoutSecs)
|
int timeoutSecs)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
bool websocket = true;
|
|
||||||
|
|
||||||
if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
|
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||||
{
|
{
|
||||||
return WebSocketInitResult(false, 0,
|
return WebSocketInitResult(false, 0,
|
||||||
std::string("Could not parse URL ") + url);
|
std::string("Could not parse URL ") + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool tls = protocol == "wss";
|
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
|
bool tls = protocol == "wss";
|
||||||
_socket = createSocket(tls, errorMsg);
|
_socket = createSocket(tls, errorMsg);
|
||||||
|
|
||||||
if (!_socket)
|
if (!_socket)
|
||||||
@ -160,7 +160,7 @@ namespace ix
|
|||||||
timeoutSecs);
|
timeoutSecs);
|
||||||
if (result.success)
|
if (result.success)
|
||||||
{
|
{
|
||||||
setReadyState(OPEN);
|
setReadyState(ReadyState::OPEN);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -168,6 +168,8 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
||||||
{
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
|
||||||
// Server should not mask the data it sends to the client
|
// Server should not mask the data it sends to the client
|
||||||
_useMask = false;
|
_useMask = false;
|
||||||
|
|
||||||
@ -188,32 +190,36 @@ namespace ix
|
|||||||
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
|
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
|
||||||
if (result.success)
|
if (result.success)
|
||||||
{
|
{
|
||||||
setReadyState(OPEN);
|
setReadyState(ReadyState::OPEN);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketTransport::ReadyStateValues WebSocketTransport::getReadyState() const
|
WebSocketTransport::ReadyState WebSocketTransport::getReadyState() const
|
||||||
{
|
{
|
||||||
return _readyState;
|
return _readyState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::setReadyState(ReadyStateValues readyStateValue)
|
void WebSocketTransport::setReadyState(ReadyState readyState)
|
||||||
{
|
{
|
||||||
// No state change, return
|
// No state change, return
|
||||||
if (_readyState == readyStateValue) return;
|
if (_readyState == readyState) return;
|
||||||
|
|
||||||
if (readyStateValue == CLOSED)
|
if (readyState == ReadyState::CLOSED)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
_onCloseCallback(_closeCode, _closeReason, _closeWireSize, _closeRemote);
|
||||||
_closeCode = kInternalErrorCode;
|
_closeCode = WebSocketCloseConstants::kInternalErrorCode;
|
||||||
_closeReason = kInternalErrorMessage;
|
_closeReason = WebSocketCloseConstants::kInternalErrorMessage;
|
||||||
_closeWireSize = 0;
|
_closeWireSize = 0;
|
||||||
_closeRemote = false;
|
_closeRemote = false;
|
||||||
}
|
}
|
||||||
|
else if (readyState == ReadyState::OPEN)
|
||||||
|
{
|
||||||
|
initTimePointsAndGCDAfterConnect();
|
||||||
|
}
|
||||||
|
|
||||||
_readyState = readyStateValue;
|
_readyState = readyState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
|
void WebSocketTransport::setOnCloseCallback(const OnCloseCallback& onCloseCallback)
|
||||||
@ -221,6 +227,23 @@ namespace ix
|
|||||||
_onCloseCallback = onCloseCallback;
|
_onCloseCallback = onCloseCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::initTimePointsAndGCDAfterConnect()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
|
||||||
|
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
|
||||||
|
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
||||||
|
{
|
||||||
|
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only consider send PING time points for that computation.
|
// Only consider send PING time points for that computation.
|
||||||
bool WebSocketTransport::pingIntervalExceeded()
|
bool WebSocketTransport::pingIntervalExceeded()
|
||||||
{
|
{
|
||||||
@ -242,17 +265,23 @@ namespace ix
|
|||||||
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
|
return now - _lastReceivePongTimePoint > std::chrono::seconds(_pingTimeoutSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::poll()
|
bool WebSocketTransport::closingDelayExceeded()
|
||||||
{
|
{
|
||||||
PollResultType pollResult = _socket->poll(_pingIntervalOrTimeoutGCDSecs);
|
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
return now - _closingTimePoint > std::chrono::milliseconds(kClosingMaximumWaitingDelayInMs);
|
||||||
|
}
|
||||||
|
|
||||||
if (_readyState == OPEN)
|
WebSocketTransport::PollResult WebSocketTransport::poll()
|
||||||
|
{
|
||||||
|
if (_readyState == ReadyState::OPEN)
|
||||||
{
|
{
|
||||||
// if (1) ping timeout is enabled and (2) duration since last received
|
// if (1) ping timeout is enabled and (2) duration since last received
|
||||||
// ping response (PONG) exceeds the maximum delay, then close the connection
|
// ping response (PONG) exceeds the maximum delay, then close the connection
|
||||||
if (pingTimeoutExceeded())
|
if (pingTimeoutExceeded())
|
||||||
{
|
{
|
||||||
close(kInternalErrorCode, kPingTimeoutMessage);
|
close(WebSocketCloseConstants::kInternalErrorCode,
|
||||||
|
WebSocketCloseConstants::kPingTimeoutMessage);
|
||||||
}
|
}
|
||||||
// If ping is enabled and no ping has been sent for a duration
|
// If ping is enabled and no ping has been sent for a duration
|
||||||
// exceeding our ping interval, send a ping to the server.
|
// exceeding our ping interval, send a ping to the server.
|
||||||
@ -264,6 +293,45 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No timeout if state is not OPEN, otherwise computed
|
||||||
|
// pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set)
|
||||||
|
int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
|
||||||
|
|
||||||
|
if (_pingIntervalOrTimeoutGCDSecs > 0)
|
||||||
|
{
|
||||||
|
// compute lasting delay to wait for next ping / timeout, if at least one set
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
if (now >= _nextGCDTimePoint)
|
||||||
|
{
|
||||||
|
_nextGCDTimePoint = now + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
|
||||||
|
|
||||||
|
lastingTimeoutDelayInMs = _pingIntervalOrTimeoutGCDSecs * 1000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Windows does not have select interrupt capabilities, so wait with a small timeout
|
||||||
|
if (lastingTimeoutDelayInMs <= 0)
|
||||||
|
{
|
||||||
|
lastingTimeoutDelayInMs = 20;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// If we are requesting a cancellation, pass in a positive and small timeout
|
||||||
|
// to never poll forever without a timeout.
|
||||||
|
if (_requestInitCancellation)
|
||||||
|
{
|
||||||
|
lastingTimeoutDelayInMs = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// poll the socket
|
||||||
|
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
|
||||||
|
|
||||||
// Make sure we send all the buffered data
|
// Make sure we send all the buffered data
|
||||||
// there can be a lot of it for large messages.
|
// there can be a lot of it for large messages.
|
||||||
if (pollResult == PollResultType::SendRequest)
|
if (pollResult == PollResultType::SendRequest)
|
||||||
@ -276,8 +344,8 @@ namespace ix
|
|||||||
|
|
||||||
if (result == PollResultType::Error)
|
if (result == PollResultType::Error)
|
||||||
{
|
{
|
||||||
_socket->close();
|
closeSocket();
|
||||||
setReadyState(CLOSED);
|
setReadyState(ReadyState::CLOSED);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (result == PollResultType::ReadyForWrite)
|
else if (result == PollResultType::ReadyForWrite)
|
||||||
@ -292,24 +360,18 @@ namespace ix
|
|||||||
{
|
{
|
||||||
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
||||||
|
|
||||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
if (ret < 0 && Socket::isWaitNeeded())
|
||||||
_socket->getErrno() == EAGAIN))
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (ret <= 0)
|
else if (ret <= 0)
|
||||||
{
|
{
|
||||||
_rxbuf.clear();
|
// if there are received data pending to be processed, then delay the abnormal closure
|
||||||
_socket->close();
|
// to after dispatch (other close code/reason could be read from the buffer)
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
closeSocket();
|
||||||
_closeCode = kAbnormalCloseCode;
|
|
||||||
_closeReason = kAbnormalCloseMessage;
|
return PollResult::AbnormalClose;
|
||||||
_closeWireSize = 0;
|
|
||||||
_closeRemote = true;
|
|
||||||
}
|
|
||||||
setReadyState(CLOSED);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -321,19 +383,22 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else if (pollResult == PollResultType::Error)
|
else if (pollResult == PollResultType::Error)
|
||||||
{
|
{
|
||||||
_socket->close();
|
closeSocket();
|
||||||
}
|
}
|
||||||
else if (pollResult == PollResultType::CloseRequest)
|
else if (pollResult == PollResultType::CloseRequest)
|
||||||
{
|
{
|
||||||
_socket->close();
|
closeSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid a race condition where we get stuck in select
|
if (_readyState == ReadyState::CLOSING && closingDelayExceeded())
|
||||||
// while closing.
|
|
||||||
if (_readyState == CLOSING)
|
|
||||||
{
|
{
|
||||||
_socket->close();
|
_rxbuf.clear();
|
||||||
|
// close code and reason were set when calling close()
|
||||||
|
closeSocket();
|
||||||
|
setReadyState(ReadyState::CLOSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return PollResult::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketTransport::isSendBufferEmpty() const
|
bool WebSocketTransport::isSendBufferEmpty() const
|
||||||
@ -395,12 +460,13 @@ namespace ix
|
|||||||
// | Payload Data continued ... |
|
// | Payload Data continued ... |
|
||||||
// +---------------------------------------------------------------+
|
// +---------------------------------------------------------------+
|
||||||
//
|
//
|
||||||
void WebSocketTransport::dispatch(const OnMessageCallback& onMessageCallback)
|
void WebSocketTransport::dispatch(WebSocketTransport::PollResult pollResult,
|
||||||
|
const OnMessageCallback& onMessageCallback)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
wsheader_type ws;
|
wsheader_type ws;
|
||||||
if (_rxbuf.size() < 2) return; /* Need at least 2 */
|
if (_rxbuf.size() < 2) break; /* Need at least 2 */
|
||||||
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
|
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
|
||||||
ws.fin = (data[0] & 0x80) == 0x80;
|
ws.fin = (data[0] & 0x80) == 0x80;
|
||||||
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
ws.rsv1 = (data[0] & 0x40) == 0x40;
|
||||||
@ -408,7 +474,7 @@ namespace ix
|
|||||||
ws.mask = (data[1] & 0x80) == 0x80;
|
ws.mask = (data[1] & 0x80) == 0x80;
|
||||||
ws.N0 = (data[1] & 0x7f);
|
ws.N0 = (data[1] & 0x7f);
|
||||||
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
|
||||||
if (_rxbuf.size() < ws.header_size) return; /* Need: ws.header_size - _rxbuf.size() */
|
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
|
||||||
|
|
||||||
//
|
//
|
||||||
// Calculate payload length:
|
// Calculate payload length:
|
||||||
@ -481,7 +547,7 @@ namespace ix
|
|||||||
//
|
//
|
||||||
if (ws.fin && _chunks.empty())
|
if (ws.fin && _chunks.empty())
|
||||||
{
|
{
|
||||||
emitMessage(MSG,
|
emitMessage(MessageKind::MSG,
|
||||||
std::string(_rxbuf.begin()+ws.header_size,
|
std::string(_rxbuf.begin()+ws.header_size,
|
||||||
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
||||||
ws,
|
ws,
|
||||||
@ -501,12 +567,12 @@ namespace ix
|
|||||||
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
||||||
if (ws.fin)
|
if (ws.fin)
|
||||||
{
|
{
|
||||||
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
|
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
|
||||||
_chunks.clear();
|
_chunks.clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
|
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,7 +590,7 @@ namespace ix
|
|||||||
sendData(wsheader_type::PONG, pingData, compress);
|
sendData(wsheader_type::PONG, pingData, compress);
|
||||||
}
|
}
|
||||||
|
|
||||||
emitMessage(PING, pingData, ws, onMessageCallback);
|
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
|
||||||
}
|
}
|
||||||
else if (ws.opcode == wsheader_type::PONG)
|
else if (ws.opcode == wsheader_type::PONG)
|
||||||
{
|
{
|
||||||
@ -535,36 +601,96 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
|
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
|
||||||
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
emitMessage(PONG, pongData, ws, onMessageCallback);
|
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
|
||||||
}
|
}
|
||||||
else if (ws.opcode == wsheader_type::CLOSE)
|
else if (ws.opcode == wsheader_type::CLOSE)
|
||||||
{
|
{
|
||||||
|
std::string reason;
|
||||||
|
uint16_t code = 0;
|
||||||
|
|
||||||
unmaskReceiveBuffer(ws);
|
unmaskReceiveBuffer(ws);
|
||||||
|
|
||||||
// Extract the close code first, available as the first 2 bytes
|
if (ws.N >= 2)
|
||||||
uint16_t code = 0;
|
{
|
||||||
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
|
// Extract the close code first, available as the first 2 bytes
|
||||||
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
|
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
|
||||||
|
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
|
||||||
|
|
||||||
// Get the reason.
|
// Get the reason.
|
||||||
std::string reason(_rxbuf.begin()+ws.header_size + 2,
|
if (ws.N > 2)
|
||||||
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
{
|
||||||
|
reason.assign(_rxbuf.begin()+ws.header_size + 2,
|
||||||
bool remote = true;
|
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no close code received
|
||||||
|
code = WebSocketCloseConstants::kNoStatusCodeErrorCode;
|
||||||
|
reason = WebSocketCloseConstants::kNoStatusCodeErrorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
close(code, reason, _rxbuf.size(), remote);
|
// We receive a CLOSE frame from remote and are NOT the ones who triggered the close
|
||||||
|
if (_readyState != ReadyState::CLOSING)
|
||||||
|
{
|
||||||
|
// send back the CLOSE frame
|
||||||
|
sendCloseFrame(code, reason);
|
||||||
|
|
||||||
|
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
||||||
|
|
||||||
|
bool remote = true;
|
||||||
|
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we got the CLOSE frame answer from our close, so we can close the connection if
|
||||||
|
// the code/reason are the same
|
||||||
|
bool identicalReason;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
|
identicalReason = _closeCode == code && _closeReason == reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identicalReason)
|
||||||
|
{
|
||||||
|
bool remote = false;
|
||||||
|
closeSocketAndSwitchToClosedState(code, reason, _rxbuf.size(), remote);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Unexpected frame type
|
// Unexpected frame type
|
||||||
|
close(WebSocketCloseConstants::kProtocolErrorCode,
|
||||||
close(kProtocolErrorCode, kProtocolErrorMessage, _rxbuf.size());
|
WebSocketCloseConstants::kProtocolErrorMessage,
|
||||||
|
_rxbuf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erase the message that has been processed from the input/read buffer
|
// Erase the message that has been processed from the input/read buffer
|
||||||
_rxbuf.erase(_rxbuf.begin(),
|
_rxbuf.erase(_rxbuf.begin(),
|
||||||
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
|
||||||
|
// the received and processed data then close the connection
|
||||||
|
if (pollResult == PollResult::AbnormalClose)
|
||||||
|
{
|
||||||
|
_rxbuf.clear();
|
||||||
|
|
||||||
|
// if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
|
||||||
|
if (_readyState == ReadyState::CLOSING)
|
||||||
|
{
|
||||||
|
closeSocket();
|
||||||
|
setReadyState(ReadyState::CLOSED);
|
||||||
|
}
|
||||||
|
// if we weren't closing, then close using abnormal close code and message
|
||||||
|
else if (_readyState != ReadyState::CLOSED)
|
||||||
|
{
|
||||||
|
closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode,
|
||||||
|
WebSocketCloseConstants::kAbnormalCloseMessage,
|
||||||
|
0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WebSocketTransport::getMergedChunks() const
|
std::string WebSocketTransport::getMergedChunks() const
|
||||||
@ -595,7 +721,7 @@ namespace ix
|
|||||||
size_t wireSize = message.size();
|
size_t wireSize = message.size();
|
||||||
|
|
||||||
// When the RSV1 bit is 1 it means the message is compressed
|
// When the RSV1 bit is 1 it means the message is compressed
|
||||||
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
|
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
|
||||||
{
|
{
|
||||||
std::string decompressedMessage;
|
std::string decompressedMessage;
|
||||||
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
||||||
@ -622,7 +748,7 @@ namespace ix
|
|||||||
bool compress,
|
bool compress,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
if (_readyState == CLOSING || _readyState == CLOSED)
|
if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
|
||||||
{
|
{
|
||||||
return WebSocketSendInfo();
|
return WebSocketSendInfo();
|
||||||
}
|
}
|
||||||
@ -832,24 +958,28 @@ namespace ix
|
|||||||
_enablePerMessageDeflate, onProgressCallback);
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ssize_t WebSocketTransport::send()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
return _socket->send((char*)&_txbuf[0], _txbuf.size());
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketTransport::sendOnSocket()
|
void WebSocketTransport::sendOnSocket()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||||
|
|
||||||
while (_txbuf.size())
|
while (_txbuf.size())
|
||||||
{
|
{
|
||||||
ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
|
ssize_t ret = send();
|
||||||
|
|
||||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
if (ret < 0 && Socket::isWaitNeeded())
|
||||||
_socket->getErrno() == EAGAIN))
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (ret <= 0)
|
else if (ret <= 0)
|
||||||
{
|
{
|
||||||
_socket->close();
|
closeSocket();
|
||||||
|
setReadyState(ReadyState::CLOSED);
|
||||||
setReadyState(CLOSED);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -859,28 +989,39 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::close(uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
|
||||||
{
|
{
|
||||||
_requestInitCancellation = true;
|
|
||||||
|
|
||||||
if (_readyState == CLOSING || _readyState == CLOSED) return;
|
|
||||||
|
|
||||||
// See list of close events here:
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
||||||
|
|
||||||
int codeLength = 2;
|
|
||||||
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
|
||||||
closure.resize(codeLength + reason.size());
|
|
||||||
|
|
||||||
// copy reason after code
|
|
||||||
closure.replace(codeLength, reason.size(), reason);
|
|
||||||
|
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
sendData(wsheader_type::CLOSE, closure, compress);
|
|
||||||
setReadyState(CLOSING);
|
|
||||||
|
|
||||||
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
// if a status is set/was read
|
||||||
|
if (code != WebSocketCloseConstants::kNoStatusCodeErrorCode)
|
||||||
|
{
|
||||||
|
// See list of close events here:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||||
|
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
|
||||||
|
|
||||||
|
// copy reason after code
|
||||||
|
closure.append(reason);
|
||||||
|
|
||||||
|
sendData(wsheader_type::CLOSE, closure, compress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// no close code/reason set
|
||||||
|
sendData(wsheader_type::CLOSE, "", compress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::closeSocket()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
_socket->close();
|
_socket->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::closeSocketAndSwitchToClosedState(
|
||||||
|
uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
||||||
|
{
|
||||||
|
closeSocket();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
@ -890,7 +1031,33 @@ namespace ix
|
|||||||
_closeRemote = remote;
|
_closeRemote = remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadyState(CLOSED);
|
setReadyState(ReadyState::CLOSED);
|
||||||
|
_requestInitCancellation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketTransport::close(
|
||||||
|
uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
|
||||||
|
{
|
||||||
|
_requestInitCancellation = true;
|
||||||
|
|
||||||
|
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_closeDataMutex);
|
||||||
|
_closeCode = code;
|
||||||
|
_closeReason = reason;
|
||||||
|
_closeWireSize = closeWireSize;
|
||||||
|
_closeRemote = remote;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_closingTimePointMutex);
|
||||||
|
_closingTimePoint = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
setReadyState(ReadyState::CLOSING);
|
||||||
|
|
||||||
|
sendCloseFrame(code, reason);
|
||||||
|
// wake up the poll, but do not close yet
|
||||||
|
_socket->wakeUpFromPoll(Socket::kSendRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WebSocketTransport::bufferedAmount() const
|
size_t WebSocketTransport::bufferedAmount() const
|
||||||
|
@ -10,21 +10,21 @@
|
|||||||
// Adapted from https://github.com/dhbaird/easywsclient
|
// Adapted from https://github.com/dhbaird/easywsclient
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <string>
|
#include "IXCancellationRequest.h"
|
||||||
#include <vector>
|
#include "IXProgressCallback.h"
|
||||||
#include <functional>
|
#include "IXWebSocketCloseConstants.h"
|
||||||
#include <memory>
|
#include "IXWebSocketHandshake.h"
|
||||||
#include <mutex>
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include <atomic>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
#include "IXWebSocketPerMessageDeflate.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketSendInfo.h"
|
||||||
#include "IXCancellationRequest.h"
|
#include <atomic>
|
||||||
#include "IXWebSocketHandshake.h"
|
#include <functional>
|
||||||
#include "IXProgressCallback.h"
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -40,7 +40,7 @@ namespace ix
|
|||||||
class WebSocketTransport
|
class WebSocketTransport
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum ReadyStateValues
|
enum class ReadyState
|
||||||
{
|
{
|
||||||
CLOSING,
|
CLOSING,
|
||||||
CLOSED,
|
CLOSED,
|
||||||
@ -48,7 +48,7 @@ namespace ix
|
|||||||
OPEN
|
OPEN
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MessageKind
|
enum class MessageKind
|
||||||
{
|
{
|
||||||
MSG,
|
MSG,
|
||||||
PING,
|
PING,
|
||||||
@ -56,14 +56,15 @@ namespace ix
|
|||||||
FRAGMENT
|
FRAGMENT
|
||||||
};
|
};
|
||||||
|
|
||||||
using OnMessageCallback = std::function<void(const std::string&,
|
enum class PollResult
|
||||||
size_t,
|
{
|
||||||
bool,
|
Succeeded,
|
||||||
MessageKind)>;
|
AbnormalClose
|
||||||
using OnCloseCallback = std::function<void(uint16_t,
|
};
|
||||||
const std::string&,
|
|
||||||
size_t,
|
using OnMessageCallback =
|
||||||
bool)>;
|
std::function<void(const std::string&, size_t, bool, MessageKind)>;
|
||||||
|
using OnCloseCallback = std::function<void(uint16_t, const std::string&, size_t, bool)>;
|
||||||
|
|
||||||
WebSocketTransport();
|
WebSocketTransport();
|
||||||
~WebSocketTransport();
|
~WebSocketTransport();
|
||||||
@ -75,36 +76,41 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
WebSocketInitResult connectToSocket(int fd, // Server
|
WebSocketInitResult connectToSocket(int fd, // Server
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
|
|
||||||
void poll();
|
PollResult poll();
|
||||||
WebSocketSendInfo sendBinary(const std::string& message,
|
WebSocketSendInfo sendBinary(const std::string& message,
|
||||||
const OnProgressCallback& onProgressCallback);
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendText(const std::string& message,
|
WebSocketSendInfo sendText(const std::string& message,
|
||||||
const OnProgressCallback& onProgressCallback);
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendPing(const std::string& message);
|
WebSocketSendInfo sendPing(const std::string& message);
|
||||||
|
|
||||||
void close(uint16_t code = 1000,
|
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
||||||
const std::string& reason = "Normal closure",
|
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
|
||||||
size_t closeWireSize = 0,
|
size_t closeWireSize = 0,
|
||||||
bool remote = false);
|
bool remote = false);
|
||||||
|
|
||||||
ReadyStateValues getReadyState() const;
|
void closeSocket();
|
||||||
void setReadyState(ReadyStateValues readyStateValue);
|
ssize_t send();
|
||||||
|
|
||||||
|
ReadyState getReadyState() const;
|
||||||
|
void setReadyState(ReadyState readyState);
|
||||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
||||||
void dispatch(const OnMessageCallback& onMessageCallback);
|
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
|
||||||
size_t bufferedAmount() const;
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _url;
|
std::string _url;
|
||||||
|
|
||||||
struct wsheader_type {
|
struct wsheader_type
|
||||||
|
{
|
||||||
unsigned header_size;
|
unsigned header_size;
|
||||||
bool fin;
|
bool fin;
|
||||||
bool rsv1;
|
bool rsv1;
|
||||||
bool mask;
|
bool mask;
|
||||||
enum opcode_type {
|
enum opcode_type
|
||||||
|
{
|
||||||
CONTINUATION = 0x0,
|
CONTINUATION = 0x0,
|
||||||
TEXT_FRAME = 0x1,
|
TEXT_FRAME = 0x1,
|
||||||
BINARY_FRAME = 0x2,
|
BINARY_FRAME = 0x2,
|
||||||
@ -119,7 +125,7 @@ namespace ix
|
|||||||
|
|
||||||
// Tells whether we should mask the data we send.
|
// Tells whether we should mask the data we send.
|
||||||
// client should mask but server should not
|
// client should mask but server should not
|
||||||
bool _useMask;
|
std::atomic<bool> _useMask;
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
// Buffer for reading from our socket. That buffer is never resized.
|
||||||
std::vector<uint8_t> _readbuf;
|
std::vector<uint8_t> _readbuf;
|
||||||
@ -144,9 +150,10 @@ namespace ix
|
|||||||
|
|
||||||
// Underlying TCP socket
|
// Underlying TCP socket
|
||||||
std::shared_ptr<Socket> _socket;
|
std::shared_ptr<Socket> _socket;
|
||||||
|
std::mutex _socketMutex;
|
||||||
|
|
||||||
// Hold the state of the connection (OPEN, CLOSED, etc...)
|
// Hold the state of the connection (OPEN, CLOSED, etc...)
|
||||||
std::atomic<ReadyStateValues> _readyState;
|
std::atomic<ReadyState> _readyState;
|
||||||
|
|
||||||
OnCloseCallback _onCloseCallback;
|
OnCloseCallback _onCloseCallback;
|
||||||
uint16_t _closeCode;
|
uint16_t _closeCode;
|
||||||
@ -163,17 +170,12 @@ namespace ix
|
|||||||
// Used to cancel dns lookup + socket connect + http upgrade
|
// Used to cancel dns lookup + socket connect + http upgrade
|
||||||
std::atomic<bool> _requestInitCancellation;
|
std::atomic<bool> _requestInitCancellation;
|
||||||
|
|
||||||
// Constants for dealing with closing conneections
|
mutable std::mutex _closingTimePointMutex;
|
||||||
static const uint16_t kInternalErrorCode;
|
std::chrono::time_point<std::chrono::steady_clock> _closingTimePoint;
|
||||||
static const uint16_t kAbnormalCloseCode;
|
static const int kClosingMaximumWaitingDelayInMs;
|
||||||
static const uint16_t kProtocolErrorCode;
|
|
||||||
static const std::string kInternalErrorMessage;
|
|
||||||
static const std::string kAbnormalCloseMessage;
|
|
||||||
static const std::string kPingTimeoutMessage;
|
|
||||||
static const std::string kProtocolErrorMessage;
|
|
||||||
|
|
||||||
// enable auto response to ping
|
// enable auto response to ping
|
||||||
bool _enablePong;
|
std::atomic<bool> _enablePong;
|
||||||
static const bool kDefaultEnablePong;
|
static const bool kDefaultEnablePong;
|
||||||
|
|
||||||
// Optional ping and pong timeout
|
// Optional ping and pong timeout
|
||||||
@ -187,6 +189,9 @@ namespace ix
|
|||||||
static const int kDefaultPingTimeoutSecs;
|
static const int kDefaultPingTimeoutSecs;
|
||||||
static const std::string kPingMessage;
|
static const std::string kPingMessage;
|
||||||
|
|
||||||
|
// Record time step for ping/ ping timeout to ensure we wait for the right left duration
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> _nextGCDTimePoint;
|
||||||
|
|
||||||
// We record when ping are being sent so that we can know when to send the next one
|
// We record when ping are being sent so that we can know when to send the next one
|
||||||
// We also record when pong are being sent as a reply to pings, to close the connections
|
// We also record when pong are being sent as a reply to pings, to close the connections
|
||||||
// if no pong were received sufficiently fast.
|
// if no pong were received sufficiently fast.
|
||||||
@ -201,6 +206,19 @@ namespace ix
|
|||||||
// No PONG data was received through the socket for longer than ping timeout delay
|
// No PONG data was received through the socket for longer than ping timeout delay
|
||||||
bool pingTimeoutExceeded();
|
bool pingTimeoutExceeded();
|
||||||
|
|
||||||
|
// after calling close(), if no CLOSE frame answer is received back from the remote, we
|
||||||
|
// should close the connexion
|
||||||
|
bool closingDelayExceeded();
|
||||||
|
|
||||||
|
void initTimePointsAndGCDAfterConnect();
|
||||||
|
|
||||||
|
void sendCloseFrame(uint16_t code, const std::string& reason);
|
||||||
|
|
||||||
|
void closeSocketAndSwitchToClosedState(uint16_t code,
|
||||||
|
const std::string& reason,
|
||||||
|
size_t closeWireSize,
|
||||||
|
bool remote);
|
||||||
|
|
||||||
void sendOnSocket();
|
void sendOnSocket();
|
||||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||||
const std::string& message,
|
const std::string& message,
|
||||||
@ -230,4 +248,4 @@ namespace ix
|
|||||||
|
|
||||||
std::string getMergedChunks() const;
|
std::string getMergedChunks() const;
|
||||||
};
|
};
|
||||||
}
|
} // namespace ix
|
||||||
|
263
ixwebsocket/LUrlParser.cpp
Normal file
263
ixwebsocket/LUrlParser.cpp
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||||
|
* https://github.com/corporateshark/LUrlParser
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "LUrlParser.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// check if the scheme name is valid
|
||||||
|
static bool IsSchemeValid( const std::string& SchemeName )
|
||||||
|
{
|
||||||
|
for ( auto c : SchemeName )
|
||||||
|
{
|
||||||
|
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
|
||||||
|
{
|
||||||
|
if ( !IsValid() ) { return false; }
|
||||||
|
|
||||||
|
int Port = atoi( m_Port.c_str() );
|
||||||
|
|
||||||
|
if ( Port <= 0 || Port > 65535 ) { return false; }
|
||||||
|
|
||||||
|
if ( OutPort ) { *OutPort = Port; }
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// based on RFC 1738 and RFC 3986
|
||||||
|
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
|
||||||
|
{
|
||||||
|
LUrlParser::clParseURL Result;
|
||||||
|
|
||||||
|
const char* CurrentString = URL.c_str();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <scheme>:<scheme-specific-part>
|
||||||
|
* <scheme> := [a-z\+\-\.]+
|
||||||
|
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
|
||||||
|
*/
|
||||||
|
|
||||||
|
// try to read scheme
|
||||||
|
{
|
||||||
|
const char* LocalString = strchr( CurrentString, ':' );
|
||||||
|
|
||||||
|
if ( !LocalString )
|
||||||
|
{
|
||||||
|
return clParseURL( LUrlParserError_NoUrlCharacter );
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the scheme name
|
||||||
|
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
if ( !IsSchemeValid( Result.m_Scheme ) )
|
||||||
|
{
|
||||||
|
return clParseURL( LUrlParserError_InvalidSchemeName );
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheme should be lowercase
|
||||||
|
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
|
||||||
|
|
||||||
|
// skip ':'
|
||||||
|
CurrentString = LocalString+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* //<user>:<password>@<host>:<port>/<url-path>
|
||||||
|
* any ":", "@" and "/" must be normalized
|
||||||
|
*/
|
||||||
|
|
||||||
|
// skip "//"
|
||||||
|
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||||
|
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
||||||
|
|
||||||
|
// check if the user name and password are specified
|
||||||
|
bool bHasUserName = false;
|
||||||
|
|
||||||
|
const char* LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString )
|
||||||
|
{
|
||||||
|
if ( *LocalString == '@' )
|
||||||
|
{
|
||||||
|
// user name and password are specified
|
||||||
|
bHasUserName = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ( *LocalString == '/' )
|
||||||
|
{
|
||||||
|
// end of <host>:<port> specification
|
||||||
|
bHasUserName = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user name and password
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
if ( bHasUserName )
|
||||||
|
{
|
||||||
|
// read user name
|
||||||
|
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
// proceed with the current pointer
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
if ( *CurrentString == ':' )
|
||||||
|
{
|
||||||
|
// skip ':'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read password
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString && *LocalString != '@' ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '@'
|
||||||
|
if ( *CurrentString != '@' )
|
||||||
|
{
|
||||||
|
return clParseURL( LUrlParserError_NoAtSign );
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bHasBracket = ( *CurrentString == '[' );
|
||||||
|
|
||||||
|
// go ahead, read the host name
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString )
|
||||||
|
{
|
||||||
|
if ( bHasBracket && *LocalString == ']' )
|
||||||
|
{
|
||||||
|
// end of IPv6 address
|
||||||
|
LocalString++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
|
||||||
|
{
|
||||||
|
// port number is specified
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalString++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
// is port number specified?
|
||||||
|
if ( *CurrentString == ':' )
|
||||||
|
{
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read port number
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString && *LocalString != '/' ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of string
|
||||||
|
if ( !*CurrentString )
|
||||||
|
{
|
||||||
|
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip '/'
|
||||||
|
if ( *CurrentString != '/' )
|
||||||
|
{
|
||||||
|
return clParseURL( LUrlParserError_NoSlash );
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// parse the path
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
|
||||||
|
// check for query
|
||||||
|
if ( *CurrentString == '?' )
|
||||||
|
{
|
||||||
|
// skip '?'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read query
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString && *LocalString != '#' ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
|
||||||
|
CurrentString = LocalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for fragment
|
||||||
|
if ( *CurrentString == '#' )
|
||||||
|
{
|
||||||
|
// skip '#'
|
||||||
|
CurrentString++;
|
||||||
|
|
||||||
|
// read fragment
|
||||||
|
LocalString = CurrentString;
|
||||||
|
|
||||||
|
while ( *LocalString ) LocalString++;
|
||||||
|
|
||||||
|
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.m_ErrorCode = LUrlParserError_Ok;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
81
ixwebsocket/LUrlParser.h
Normal file
81
ixwebsocket/LUrlParser.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
||||||
|
* https://github.com/corporateshark/LUrlParser
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace LUrlParser
|
||||||
|
{
|
||||||
|
enum LUrlParserError
|
||||||
|
{
|
||||||
|
LUrlParserError_Ok = 0,
|
||||||
|
LUrlParserError_Uninitialized = 1,
|
||||||
|
LUrlParserError_NoUrlCharacter = 2,
|
||||||
|
LUrlParserError_InvalidSchemeName = 3,
|
||||||
|
LUrlParserError_NoDoubleSlash = 4,
|
||||||
|
LUrlParserError_NoAtSign = 5,
|
||||||
|
LUrlParserError_UnexpectedEndOfLine = 6,
|
||||||
|
LUrlParserError_NoSlash = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
class clParseURL
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LUrlParserError m_ErrorCode;
|
||||||
|
std::string m_Scheme;
|
||||||
|
std::string m_Host;
|
||||||
|
std::string m_Port;
|
||||||
|
std::string m_Path;
|
||||||
|
std::string m_Query;
|
||||||
|
std::string m_Fragment;
|
||||||
|
std::string m_UserName;
|
||||||
|
std::string m_Password;
|
||||||
|
|
||||||
|
clParseURL()
|
||||||
|
: m_ErrorCode(LUrlParserError_Uninitialized)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return 'true' if the parsing was successful
|
||||||
|
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
||||||
|
|
||||||
|
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
||||||
|
/// 0..65535 range)
|
||||||
|
bool GetPort(int* OutPort) const;
|
||||||
|
|
||||||
|
/// parse the URL
|
||||||
|
static clParseURL ParseURL(const std::string& URL);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit clParseURL(LUrlParserError ErrorCode)
|
||||||
|
: m_ErrorCode(ErrorCode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace LUrlParser
|
@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
class WebSocketHandshakeKeyGen {
|
class WebSocketHandshakeKeyGen {
|
||||||
template <int N, typename T>
|
template <int N, typename T>
|
||||||
@ -100,7 +102,12 @@ class WebSocketHandshakeKeyGen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static inline void generate(const char input[24], char output[28]) {
|
static inline void generate(const std::string& inputStr, char output[28]) {
|
||||||
|
|
||||||
|
char input[25] = {};
|
||||||
|
strncpy(input, inputStr.c_str(), 25 - 1);
|
||||||
|
input[25 - 1] = '\0';
|
||||||
|
|
||||||
uint32_t b_output[5] = {
|
uint32_t b_output[5] = {
|
||||||
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
|
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
|
||||||
};
|
};
|
||||||
|
@ -4,13 +4,41 @@
|
|||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
#include "../IXSetThreadName.h"
|
#include "../IXSetThreadName.h"
|
||||||
#include <iostream>
|
#include <Windows.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
|
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
||||||
|
|
||||||
|
#pragma pack(push,8)
|
||||||
|
typedef struct tagTHREADNAME_INFO
|
||||||
|
{
|
||||||
|
DWORD dwType; // Must be 0x1000.
|
||||||
|
LPCSTR szName; // Pointer to name (in user addr space).
|
||||||
|
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||||
|
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||||
|
} THREADNAME_INFO;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
||||||
|
{
|
||||||
|
THREADNAME_INFO info;
|
||||||
|
info.dwType = 0x1000;
|
||||||
|
info.szName = threadName;
|
||||||
|
info.dwThreadID = dwThreadID;
|
||||||
|
info.dwFlags = 0;
|
||||||
|
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)& info);
|
||||||
|
}
|
||||||
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setThreadName(const std::string& name)
|
void setThreadName(const std::string& name)
|
||||||
{
|
{
|
||||||
// FIXME
|
SetThreadName(-1, name.c_str());
|
||||||
std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
makefile
18
makefile
@ -9,7 +9,10 @@ install: brew
|
|||||||
# on osx it is good practice to make /usr/local user writable
|
# on osx it is good practice to make /usr/local user writable
|
||||||
# sudo chown -R `whoami`/staff /usr/local
|
# sudo chown -R `whoami`/staff /usr/local
|
||||||
brew:
|
brew:
|
||||||
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 .. ; make -j install)
|
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j install)
|
||||||
|
|
||||||
|
ws:
|
||||||
|
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
xargs rm -fv < build/install_manifest.txt
|
xargs rm -fv < build/install_manifest.txt
|
||||||
@ -22,6 +25,9 @@ IMG := ${NAME}:${TAG}
|
|||||||
LATEST := ${NAME}:latest
|
LATEST := ${NAME}:latest
|
||||||
BUILD := ${NAME}:build
|
BUILD := ${NAME}:build
|
||||||
|
|
||||||
|
docker_test:
|
||||||
|
docker build -f docker/Dockerfile.debian -t bsergean/ixwebsocket_test:build .
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t ${IMG} .
|
docker build -t ${IMG} .
|
||||||
docker tag ${IMG} ${BUILD}
|
docker tag ${IMG} ${BUILD}
|
||||||
@ -31,12 +37,15 @@ docker_push:
|
|||||||
docker push ${LATEST}
|
docker push ${LATEST}
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker run --cap-add sys_ptrace --entrypoint=bash -it bsergean/ws:build
|
docker run --cap-add sys_ptrace --entrypoint=sh -it bsergean/ws:build
|
||||||
|
|
||||||
# this is helpful to remove trailing whitespaces
|
# this is helpful to remove trailing whitespaces
|
||||||
trail:
|
trail:
|
||||||
sh third_party/remote_trailing_whitespaces.sh
|
sh third_party/remote_trailing_whitespaces.sh
|
||||||
|
|
||||||
|
format:
|
||||||
|
find ixwebsocket ws -name '*.cpp' -o -name '*.h' -exec clang-format -i {} \;
|
||||||
|
|
||||||
# That target is used to start a node server, but isn't required as we have
|
# That target is used to start a node server, but isn't required as we have
|
||||||
# a builtin C++ server started in the unittest now
|
# a builtin C++ server started in the unittest now
|
||||||
test_server:
|
test_server:
|
||||||
@ -48,8 +57,8 @@ test_server:
|
|||||||
test:
|
test:
|
||||||
python2.7 test/run.py
|
python2.7 test/run.py
|
||||||
|
|
||||||
ws_test: all
|
ws_test: ws
|
||||||
(cd ws ; bash test_ws.sh)
|
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
|
||||||
|
|
||||||
# For the fork that is configured with appveyor
|
# For the fork that is configured with appveyor
|
||||||
rebase_upstream:
|
rebase_upstream:
|
||||||
@ -64,3 +73,4 @@ install_cmake_for_linux:
|
|||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
.PHONY: ws
|
||||||
|
7
test/.gitignore
vendored
7
test/.gitignore
vendored
@ -1,9 +1,10 @@
|
|||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
package-lock.json
|
package-lock.json
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
ixwebsocket_unittest
|
ixwebsocket_unittest
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
node_modules
|
node_modules
|
||||||
ixwebsocket
|
ixwebsocket
|
||||||
Makefile
|
Makefile
|
||||||
build
|
build
|
||||||
|
ixwebsocket_unittest.xml
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
cmake_minimum_required (VERSION 3.4.1)
|
cmake_minimum_required (VERSION 3.4.1)
|
||||||
project (ixwebsocket_unittest)
|
project (ixwebsocket_unittest)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
|
||||||
find_package(Sanitizers)
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||||
|
find_package(Sanitizers)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
|
set(CMAKE_LD_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
option(USE_TLS "Add TLS support" ON)
|
option(USE_TLS "Add TLS support" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
|
|||||||
include_directories(
|
include_directories(
|
||||||
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
${PROJECT_SOURCE_DIR}/Catch2/single_include
|
||||||
../third_party/msgpack11
|
../third_party/msgpack11
|
||||||
|
../third_party/spdlog/include
|
||||||
|
../ws
|
||||||
)
|
)
|
||||||
|
|
||||||
# Shared sources
|
# Shared sources
|
||||||
@ -26,25 +29,44 @@ set (SOURCES
|
|||||||
test_runner.cpp
|
test_runner.cpp
|
||||||
IXTest.cpp
|
IXTest.cpp
|
||||||
../third_party/msgpack11/msgpack11.cpp
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
|
../ws/ixcore/utils/IXCoreLogger.cpp
|
||||||
|
|
||||||
IXDNSLookupTest.cpp
|
|
||||||
IXSocketTest.cpp
|
IXSocketTest.cpp
|
||||||
IXSocketConnectTest.cpp
|
IXSocketConnectTest.cpp
|
||||||
|
IXWebSocketServerTest.cpp
|
||||||
|
IXWebSocketTestConnectionDisconnection.cpp
|
||||||
|
IXUrlParserTest.cpp
|
||||||
|
IXWebSocketServerTest.cpp
|
||||||
|
IXHttpClientTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
if (NOT WIN32)
|
if (UNIX)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
IXWebSocketServerTest.cpp
|
IXDNSLookupTest.cpp
|
||||||
IXWebSocketPingTest.cpp
|
|
||||||
IXWebSocketPingTimeoutTest.cpp
|
|
||||||
cmd_websocket_chat.cpp
|
cmd_websocket_chat.cpp
|
||||||
IXWebSocketTestConnectionDisconnection.cpp
|
IXWebSocketCloseTest.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Some unittest fail for dubious reason on Ubuntu Xenial with TSAN
|
||||||
|
if (MAC OR WIN32)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
IXWebSocketMessageQTest.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Ping test fails intermittently, disabling them for now
|
||||||
|
# IXWebSocketPingTest.cpp
|
||||||
|
# IXWebSocketPingTimeoutTest.cpp
|
||||||
|
|
||||||
|
# Disable tests for now that are failing or not reliable
|
||||||
|
|
||||||
add_executable(ixwebsocket_unittest ${SOURCES})
|
add_executable(ixwebsocket_unittest ${SOURCES})
|
||||||
add_sanitizers(ixwebsocket_unittest)
|
|
||||||
|
if (NOT WIN32)
|
||||||
|
add_sanitizers(ixwebsocket_unittest)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
if (APPLE AND USE_TLS)
|
||||||
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
|
||||||
|
233
test/IXHttpClientTest.cpp
Normal file
233
test/IXHttpClientTest.cpp
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
TEST_CASE("http client", "[http]")
|
||||||
|
{
|
||||||
|
SECTION("Connect to a remote HTTP server")
|
||||||
|
{
|
||||||
|
HttpClient httpClient;
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
std::string url("http://httpbin.org/");
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cerr << "\r" << "Downloaded "
|
||||||
|
<< current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto response = httpClient.get(url, args);
|
||||||
|
|
||||||
|
for (auto it : response->headers)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||||
|
REQUIRE(response->statusCode == 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Connect to a remote HTTPS server")
|
||||||
|
{
|
||||||
|
HttpClient httpClient;
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
std::string url("https://httpbin.org/");
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cerr << "\r" << "Downloaded "
|
||||||
|
<< current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto response = httpClient.get(url, args);
|
||||||
|
|
||||||
|
for (auto it : response->headers)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||||
|
REQUIRE(response->statusCode == 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Async API, one call")
|
||||||
|
{
|
||||||
|
bool async = true;
|
||||||
|
HttpClient httpClient(async);
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
std::string url("https://httpbin.org/");
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cerr << "\r" << "Downloaded "
|
||||||
|
<< current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<bool> requestCompleted(false);
|
||||||
|
std::atomic<int> statusCode(0);
|
||||||
|
|
||||||
|
httpClient.performRequest(args, [&requestCompleted, &statusCode]
|
||||||
|
(const HttpResponsePtr& response)
|
||||||
|
{
|
||||||
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
// In case of failure, print response->errorMsg
|
||||||
|
statusCode = response->statusCode;
|
||||||
|
requestCompleted = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
int wait = 0;
|
||||||
|
while (wait < 5000)
|
||||||
|
{
|
||||||
|
if (requestCompleted) break;
|
||||||
|
|
||||||
|
std::chrono::duration<double, std::milli> duration(10);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
wait += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Done" << std::endl;
|
||||||
|
REQUIRE(statusCode == 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Async API, multiple calls")
|
||||||
|
{
|
||||||
|
bool async = true;
|
||||||
|
HttpClient httpClient(async);
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
std::string url("http://httpbin.org/");
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cerr << "\r" << "Downloaded "
|
||||||
|
<< current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<bool> requestCompleted(false);
|
||||||
|
std::atomic<int> statusCode0(0);
|
||||||
|
std::atomic<int> statusCode1(0);
|
||||||
|
std::atomic<int> statusCode2(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
httpClient.performRequest(args, [i, &requestCompleted, &statusCode0, &statusCode1, &statusCode2]
|
||||||
|
(const HttpResponsePtr& response)
|
||||||
|
{
|
||||||
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
// In case of failure, print response->errorMsg
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
statusCode0 = response->statusCode;
|
||||||
|
}
|
||||||
|
else if (i == 1)
|
||||||
|
{
|
||||||
|
statusCode1 = response->statusCode;
|
||||||
|
}
|
||||||
|
else if (i == 2)
|
||||||
|
{
|
||||||
|
statusCode2 = response->statusCode;
|
||||||
|
requestCompleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int wait = 0;
|
||||||
|
while (wait < 10000)
|
||||||
|
{
|
||||||
|
if (requestCompleted) break;
|
||||||
|
|
||||||
|
std::chrono::duration<double, std::milli> duration(10);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
wait += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Done" << std::endl;
|
||||||
|
REQUIRE(statusCode0 == 200);
|
||||||
|
REQUIRE(statusCode1 == 200);
|
||||||
|
REQUIRE(statusCode2 == 200);
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,12 @@ TEST_CASE("socket_connect", "[net]")
|
|||||||
{
|
{
|
||||||
SECTION("Test connecting to a known hostname")
|
SECTION("Test connecting to a known hostname")
|
||||||
{
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
REQUIRE(startWebSocketEchoServer(server));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return false; });
|
int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return false; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(fd != -1);
|
REQUIRE(fd != -1);
|
||||||
}
|
}
|
||||||
@ -34,9 +38,13 @@ TEST_CASE("socket_connect", "[net]")
|
|||||||
|
|
||||||
SECTION("Test connecting to a good hostname, with cancellation")
|
SECTION("Test connecting to a good hostname, with cancellation")
|
||||||
{
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
REQUIRE(startWebSocketEchoServer(server));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
// The callback returning true means we are requesting cancellation
|
// The callback returning true means we are requesting cancellation
|
||||||
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return true; });
|
int fd = SocketConnect::connect("127.0.0.1", port, errMsg, [] { return true; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(fd == -1);
|
REQUIRE(fd == -1);
|
||||||
}
|
}
|
||||||
|
@ -53,13 +53,17 @@ namespace ix
|
|||||||
|
|
||||||
TEST_CASE("socket", "[socket]")
|
TEST_CASE("socket", "[socket]")
|
||||||
{
|
{
|
||||||
SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
|
SECTION("Connect to a local websocket server over a free port. Send GET request without header. Should return 400")
|
||||||
{
|
{
|
||||||
|
// Start a server first which we'll hit with our socket code
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
REQUIRE(startWebSocketEchoServer(server));
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::string host("www.google.com");
|
std::string host("127.0.0.1");
|
||||||
int port = 80;
|
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "GET / HTTP/1.1\r\n";
|
ss << "GET / HTTP/1.1\r\n";
|
||||||
@ -67,14 +71,14 @@ TEST_CASE("socket", "[socket]")
|
|||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
std::string request(ss.str());
|
std::string request(ss.str());
|
||||||
|
|
||||||
int expectedStatus = 200;
|
int expectedStatus = 400;
|
||||||
int timeoutSecs = 3;
|
int timeoutSecs = 3;
|
||||||
|
|
||||||
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
|
testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__linux__)
|
#if defined(IXWEBSOCKET_USE_TLS)
|
||||||
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
|
SECTION("Connect to google HTTPS server over port 443. Send GET request without header. Should return 200")
|
||||||
{
|
{
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = true;
|
bool tls = true;
|
||||||
|
@ -108,22 +108,22 @@ namespace ix
|
|||||||
{
|
{
|
||||||
log("Cannot compute a free port. bind error.");
|
log("Cannot compute a free port. bind error.");
|
||||||
|
|
||||||
::close(sockfd);
|
Socket::closeSocket(sockfd);
|
||||||
return getAnyFreePortRandom();
|
return getAnyFreePortRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_in sa; // server address information
|
struct sockaddr_in sa; // server address information
|
||||||
socklen_t len;
|
socklen_t len = sizeof(sa);
|
||||||
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
||||||
{
|
{
|
||||||
log("Cannot compute a free port. getsockname error.");
|
log("Cannot compute a free port. getsockname error.");
|
||||||
|
|
||||||
::close(sockfd);
|
Socket::closeSocket(sockfd);
|
||||||
return getAnyFreePortRandom();
|
return getAnyFreePortRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
int port = ntohs(sa.sin_port);
|
int port = ntohs(sa.sin_port);
|
||||||
::close(sockfd);
|
Socket::closeSocket(sockfd);
|
||||||
|
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
@ -170,4 +170,58 @@ namespace ix
|
|||||||
|
|
||||||
std::cout << prefix << ": " << s << " => " << ss.str() << std::endl;
|
std::cout << prefix << ": " << s << " => " << ss.str() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool startWebSocketEchoServer(ix::WebSocketServer& server)
|
||||||
|
{
|
||||||
|
server.setOnConnectionCallback(
|
||||||
|
[&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
|
{
|
||||||
|
webSocket->setOnMessageCallback(
|
||||||
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
Logger() << "New connection";
|
||||||
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
|
Logger() << "Headers:";
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
Logger() << it.first << ": " << it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
Logger() << "Closed connection";
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
|
{
|
||||||
|
if (client != webSocket)
|
||||||
|
{
|
||||||
|
client->send(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
Logger() << res.second;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -27,22 +29,14 @@ namespace ix
|
|||||||
struct Logger
|
struct Logger
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Logger& operator<<(const std::string& msg)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
std::cerr << msg;
|
|
||||||
std::cerr << std::endl;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Logger& operator<<(T const& obj)
|
Logger& operator<<(T const& obj)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
std::lock_guard<std::mutex> lock(_mutex);
|
||||||
|
|
||||||
std::cerr << obj;
|
std::stringstream ss;
|
||||||
std::cerr << std::endl;
|
ss << obj;
|
||||||
|
spdlog::info(ss.str());
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +46,7 @@ namespace ix
|
|||||||
|
|
||||||
void log(const std::string& msg);
|
void log(const std::string& msg);
|
||||||
|
|
||||||
bool computeFreePorts(int count);
|
|
||||||
int getFreePort();
|
int getFreePort();
|
||||||
|
|
||||||
|
bool startWebSocketEchoServer(ix::WebSocketServer& server);
|
||||||
}
|
}
|
||||||
|
108
test/IXUrlParserTest.cpp
Normal file
108
test/IXUrlParserTest.cpp
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketTest.cpp
|
||||||
|
* Author: Korchynskyi Dmytro
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXUrlParser.h>
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
|
||||||
|
TEST_CASE("urlParser", "[urlParser]")
|
||||||
|
{
|
||||||
|
SECTION("http://google.com")
|
||||||
|
{
|
||||||
|
std::string url = "http://google.com";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "http");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/");
|
||||||
|
REQUIRE(query == "");
|
||||||
|
REQUIRE(port == 80); // default port for http
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("https://google.com")
|
||||||
|
{
|
||||||
|
std::string url = "https://google.com";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "https");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/");
|
||||||
|
REQUIRE(query == "");
|
||||||
|
REQUIRE(port == 443); // default port for https
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ws://google.com")
|
||||||
|
{
|
||||||
|
std::string url = "ws://google.com";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "ws");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/");
|
||||||
|
REQUIRE(query == "");
|
||||||
|
REQUIRE(port == 80); // default port for ws
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("wss://google.com/ws?arg=value")
|
||||||
|
{
|
||||||
|
std::string url = "wss://google.com/ws?arg=value&arg2=value2";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "wss");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/ws?arg=value&arg2=value2");
|
||||||
|
REQUIRE(query == "arg=value&arg2=value2");
|
||||||
|
REQUIRE(port == 443); // default port for wss
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("real test")
|
||||||
|
{
|
||||||
|
std::string url = "ws://127.0.0.1:7350/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "ws");
|
||||||
|
REQUIRE(host == "127.0.0.1");
|
||||||
|
REQUIRE(path == "/ws?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
|
||||||
|
REQUIRE(query == "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTcxNzAwNzIsInVpZCI6ImMwZmZjOGE1LTk4OTktNDAwYi1hNGU5LTJjNWM3NjFmNWQxZiIsInVzbiI6InN2YmhOdlNJSmEifQ.5L8BUbpTA4XAHlSrdwhIVlrlIpRtjExepim7Yh5eEO4&status=true&format=protobuf");
|
||||||
|
REQUIRE(port == 7350);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
456
test/IXWebSocketCloseTest.cpp
Normal file
456
test/IXWebSocketCloseTest.cpp
Normal file
@ -0,0 +1,456 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketCloseTest.cpp
|
||||||
|
* Author: Alexandre Konieczny
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <queue>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class WebSocketClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebSocketClient(int port);
|
||||||
|
|
||||||
|
void subscribe(const std::string& channel);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void stop(uint16_t code, const std::string& reason);
|
||||||
|
bool isReady() const;
|
||||||
|
void sendMessage(const std::string& text);
|
||||||
|
|
||||||
|
uint16_t getCloseCode();
|
||||||
|
const std::string& getCloseReason();
|
||||||
|
bool getCloseRemote();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ix::WebSocket _webSocket;
|
||||||
|
int _port;
|
||||||
|
|
||||||
|
mutable std::mutex _mutexCloseData;
|
||||||
|
uint16_t _closeCode;
|
||||||
|
std::string _closeReason;
|
||||||
|
bool _closeRemote;
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketClient::WebSocketClient(int port)
|
||||||
|
: _port(port)
|
||||||
|
, _closeCode(0)
|
||||||
|
, _closeReason(std::string(""))
|
||||||
|
, _closeRemote(false)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketClient::isReady() const
|
||||||
|
{
|
||||||
|
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t WebSocketClient::getCloseCode()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||||
|
|
||||||
|
return _closeCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& WebSocketClient::getCloseReason()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||||
|
|
||||||
|
return _closeReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebSocketClient::getCloseRemote()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||||
|
|
||||||
|
return _closeRemote;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::stop()
|
||||||
|
{
|
||||||
|
_webSocket.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::stop(uint16_t code, const std::string& reason)
|
||||||
|
{
|
||||||
|
_webSocket.stop(code, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::start()
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "ws://localhost:"
|
||||||
|
<< _port
|
||||||
|
<< "/";
|
||||||
|
|
||||||
|
url = ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocket.setUrl(url);
|
||||||
|
_webSocket.disableAutomaticReconnection();
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
log(std::string("Connecting to url: ") + url);
|
||||||
|
|
||||||
|
_webSocket.setOnMessageCallback(
|
||||||
|
[this](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
log("client connected");
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "client disconnected("
|
||||||
|
<< closeInfo.code
|
||||||
|
<< ","
|
||||||
|
<< closeInfo.reason
|
||||||
|
<< ")";
|
||||||
|
log(ss.str());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lck(_mutexCloseData);
|
||||||
|
|
||||||
|
_closeCode = closeInfo.code;
|
||||||
|
_closeReason = std::string(closeInfo.reason);
|
||||||
|
_closeRemote = closeInfo.remote;
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
ss << "Error ! " << error.reason;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||||
|
{
|
||||||
|
ss << "Received pong message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
|
{
|
||||||
|
ss << "Received ping message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
ss << "Received message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "Invalid ix::WebSocketMessageType";
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_webSocket.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketClient::sendMessage(const std::string& text)
|
||||||
|
{
|
||||||
|
_webSocket.send(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startServer(ix::WebSocketServer& server,
|
||||||
|
uint16_t& receivedCloseCode,
|
||||||
|
std::string& receivedCloseReason,
|
||||||
|
bool& receivedCloseRemote,
|
||||||
|
std::mutex& mutexWrite)
|
||||||
|
{
|
||||||
|
// A dev/null server
|
||||||
|
server.setOnConnectionCallback(
|
||||||
|
[&server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
|
{
|
||||||
|
webSocket->setOnMessageCallback(
|
||||||
|
[webSocket, connectionState, &server, &receivedCloseCode, &receivedCloseReason, &receivedCloseRemote, &mutexWrite](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string& str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo& error,
|
||||||
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
|
{
|
||||||
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
Logger() << "New server connection";
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
|
Logger() << "Headers:";
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
Logger() << it.first << ": " << it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Server closed connection("
|
||||||
|
<< closeInfo.code
|
||||||
|
<< ","
|
||||||
|
<< closeInfo.reason
|
||||||
|
<< ")";
|
||||||
|
log(ss.str());
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
|
receivedCloseCode = closeInfo.code;
|
||||||
|
receivedCloseReason = std::string(closeInfo.reason);
|
||||||
|
receivedCloseRemote = closeInfo.remote;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
log(res.second);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_client_close_default", "[close]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that close code and reason was used and sent to server.")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
uint16_t serverReceivedCloseCode(0);
|
||||||
|
bool serverReceivedCloseRemote(false);
|
||||||
|
std::string serverReceivedCloseReason("");
|
||||||
|
std::mutex mutexWrite;
|
||||||
|
|
||||||
|
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
WebSocketClient webSocketClient(port);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
// ensure client close is the same as values given
|
||||||
|
REQUIRE(webSocketClient.getCloseCode() == 1000);
|
||||||
|
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
|
||||||
|
REQUIRE(webSocketClient.getCloseRemote() == false);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
|
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||||
|
REQUIRE(serverReceivedCloseCode == 1000);
|
||||||
|
REQUIRE(serverReceivedCloseReason == "Normal closure");
|
||||||
|
REQUIRE(serverReceivedCloseRemote == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_client_close_params_given", "[close]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that close code and reason was used and sent to server.")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
uint16_t serverReceivedCloseCode(0);
|
||||||
|
bool serverReceivedCloseRemote(false);
|
||||||
|
std::string serverReceivedCloseReason("");
|
||||||
|
std::mutex mutexWrite;
|
||||||
|
|
||||||
|
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
WebSocketClient webSocketClient(port);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
webSocketClient.stop(4000, "My reason");
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
// ensure client close is the same as values given
|
||||||
|
REQUIRE(webSocketClient.getCloseCode() == 4000);
|
||||||
|
REQUIRE(webSocketClient.getCloseReason() == "My reason");
|
||||||
|
REQUIRE(webSocketClient.getCloseRemote() == false);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
|
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||||
|
REQUIRE(serverReceivedCloseCode == 4000);
|
||||||
|
REQUIRE(serverReceivedCloseReason == "My reason");
|
||||||
|
REQUIRE(serverReceivedCloseRemote == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_server_close", "[close]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that close code and reason was read from server.")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
uint16_t serverReceivedCloseCode(0);
|
||||||
|
bool serverReceivedCloseRemote(false);
|
||||||
|
std::string serverReceivedCloseReason("");
|
||||||
|
std::mutex mutexWrite;
|
||||||
|
|
||||||
|
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
WebSocketClient webSocketClient(port);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
// ensure client close is the same as values given
|
||||||
|
REQUIRE(webSocketClient.getCloseCode() == 1000);
|
||||||
|
REQUIRE(webSocketClient.getCloseReason() == "Normal closure");
|
||||||
|
REQUIRE(webSocketClient.getCloseRemote() == true);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
|
// Here we read the code/reason received by the server, and ensure that remote is true
|
||||||
|
REQUIRE(serverReceivedCloseCode == 1000);
|
||||||
|
REQUIRE(serverReceivedCloseReason == "Normal closure");
|
||||||
|
REQUIRE(serverReceivedCloseRemote == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_server_close_immediatly", "[close]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that close code and reason was read from server.")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
|
uint16_t serverReceivedCloseCode(0);
|
||||||
|
bool serverReceivedCloseRemote(false);
|
||||||
|
std::string serverReceivedCloseReason("");
|
||||||
|
std::mutex mutexWrite;
|
||||||
|
|
||||||
|
REQUIRE(startServer(server, serverReceivedCloseCode, serverReceivedCloseReason, serverReceivedCloseRemote, mutexWrite));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
WebSocketClient webSocketClient(port);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
|
||||||
|
ix::msleep(500);
|
||||||
|
|
||||||
|
// ensure client close hasn't been called
|
||||||
|
REQUIRE(webSocketClient.getCloseCode() == 0);
|
||||||
|
REQUIRE(webSocketClient.getCloseReason() == "");
|
||||||
|
REQUIRE(webSocketClient.getCloseRemote() == false);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(mutexWrite);
|
||||||
|
|
||||||
|
// Here we ensure that the code/reason wasn't received by the server
|
||||||
|
REQUIRE(serverReceivedCloseCode == 0);
|
||||||
|
REQUIRE(serverReceivedCloseReason == "");
|
||||||
|
REQUIRE(serverReceivedCloseRemote == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
193
test/IXWebSocketMessageQTest.cpp
Normal file
193
test/IXWebSocketMessageQTest.cpp
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* IXWebSocketMessageQTest.cpp
|
||||||
|
* Author: Korchynskyi Dmytro
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include "catch.hpp"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool startServer(ix::WebSocketServer& server)
|
||||||
|
{
|
||||||
|
server.setOnConnectionCallback(
|
||||||
|
[&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
|
{
|
||||||
|
webSocket->setOnMessageCallback(
|
||||||
|
[connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
|
const std::string & str,
|
||||||
|
size_t wireSize,
|
||||||
|
const ix::WebSocketErrorInfo & error,
|
||||||
|
const ix::WebSocketOpenInfo & openInfo,
|
||||||
|
const ix::WebSocketCloseInfo & closeInfo)
|
||||||
|
{
|
||||||
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
Logger() << "New connection";
|
||||||
|
connectionState->computeId();
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
|
Logger() << "Headers:";
|
||||||
|
for (auto it : openInfo.headers)
|
||||||
|
{
|
||||||
|
Logger() << it.first << ": " << it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
Logger() << "Closed connection";
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
Logger() << "Message received: " << str;
|
||||||
|
|
||||||
|
for (auto&& client : server.getClients())
|
||||||
|
{
|
||||||
|
client->send(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
Logger() << res.second;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MsgQTestClient
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MsgQTestClient()
|
||||||
|
{
|
||||||
|
msgQ.bindWebsocket(&ws);
|
||||||
|
|
||||||
|
msgQ.setOnMessageCallback([this](WebSocketMessageType messageType,
|
||||||
|
const std::string & str,
|
||||||
|
size_t wireSize,
|
||||||
|
const WebSocketErrorInfo & error,
|
||||||
|
const WebSocketOpenInfo & openInfo,
|
||||||
|
const WebSocketCloseInfo & closeInfo)
|
||||||
|
{
|
||||||
|
REQUIRE(mainThreadId == std::this_thread::get_id());
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
if (messageType == WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
log("client connected");
|
||||||
|
sendNextMessage();
|
||||||
|
}
|
||||||
|
else if (messageType == WebSocketMessageType::Close)
|
||||||
|
{
|
||||||
|
log("client disconnected");
|
||||||
|
}
|
||||||
|
else if (messageType == WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
ss << "Error ! " << error.reason;
|
||||||
|
log(ss.str());
|
||||||
|
testDone = true;
|
||||||
|
}
|
||||||
|
else if (messageType == WebSocketMessageType::Pong)
|
||||||
|
{
|
||||||
|
ss << "Received pong message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == WebSocketMessageType::Ping)
|
||||||
|
{
|
||||||
|
ss << "Received ping message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
}
|
||||||
|
else if (messageType == WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
REQUIRE(str.compare("Hey dude!") == 0);
|
||||||
|
++receivedCount;
|
||||||
|
ss << "Received message " << str;
|
||||||
|
log(ss.str());
|
||||||
|
sendNextMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ss << "Invalid WebSocketMessageType";
|
||||||
|
log(ss.str());
|
||||||
|
testDone = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendNextMessage()
|
||||||
|
{
|
||||||
|
if (receivedCount >= 3)
|
||||||
|
{
|
||||||
|
testDone = true;
|
||||||
|
succeeded = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto info = ws.sendText("Hey dude!");
|
||||||
|
if (info.success)
|
||||||
|
log("sent message");
|
||||||
|
else
|
||||||
|
log("send failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(const std::string& url)
|
||||||
|
{
|
||||||
|
mainThreadId = std::this_thread::get_id();
|
||||||
|
testDone = false;
|
||||||
|
receivedCount = 0;
|
||||||
|
|
||||||
|
ws.setUrl(url);
|
||||||
|
ws.start();
|
||||||
|
|
||||||
|
while (!testDone)
|
||||||
|
{
|
||||||
|
msgQ.poll();
|
||||||
|
msleep(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSucceeded() const { return succeeded; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
WebSocket ws;
|
||||||
|
WebSocketMessageQueue msgQ;
|
||||||
|
bool testDone = false;
|
||||||
|
uint32_t receivedCount = 0;
|
||||||
|
std::thread::id mainThreadId;
|
||||||
|
bool succeeded = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_message_queue", "[websocket_message_q]")
|
||||||
|
{
|
||||||
|
SECTION("Send several messages")
|
||||||
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
WebSocketServer server(port);
|
||||||
|
REQUIRE(startServer(server));
|
||||||
|
|
||||||
|
MsgQTestClient testClient;
|
||||||
|
testClient.run("ws://127.0.0.1:" + std::to_string(port));
|
||||||
|
REQUIRE(testClient.isSucceeded());
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,7 +23,6 @@ namespace
|
|||||||
public:
|
public:
|
||||||
WebSocketClient(int port, bool useHeartBeatMethod);
|
WebSocketClient(int port, bool useHeartBeatMethod);
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
bool isReady() const;
|
bool isReady() const;
|
||||||
@ -44,7 +43,7 @@ namespace
|
|||||||
|
|
||||||
bool WebSocketClient::isReady() const
|
bool WebSocketClient::isReady() const
|
||||||
{
|
{
|
||||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketClient::stop()
|
void WebSocketClient::stop()
|
||||||
@ -57,7 +56,7 @@ namespace
|
|||||||
std::string url;
|
std::string url;
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "ws://localhost:"
|
ss << "ws://127.0.0.1:"
|
||||||
<< _port
|
<< _port
|
||||||
<< "/";
|
<< "/";
|
||||||
|
|
||||||
@ -69,9 +68,13 @@ namespace
|
|||||||
// The important bit for this test.
|
// The important bit for this test.
|
||||||
// Set a 1 second heartbeat with the setter method to test
|
// Set a 1 second heartbeat with the setter method to test
|
||||||
if (_useHeartBeatMethod)
|
if (_useHeartBeatMethod)
|
||||||
|
{
|
||||||
_webSocket.setHeartBeatPeriod(1);
|
_webSocket.setHeartBeatPeriod(1);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_webSocket.setPingInterval(1);
|
_webSocket.setPingInterval(1);
|
||||||
|
}
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
log(std::string("Connecting to url: ") + url);
|
log(std::string("Connecting to url: ") + url);
|
||||||
@ -85,33 +88,32 @@ namespace
|
|||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
log("client connected");
|
log("client connected");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("client disconnected");
|
log("client disconnected");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
ss << "Error ! " << error.reason;
|
ss << "Error ! " << error.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
ss << "Received pong message " << str;
|
ss << "Received pong message " << str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
ss << "Received ping message " << str;
|
ss << "Received ping message " << str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
ss << "Received message " << str;
|
// too many messages to log
|
||||||
log(ss.str());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -143,7 +145,7 @@ namespace
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
Logger() << "New server connection";
|
Logger() << "New server connection";
|
||||||
Logger() << "id: " << connectionState->getId();
|
Logger() << "id: " << connectionState->getId();
|
||||||
@ -154,15 +156,23 @@ namespace
|
|||||||
Logger() << it.first << ": " << it.second;
|
Logger() << it.first << ": " << it.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("Server closed connection");
|
log("Server closed connection");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
log("Server received a ping");
|
log("Server received a ping");
|
||||||
receivedPingMessages++;
|
receivedPingMessages++;
|
||||||
}
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
// to many messages to log
|
||||||
|
for(auto client: server.getClients())
|
||||||
|
{
|
||||||
|
client->sendText("reply");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -180,96 +190,6 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
|
||||||
{
|
|
||||||
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
|
||||||
{
|
|
||||||
ix::setupWebSocketTrafficTrackerCallback();
|
|
||||||
|
|
||||||
int port = getFreePort();
|
|
||||||
ix::WebSocketServer server(port);
|
|
||||||
std::atomic<int> serverReceivedPingMessages(0);
|
|
||||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
|
||||||
|
|
||||||
std::string session = ix::generateSessionId();
|
|
||||||
bool useSetHeartBeatPeriodMethod = true;
|
|
||||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
|
||||||
|
|
||||||
webSocketClient.start();
|
|
||||||
|
|
||||||
// Wait for all chat instance to be ready
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (webSocketClient.isReady()) break;
|
|
||||||
ix::msleep(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
|
||||||
|
|
||||||
ix::msleep(1900);
|
|
||||||
|
|
||||||
webSocketClient.stop();
|
|
||||||
|
|
||||||
|
|
||||||
// Here we test ping interval
|
|
||||||
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
|
|
||||||
REQUIRE(serverReceivedPingMessages == 1);
|
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
|
||||||
ix::msleep(500);
|
|
||||||
REQUIRE(server.getClients().size() == 0);
|
|
||||||
|
|
||||||
ix::reportWebSocketTraffic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
|
||||||
{
|
|
||||||
SECTION("Make sure that ping messages are sent, even if other messages are sent")
|
|
||||||
{
|
|
||||||
ix::setupWebSocketTrafficTrackerCallback();
|
|
||||||
|
|
||||||
int port = getFreePort();
|
|
||||||
ix::WebSocketServer server(port);
|
|
||||||
std::atomic<int> serverReceivedPingMessages(0);
|
|
||||||
REQUIRE(startServer(server, serverReceivedPingMessages));
|
|
||||||
|
|
||||||
std::string session = ix::generateSessionId();
|
|
||||||
bool useSetHeartBeatPeriodMethod = true;
|
|
||||||
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
|
||||||
|
|
||||||
webSocketClient.start();
|
|
||||||
|
|
||||||
// Wait for all chat instance to be ready
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (webSocketClient.isReady()) break;
|
|
||||||
ix::msleep(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
|
||||||
|
|
||||||
ix::msleep(900);
|
|
||||||
webSocketClient.sendMessage("hello world");
|
|
||||||
ix::msleep(900);
|
|
||||||
webSocketClient.sendMessage("hello world");
|
|
||||||
ix::msleep(1100);
|
|
||||||
|
|
||||||
webSocketClient.stop();
|
|
||||||
|
|
||||||
// Here we test ping interval
|
|
||||||
// client has sent data, but ping should have been sent no matter what
|
|
||||||
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
|
|
||||||
REQUIRE(serverReceivedPingMessages == 2);
|
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
|
||||||
ix::msleep(500);
|
|
||||||
REQUIRE(server.getClients().size() == 0);
|
|
||||||
|
|
||||||
ix::reportWebSocketTraffic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]")
|
TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]")
|
||||||
{
|
{
|
||||||
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
||||||
@ -305,8 +225,8 @@ TEST_CASE("Websocket_ping_no_data_sent_setPingInterval", "[setPingInterval]")
|
|||||||
// -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second
|
// -> expected ping messages == 2 as 2100 seconds, 1 ping sent every second
|
||||||
REQUIRE(serverReceivedPingMessages == 2);
|
REQUIRE(serverReceivedPingMessages == 2);
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(1000);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
ix::reportWebSocketTraffic();
|
ix::reportWebSocketTraffic();
|
||||||
@ -352,10 +272,212 @@ TEST_CASE("Websocket_ping_data_sent_setPingInterval", "[setPingInterval]")
|
|||||||
// -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second
|
// -> expected ping messages == 3 as 900+900+1300 = 3100 seconds, 1 ping sent every second
|
||||||
REQUIRE(serverReceivedPingMessages == 3);
|
REQUIRE(serverReceivedPingMessages == 3);
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(1000);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
ix::reportWebSocketTraffic();
|
ix::reportWebSocketTraffic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_ping_data_sent_setPingInterval_half_full", "[setPingInterval]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously during a given time")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
std::atomic<int> serverReceivedPingMessages(0);
|
||||||
|
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
|
||||||
|
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
// send continuously for 1100ms
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(900))
|
||||||
|
{
|
||||||
|
webSocketClient.sendMessage("message");
|
||||||
|
ix::msleep(1);
|
||||||
|
}
|
||||||
|
ix::msleep(150);
|
||||||
|
|
||||||
|
// Here we test ping interval
|
||||||
|
// client has sent data, but ping should have been sent no matter what
|
||||||
|
// -> expected ping messages == 1, as 900+150 = 1050ms, 1 ping sent every second
|
||||||
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
|
|
||||||
|
ix::msleep(100);
|
||||||
|
|
||||||
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_ping_data_sent_setPingInterval_full", "[setPingInterval]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that ping messages are sent, even if other messages are sent continuously for longer than ping interval")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
std::atomic<int> serverReceivedPingMessages(0);
|
||||||
|
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
bool useSetHeartBeatPeriodMethod = false; // so use setPingInterval
|
||||||
|
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
// send continuously for 1100ms
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
while(std::chrono::steady_clock::now() - now <= std::chrono::milliseconds(1100))
|
||||||
|
{
|
||||||
|
webSocketClient.sendMessage("message");
|
||||||
|
ix::msleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we test ping interval
|
||||||
|
// client has sent data, but ping should have been sent no matter what
|
||||||
|
// -> expected ping messages == 1, 1 ping sent every second
|
||||||
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
|
|
||||||
|
ix::msleep(100);
|
||||||
|
|
||||||
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using setHeartBeatPeriod
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_ping_no_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that ping messages are sent when no other data are sent.")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
std::atomic<int> serverReceivedPingMessages(0);
|
||||||
|
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
bool useSetHeartBeatPeriodMethod = true;
|
||||||
|
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
ix::msleep(1900);
|
||||||
|
|
||||||
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
|
||||||
|
// Here we test ping interval
|
||||||
|
// -> expected ping messages == 1 as 1900 seconds, 1 ping sent every second
|
||||||
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Websocket_ping_data_sent_setHeartBeatPeriod", "[setHeartBeatPeriod]")
|
||||||
|
{
|
||||||
|
SECTION("Make sure that ping messages are sent, even if other messages are sent")
|
||||||
|
{
|
||||||
|
ix::setupWebSocketTrafficTrackerCallback();
|
||||||
|
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::WebSocketServer server(port);
|
||||||
|
std::atomic<int> serverReceivedPingMessages(0);
|
||||||
|
REQUIRE(startServer(server, serverReceivedPingMessages));
|
||||||
|
|
||||||
|
std::string session = ix::generateSessionId();
|
||||||
|
bool useSetHeartBeatPeriodMethod = true;
|
||||||
|
WebSocketClient webSocketClient(port, useSetHeartBeatPeriodMethod);
|
||||||
|
|
||||||
|
webSocketClient.start();
|
||||||
|
|
||||||
|
// Wait for all chat instance to be ready
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (webSocketClient.isReady()) break;
|
||||||
|
ix::msleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
|
ix::msleep(900);
|
||||||
|
webSocketClient.sendMessage("hello world");
|
||||||
|
ix::msleep(900);
|
||||||
|
webSocketClient.sendMessage("hello world");
|
||||||
|
ix::msleep(1100);
|
||||||
|
|
||||||
|
webSocketClient.stop();
|
||||||
|
|
||||||
|
// without this sleep test fails on Windows
|
||||||
|
ix::msleep(100);
|
||||||
|
|
||||||
|
// Here we test ping interval
|
||||||
|
// client has sent data, but ping should have been sent no matter what
|
||||||
|
// -> expected ping messages == 2 as 900+900+1100 = 2900 seconds, 1 ping sent every second
|
||||||
|
REQUIRE(serverReceivedPingMessages == 2);
|
||||||
|
|
||||||
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
|
ix::msleep(1000);
|
||||||
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
|
ix::reportWebSocketTraffic();
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,7 +23,6 @@ namespace
|
|||||||
public:
|
public:
|
||||||
WebSocketClient(int port, int pingInterval, int pingTimeout);
|
WebSocketClient(int port, int pingInterval, int pingTimeout);
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
bool isReady() const;
|
bool isReady() const;
|
||||||
@ -53,12 +52,12 @@ namespace
|
|||||||
|
|
||||||
bool WebSocketClient::isReady() const
|
bool WebSocketClient::isReady() const
|
||||||
{
|
{
|
||||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebSocketClient::isClosed() const
|
bool WebSocketClient::isClosed() const
|
||||||
{
|
{
|
||||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Closed;
|
return _webSocket.getReadyState() == ix::ReadyState::Closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketClient::stop()
|
void WebSocketClient::stop()
|
||||||
@ -71,7 +70,7 @@ namespace
|
|||||||
std::string url;
|
std::string url;
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "ws://localhost:"
|
ss << "ws://127.0.0.1:"
|
||||||
<< _port
|
<< _port
|
||||||
<< "/";
|
<< "/";
|
||||||
|
|
||||||
@ -79,6 +78,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
_webSocket.setUrl(url);
|
_webSocket.setUrl(url);
|
||||||
|
_webSocket.disableAutomaticReconnection();
|
||||||
|
|
||||||
// The important bit for this test.
|
// The important bit for this test.
|
||||||
// Set a ping interval, and a ping timeout
|
// Set a ping interval, and a ping timeout
|
||||||
@ -97,13 +97,12 @@ namespace
|
|||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
log("client connected");
|
log("client connected");
|
||||||
|
|
||||||
_webSocket.disableAutomaticReconnection();
|
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("client disconnected");
|
log("client disconnected");
|
||||||
|
|
||||||
@ -112,29 +111,25 @@ namespace
|
|||||||
_closedDueToPingTimeout = true;
|
_closedDueToPingTimeout = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_webSocket.disableAutomaticReconnection();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
ss << "Error ! " << error.reason;
|
ss << "Error ! " << error.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
|
|
||||||
_webSocket.disableAutomaticReconnection();
|
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
_receivedPongMessages++;
|
_receivedPongMessages++;
|
||||||
|
|
||||||
ss << "Received pong message " << str;
|
ss << "Received pong message " << str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
ss << "Received ping message " << str;
|
ss << "Received ping message " << str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
ss << "Received message " << str;
|
ss << "Received message " << str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
@ -179,7 +174,7 @@ namespace
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
Logger() << "New server connection";
|
Logger() << "New server connection";
|
||||||
Logger() << "id: " << connectionState->getId();
|
Logger() << "id: " << connectionState->getId();
|
||||||
@ -190,11 +185,11 @@ namespace
|
|||||||
Logger() << it.first << ": " << it.second;
|
Logger() << it.first << ": " << it.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("Server closed connection");
|
log("Server closed connection");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
log("Server received a ping");
|
log("Server received a ping");
|
||||||
receivedPingMessages++;
|
receivedPingMessages++;
|
||||||
@ -264,8 +259,8 @@ TEST_CASE("Websocket_ping_timeout_not_checked", "[setPingTimeout]")
|
|||||||
|
|
||||||
webSocketClient.stop();
|
webSocketClient.stop();
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(1000);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
// Ensure client close was not by ping timeout
|
// Ensure client close was not by ping timeout
|
||||||
@ -303,7 +298,7 @@ TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]")
|
|||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
ix::msleep(1100);
|
ix::msleep(1200);
|
||||||
|
|
||||||
// Here we test ping timeout, no timeout
|
// Here we test ping timeout, no timeout
|
||||||
REQUIRE(serverReceivedPingMessages == 1);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
@ -317,8 +312,8 @@ TEST_CASE("Websocket_ping_no_timeout", "[setPingTimeout]")
|
|||||||
|
|
||||||
webSocketClient.stop();
|
webSocketClient.stop();
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(1000);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
// Ensure client close was not by ping timeout
|
// Ensure client close was not by ping timeout
|
||||||
@ -364,12 +359,13 @@ TEST_CASE("Websocket_no_ping_but_timeout", "[setPingTimeout]")
|
|||||||
REQUIRE(webSocketClient.isClosed() == false);
|
REQUIRE(webSocketClient.isClosed() == false);
|
||||||
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
||||||
|
|
||||||
ix::msleep(200);
|
ix::msleep(300);
|
||||||
|
|
||||||
// Here we test ping timeout, timeout
|
// Here we test ping timeout, timeout
|
||||||
REQUIRE(serverReceivedPingMessages == 0);
|
REQUIRE(serverReceivedPingMessages == 0);
|
||||||
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
||||||
// Ensure client close was not by ping timeout
|
// Ensure client close was by ping timeout
|
||||||
|
ix::msleep(1000);
|
||||||
REQUIRE(webSocketClient.isClosed() == true);
|
REQUIRE(webSocketClient.isClosed() == true);
|
||||||
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
|
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
|
||||||
|
|
||||||
@ -415,12 +411,13 @@ TEST_CASE("Websocket_ping_timeout", "[setPingTimeout]")
|
|||||||
REQUIRE(serverReceivedPingMessages == 1);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
||||||
|
|
||||||
ix::msleep(1000);
|
ix::msleep(1100);
|
||||||
|
|
||||||
// Here we test ping timeout, timeout
|
// Here we test ping timeout, timeout
|
||||||
REQUIRE(serverReceivedPingMessages == 1);
|
REQUIRE(serverReceivedPingMessages == 1);
|
||||||
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
REQUIRE(webSocketClient.getReceivedPongMessages() == 0);
|
||||||
// Ensure client close was not by ping timeout
|
// Ensure client close was by ping timeout
|
||||||
|
ix::msleep(1000);
|
||||||
REQUIRE(webSocketClient.isClosed() == true);
|
REQUIRE(webSocketClient.isClosed() == true);
|
||||||
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
|
REQUIRE(webSocketClient.closedDueToPingTimeout() == true);
|
||||||
|
|
||||||
@ -460,7 +457,7 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]")
|
|||||||
|
|
||||||
REQUIRE(server.getClients().size() == 1);
|
REQUIRE(server.getClients().size() == 1);
|
||||||
|
|
||||||
ix::msleep(5900);
|
ix::msleep(5800);
|
||||||
|
|
||||||
// Here we test ping timeout, no timeout yet (2 ping sent at 2s and 4s)
|
// Here we test ping timeout, no timeout yet (2 ping sent at 2s and 4s)
|
||||||
REQUIRE(serverReceivedPingMessages == 2);
|
REQUIRE(serverReceivedPingMessages == 2);
|
||||||
@ -470,7 +467,7 @@ TEST_CASE("Websocket_ping_long_timeout", "[setPingTimeout]")
|
|||||||
REQUIRE(webSocketClient.isClosed() == false);
|
REQUIRE(webSocketClient.isClosed() == false);
|
||||||
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
REQUIRE(webSocketClient.closedDueToPingTimeout() == false);
|
||||||
|
|
||||||
ix::msleep(200);
|
ix::msleep(600);
|
||||||
|
|
||||||
// Here we test ping timeout, timeout (at 6 seconds)
|
// Here we test ping timeout, timeout (at 6 seconds)
|
||||||
REQUIRE(serverReceivedPingMessages == 2);
|
REQUIRE(serverReceivedPingMessages == 2);
|
||||||
|
@ -50,7 +50,7 @@ namespace ix
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
connectionState->computeId();
|
connectionState->computeId();
|
||||||
@ -64,11 +64,11 @@ namespace ix
|
|||||||
|
|
||||||
connectionId = connectionState->getId();
|
connectionId = connectionState->getId();
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
Logger() << "Closed connection";
|
Logger() << "Closed connection";
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
@ -107,7 +107,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::string host("localhost");
|
std::string host("127.0.0.1");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -141,7 +141,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::string host("localhost");
|
std::string host("127.0.0.1");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -178,7 +178,7 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::string host("localhost");
|
std::string host("127.0.0.1");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -60,29 +60,35 @@ namespace
|
|||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: connected !");
|
log("TestConnectionDisconnection: connected !");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: disconnected !");
|
log("TestConnectionDisconnection: disconnected !");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: Error!");
|
ss << "TestConnectionDisconnection: Error! ";
|
||||||
|
ss << error.reason;
|
||||||
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: received message.!");
|
log("TestConnectionDisconnection: received message.!");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: received ping message.!");
|
log("TestConnectionDisconnection: received ping message.!");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_satori_chat: received pong message.!");
|
log("TestConnectionDisconnection: received pong message.!");
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocketMessageType::Fragment)
|
||||||
|
{
|
||||||
|
log("TestConnectionDisconnection: received fragment.!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -90,6 +96,12 @@ namespace
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_webSocket.enableAutomaticReconnection();
|
||||||
|
REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == true);
|
||||||
|
|
||||||
|
_webSocket.disableAutomaticReconnection();
|
||||||
|
REQUIRE(_webSocket.isAutomaticReconnectionEnabled() == false);
|
||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
_webSocket.start();
|
_webSocket.start();
|
||||||
}
|
}
|
||||||
@ -103,26 +115,52 @@ TEST_CASE("websocket_connections", "[websocket]")
|
|||||||
{
|
{
|
||||||
SECTION("Try to connect to invalid servers.")
|
SECTION("Try to connect to invalid servers.")
|
||||||
{
|
{
|
||||||
IXWebSocketTestConnectionDisconnection chatA;
|
IXWebSocketTestConnectionDisconnection test;
|
||||||
|
|
||||||
chatA.start(GOOGLE_URL);
|
test.start(GOOGLE_URL);
|
||||||
ix::msleep(1000);
|
ix::msleep(1000);
|
||||||
chatA.stop();
|
test.stop();
|
||||||
|
|
||||||
chatA.start(UNKNOWN_URL);
|
test.start(UNKNOWN_URL);
|
||||||
ix::msleep(1000);
|
ix::msleep(1000);
|
||||||
chatA.stop();
|
test.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Try to connect and disconnect with different timing.")
|
SECTION("Try to connect and disconnect with different timing, not enough time to succesfully connect")
|
||||||
{
|
{
|
||||||
IXWebSocketTestConnectionDisconnection chatA;
|
IXWebSocketTestConnectionDisconnection test;
|
||||||
|
log(std::string("50 Runs"));
|
||||||
|
|
||||||
for (int i = 0; i < 50; ++i)
|
for (int i = 0; i < 50; ++i)
|
||||||
{
|
{
|
||||||
log(std::string("Run: ") + std::to_string(i));
|
log(std::string("Run: ") + std::to_string(i));
|
||||||
chatA.start(WEBSOCKET_DOT_ORG_URL);
|
test.start(WEBSOCKET_DOT_ORG_URL);
|
||||||
|
|
||||||
|
log(std::string("Sleeping"));
|
||||||
ix::msleep(i);
|
ix::msleep(i);
|
||||||
chatA.stop();
|
|
||||||
|
log(std::string("Stopping"));
|
||||||
|
test.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test breaks on travis CI - Ubuntu Xenial + gcc + tsan
|
||||||
|
// We should fix this.
|
||||||
|
SECTION("Try to connect and disconnect with different timing, from not enough time to successfull connect")
|
||||||
|
{
|
||||||
|
IXWebSocketTestConnectionDisconnection test;
|
||||||
|
log(std::string("20 Runs"));
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
{
|
||||||
|
log(std::string("Run: ") + std::to_string(i));
|
||||||
|
test.start(WEBSOCKET_DOT_ORG_URL);
|
||||||
|
|
||||||
|
log(std::string("Sleeping"));
|
||||||
|
ix::msleep(i*50);
|
||||||
|
|
||||||
|
log(std::string("Stopping"));
|
||||||
|
test.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ namespace
|
|||||||
|
|
||||||
bool WebSocketChat::isReady() const
|
bool WebSocketChat::isReady() const
|
||||||
{
|
{
|
||||||
return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
|
return _webSocket.getReadyState() == ix::ReadyState::Open;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketChat::stop()
|
void WebSocketChat::stop()
|
||||||
@ -100,7 +100,7 @@ namespace
|
|||||||
std::string url;
|
std::string url;
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "ws://localhost:"
|
ss << "ws://127.0.0.1:"
|
||||||
<< _port
|
<< _port
|
||||||
<< "/"
|
<< "/"
|
||||||
<< _user;
|
<< _user;
|
||||||
@ -122,21 +122,21 @@ namespace
|
|||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
ss << "cmd_websocket_chat: user "
|
ss << "cmd_websocket_chat: user "
|
||||||
<< _user
|
<< _user
|
||||||
<< " Connected !";
|
<< " Connected !";
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
ss << "cmd_websocket_chat: user "
|
ss << "cmd_websocket_chat: user "
|
||||||
<< _user
|
<< _user
|
||||||
<< " disconnected !";
|
<< " disconnected !";
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
auto result = decodeMessage(str);
|
auto result = decodeMessage(str);
|
||||||
|
|
||||||
@ -159,20 +159,20 @@ namespace
|
|||||||
<< _user << " > ";
|
<< _user << " > ";
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
else if (messageType == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
ss << "cmd_websocket_chat: Error ! " << error.reason;
|
ss << "cmd_websocket_chat: Error ! " << error.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Ping)
|
else if (messageType == ix::WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_chat: received ping message");
|
log("cmd_websocket_chat: received ping message");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Pong)
|
else if (messageType == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_chat: received pong message");
|
log("cmd_websocket_chat: received pong message");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Fragment)
|
else if (messageType == ix::WebSocketMessageType::Fragment)
|
||||||
{
|
{
|
||||||
log("cmd_websocket_chat: received message fragment");
|
log("cmd_websocket_chat: received message fragment");
|
||||||
}
|
}
|
||||||
@ -228,7 +228,7 @@ namespace
|
|||||||
const ix::WebSocketOpenInfo& openInfo,
|
const ix::WebSocketOpenInfo& openInfo,
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
const ix::WebSocketCloseInfo& closeInfo)
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
Logger() << "id: " << connectionState->getId();
|
Logger() << "id: " << connectionState->getId();
|
||||||
@ -239,11 +239,11 @@ namespace
|
|||||||
Logger() << it.first << ": " << it.second;
|
Logger() << it.first << ": " << it.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
log("Closed connection");
|
log("Closed connection");
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
else if (messageType == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
@ -336,8 +336,8 @@ TEST_CASE("Websocket_chat", "[websocket_chat]")
|
|||||||
REQUIRE(chatA.getReceivedMessages()[1] == "from B2");
|
REQUIRE(chatA.getReceivedMessages()[1] == "from B2");
|
||||||
REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size());
|
REQUIRE(chatA.getReceivedMessages()[2].size() == bigMessage.size());
|
||||||
|
|
||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 1000ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(1000);
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
|
|
||||||
ix::reportWebSocketTraffic();
|
ix::reportWebSocketTraffic();
|
||||||
|
137
test/run.py
137
test/run.py
@ -1,10 +1,4 @@
|
|||||||
#!/usr/bin/env python2.7
|
#!/usr/bin/env python2.7
|
||||||
'''
|
|
||||||
Windows notes:
|
|
||||||
generator = '-G"NMake Makefiles"'
|
|
||||||
make = 'nmake'
|
|
||||||
testBinary ='ixwebsocket_unittest.exe'
|
|
||||||
'''
|
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
@ -28,9 +22,9 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
hasClick = False
|
hasClick = False
|
||||||
|
|
||||||
|
BUILD_TYPE = 'Debug'
|
||||||
DEFAULT_EXE = 'ixwebsocket_unittest'
|
XML_OUTPUT_FILE = 'ixwebsocket_unittest.xml'
|
||||||
|
TEST_EXE_PATH = None
|
||||||
|
|
||||||
class Command(object):
|
class Command(object):
|
||||||
"""Run system commands with timeout
|
"""Run system commands with timeout
|
||||||
@ -65,7 +59,7 @@ class Command(object):
|
|||||||
return True, self.process.returncode
|
return True, self.process.returncode
|
||||||
|
|
||||||
|
|
||||||
def runCommand(cmd, assertOnFailure=True, timeout=None):
|
def runCommand(cmd, abortOnFailure=True, timeout=None):
|
||||||
'''Small wrapper to run a command and make sure it succeed'''
|
'''Small wrapper to run a command and make sure it succeed'''
|
||||||
|
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
@ -73,16 +67,13 @@ def runCommand(cmd, assertOnFailure=True, timeout=None):
|
|||||||
|
|
||||||
print('\nRunning', cmd)
|
print('\nRunning', cmd)
|
||||||
command = Command(cmd)
|
command = Command(cmd)
|
||||||
timedout, ret = command.run(timeout)
|
succeed, ret = command.run(timeout)
|
||||||
|
|
||||||
if timedout:
|
if not succeed or ret != 0:
|
||||||
print('Unittest timed out')
|
msg = 'cmd {}\nfailed with error code {}'.format(cmd, ret)
|
||||||
|
|
||||||
msg = 'cmd {} failed with error code {}'.format(cmd, ret)
|
|
||||||
if ret != 0:
|
|
||||||
print(msg)
|
print(msg)
|
||||||
if assertOnFailure:
|
if abortOnFailure:
|
||||||
assert False
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
def runCMake(sanitizer, buildDir):
|
def runCMake(sanitizer, buildDir):
|
||||||
@ -91,12 +82,6 @@ def runCMake(sanitizer, buildDir):
|
|||||||
(remove build sub-folder).
|
(remove build sub-folder).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# CMake installed via Self Service ends up here.
|
|
||||||
cmake_executable = '/Applications/CMake.app/Contents/bin/cmake'
|
|
||||||
|
|
||||||
if not os.path.exists(cmake_executable):
|
|
||||||
cmake_executable = 'cmake'
|
|
||||||
|
|
||||||
sanitizersFlags = {
|
sanitizersFlags = {
|
||||||
'asan': '-DSANITIZE_ADDRESS=On',
|
'asan': '-DSANITIZE_ADDRESS=On',
|
||||||
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
'ubsan': '-DSANITIZE_UNDEFINED=On',
|
||||||
@ -110,19 +95,23 @@ def runCMake(sanitizer, buildDir):
|
|||||||
if not os.path.exists(cmakeExecutable):
|
if not os.path.exists(cmakeExecutable):
|
||||||
cmakeExecutable = 'cmake'
|
cmakeExecutable = 'cmake'
|
||||||
|
|
||||||
generator = '"Unix Makefiles"'
|
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
generator = '"NMake Makefiles"'
|
#generator = '"NMake Makefiles"'
|
||||||
|
#generator = '"Visual Studio 16 2019"'
|
||||||
|
generator = '"Visual Studio 15 2017"'
|
||||||
|
else:
|
||||||
|
generator = '"Unix Makefiles"'
|
||||||
|
|
||||||
fmt = '''
|
CMAKE_BUILD_TYPE = BUILD_TYPE
|
||||||
{cmakeExecutable} -H. \
|
|
||||||
|
fmt = '{cmakeExecutable} -H. \
|
||||||
{sanitizerFlag} \
|
{sanitizerFlag} \
|
||||||
-B{buildDir} \
|
-B"{buildDir}" \
|
||||||
-DCMAKE_BUILD_TYPE=Debug \
|
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
|
||||||
-DUSE_TLS=1 \
|
-DUSE_TLS=1 \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-G{generator}
|
-G{generator}'
|
||||||
'''
|
|
||||||
cmakeCmd = fmt.format(**locals())
|
cmakeCmd = fmt.format(**locals())
|
||||||
runCommand(cmakeCmd)
|
runCommand(cmakeCmd)
|
||||||
|
|
||||||
@ -133,10 +122,10 @@ def runTest(args, buildDir, xmlOutput, testRunName):
|
|||||||
if args is None:
|
if args is None:
|
||||||
args = ''
|
args = ''
|
||||||
|
|
||||||
fmt = '{buildDir}/{DEFAULT_EXE} -o {xmlOutput} -n "{testRunName}" -r junit "{args}"'
|
testCommand = '{} -o {} -n "{}" -r junit "{}"'.format(TEST_EXE_PATH, xmlOutput, testRunName, args)
|
||||||
testCommand = fmt.format(**locals())
|
|
||||||
runCommand(testCommand,
|
runCommand(testCommand,
|
||||||
assertOnFailure=False)
|
abortOnFailure=False)
|
||||||
|
|
||||||
|
|
||||||
def validateTestSuite(xmlOutput):
|
def validateTestSuite(xmlOutput):
|
||||||
@ -280,12 +269,12 @@ def executeJob(job):
|
|||||||
return job
|
return job
|
||||||
|
|
||||||
|
|
||||||
def executeJobs(jobs):
|
def executeJobs(jobs, cpuCount):
|
||||||
'''Execute a list of job concurrently on multiple CPU/cores'''
|
'''Execute a list of job concurrently on multiple CPU/cores'''
|
||||||
|
|
||||||
poolSize = multiprocessing.cpu_count()
|
print('Using {} cores to execute the unittest'.format(cpuCount))
|
||||||
|
|
||||||
pool = multiprocessing.Pool(poolSize)
|
pool = multiprocessing.Pool(cpuCount)
|
||||||
results = pool.map(executeJob, jobs)
|
results = pool.map(executeJob, jobs)
|
||||||
pool.close()
|
pool.close()
|
||||||
pool.join()
|
pool.join()
|
||||||
@ -296,8 +285,7 @@ def executeJobs(jobs):
|
|||||||
def computeAllTestNames(buildDir):
|
def computeAllTestNames(buildDir):
|
||||||
'''Compute all test case names, by executing the unittest in a custom mode'''
|
'''Compute all test case names, by executing the unittest in a custom mode'''
|
||||||
|
|
||||||
executable = os.path.join(buildDir, DEFAULT_EXE)
|
cmd = '"{}" --list-test-names-only'.format(TEST_EXE_PATH)
|
||||||
cmd = '"{}" --list-test-names-only'.format(executable)
|
|
||||||
names = os.popen(cmd).read().splitlines()
|
names = os.popen(cmd).read().splitlines()
|
||||||
names.sort() # Sort test names for execution determinism
|
names.sort() # Sort test names for execution determinism
|
||||||
return names
|
return names
|
||||||
@ -344,7 +332,7 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
|
|||||||
})
|
})
|
||||||
|
|
||||||
systemOut = ET.Element('system-out')
|
systemOut = ET.Element('system-out')
|
||||||
systemOut.text = result['output'].decode('utf-8')
|
systemOut.text = result['output'].decode('utf-8', 'ignore')
|
||||||
testCase.append(systemOut)
|
testCase.append(systemOut)
|
||||||
|
|
||||||
if not result['success']:
|
if not result['success']:
|
||||||
@ -358,23 +346,22 @@ def generateXmlOutput(results, xmlOutput, testRunName, runTime):
|
|||||||
f.write(content.encode('utf-8'))
|
f.write(content.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLDB):
|
def run(testName, buildDir, sanitizer, xmlOutput,
|
||||||
|
testRunName, buildOnly, useLLDB, cpuCount):
|
||||||
'''Main driver. Run cmake, compiles, execute and validate the testsuite.'''
|
'''Main driver. Run cmake, compiles, execute and validate the testsuite.'''
|
||||||
|
|
||||||
# gen build files with CMake
|
# gen build files with CMake
|
||||||
runCMake(sanitizer, buildDir)
|
runCMake(sanitizer, buildDir)
|
||||||
|
|
||||||
# build with make
|
if platform.system() == 'Linux':
|
||||||
makeCmd = 'make'
|
# build with make -j
|
||||||
jobs = '-j8'
|
runCommand('make -C {} -j 2'.format(buildDir))
|
||||||
|
elif platform.system() == 'Darwin':
|
||||||
if platform.system() == 'Windows':
|
# build with make
|
||||||
makeCmd = 'nmake'
|
runCommand('make -C {} -j 8'.format(buildDir))
|
||||||
|
else:
|
||||||
# nmake does not have a -j option
|
# build with cmake on recent
|
||||||
jobs = ''
|
runCommand('cmake --build --parallel {}'.format(buildDir))
|
||||||
|
|
||||||
runCommand('{} -C {} {}'.format(makeCmd, buildDir, jobs))
|
|
||||||
|
|
||||||
if buildOnly:
|
if buildOnly:
|
||||||
return
|
return
|
||||||
@ -409,12 +396,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# testName can contains spaces, so we enclose them in double quotes
|
# testName can contains spaces, so we enclose them in double quotes
|
||||||
executable = os.path.join(buildDir, DEFAULT_EXE)
|
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, TEST_EXE_PATH, testName, outputPath)
|
||||||
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
executable += '.exe'
|
|
||||||
|
|
||||||
cmd = '{} "{}" "{}" > "{}" 2>&1'.format(lldb, executable, testName, outputPath)
|
|
||||||
|
|
||||||
jobs.append({
|
jobs.append({
|
||||||
'name': testName,
|
'name': testName,
|
||||||
@ -424,7 +406,7 @@ def run(testName, buildDir, sanitizer, xmlOutput, testRunName, buildOnly, useLLD
|
|||||||
})
|
})
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
results = executeJobs(jobs)
|
results = executeJobs(jobs, cpuCount)
|
||||||
runTime = time.time() - start
|
runTime = time.time() - start
|
||||||
generateXmlOutput(results, xmlOutput, testRunName, runTime)
|
generateXmlOutput(results, xmlOutput, testRunName, runTime)
|
||||||
|
|
||||||
@ -454,8 +436,6 @@ def main():
|
|||||||
if not os.path.exists(buildDir):
|
if not os.path.exists(buildDir):
|
||||||
os.makedirs(buildDir)
|
os.makedirs(buildDir)
|
||||||
|
|
||||||
defaultOutput = DEFAULT_EXE + '.xml'
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
|
parser = argparse.ArgumentParser(description='Build and Run the engine unittest')
|
||||||
|
|
||||||
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
|
sanitizers = ['tsan', 'asan', 'ubsan', 'none']
|
||||||
@ -476,19 +456,41 @@ def main():
|
|||||||
help='Run the test through lldb.')
|
help='Run the test through lldb.')
|
||||||
parser.add_argument('--run_name', '-n',
|
parser.add_argument('--run_name', '-n',
|
||||||
help='Name of the test run.')
|
help='Name of the test run.')
|
||||||
|
parser.add_argument('--cpu_count', '-j', type=int, default=multiprocessing.cpu_count(),
|
||||||
|
help='Number of cpus to use for running the tests.')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Windows does not play nice with multiple files opened by different processes
|
||||||
|
# "The process cannot access the file because it is being used by another process"
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
args.cpu_count = 1
|
||||||
|
|
||||||
# Default sanitizer is tsan
|
# Default sanitizer is tsan
|
||||||
sanitizer = args.sanitizer
|
sanitizer = args.sanitizer
|
||||||
if args.sanitizer is None:
|
|
||||||
|
if args.no_sanitizer:
|
||||||
|
sanitizer = 'none'
|
||||||
|
elif args.sanitizer is None:
|
||||||
sanitizer = 'tsan'
|
sanitizer = 'tsan'
|
||||||
|
|
||||||
|
# Sanitizers display lots of strange errors on Linux on CI,
|
||||||
|
# which looks like false positives
|
||||||
|
if platform.system() != 'Darwin':
|
||||||
|
sanitizer = 'none'
|
||||||
|
|
||||||
defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
|
defaultRunName = 'ixengine_{}_{}'.format(platform.system(), sanitizer)
|
||||||
|
|
||||||
xmlOutput = args.output or defaultOutput
|
xmlOutput = args.output or XML_OUTPUT_FILE
|
||||||
testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
|
testRunName = args.run_name or os.getenv('IXENGINE_TEST_RUN_NAME') or defaultRunName
|
||||||
|
|
||||||
|
global TEST_EXE_PATH
|
||||||
|
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
TEST_EXE_PATH = os.path.join(buildDir, BUILD_TYPE, 'ixwebsocket_unittest.exe')
|
||||||
|
else:
|
||||||
|
TEST_EXE_PATH = os.path.join(buildDir, 'ixwebsocket_unittest')
|
||||||
|
|
||||||
if args.list:
|
if args.list:
|
||||||
# catch2 exit with a different error code when requesting the list of files
|
# catch2 exit with a different error code when requesting the list of files
|
||||||
try:
|
try:
|
||||||
@ -505,13 +507,8 @@ def main():
|
|||||||
print('LLDB is only supported on Apple at this point')
|
print('LLDB is only supported on Apple at this point')
|
||||||
args.lldb = False
|
args.lldb = False
|
||||||
|
|
||||||
# Sanitizers display lots of strange errors on Linux on CI,
|
|
||||||
# which looks like false positives
|
|
||||||
if platform.system() != 'Darwin':
|
|
||||||
sanitizer = 'none'
|
|
||||||
|
|
||||||
return run(args.test, buildDir, sanitizer, xmlOutput,
|
return run(args.test, buildDir, sanitizer, xmlOutput,
|
||||||
testRunName, args.build_only, args.lldb)
|
testRunName, args.build_only, args.lldb, args.cpu_count)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -7,8 +7,23 @@
|
|||||||
#define CATCH_CONFIG_RUNNER
|
#define CATCH_CONFIG_RUNNER
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixcore/utils/IXCoreLogger.h>
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
|
ix::initNetSystem();
|
||||||
|
|
||||||
|
ix::IXCoreLogger::LogFunc logFunc = [](const char* msg)
|
||||||
|
{
|
||||||
|
spdlog::info(msg);
|
||||||
|
};
|
||||||
|
ix::IXCoreLogger::setLogFunction(logFunc);
|
||||||
|
|
||||||
int result = Catch::Session().run(argc, argv);
|
int result = Catch::Session().run(argc, argv);
|
||||||
|
|
||||||
|
ix::uninitNetSystem();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
8
third_party/README.md
vendored
8
third_party/README.md
vendored
@ -1 +1,7 @@
|
|||||||
Except ZLIB on Windows (whose port is currently broken...) all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
# Note
|
||||||
|
|
||||||
|
Except *zlib* and *mbedtls* on Windows, all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
||||||
|
|
||||||
|
## MbedTLS
|
||||||
|
|
||||||
|
A small CMakeLists.txt fix had to be done so that the library can be included with "include_directory" in the top level CMakeLists.txt file. See https://github.com/ARMmbed/mbedtls/issues/2609
|
||||||
|
160
third_party/jsoncpp/json/json-forwards.h
vendored
160
third_party/jsoncpp/json/json-forwards.h
vendored
@ -7,32 +7,32 @@
|
|||||||
// //////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The JsonCpp library's source code, including accompanying documentation,
|
The JsonCpp library's source code, including accompanying documentation,
|
||||||
tests and demonstration applications, are licensed under the following
|
tests and demonstration applications, are licensed under the following
|
||||||
conditions...
|
conditions...
|
||||||
|
|
||||||
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
|
The author (Baptiste Lepilleur) explicitly disclaims copyright in all
|
||||||
jurisdictions which recognize such a disclaimer. In such jurisdictions,
|
jurisdictions which recognize such a disclaimer. In such jurisdictions,
|
||||||
this software is released into the Public Domain.
|
this software is released into the Public Domain.
|
||||||
|
|
||||||
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
|
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
|
||||||
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and
|
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is
|
||||||
The JsonCpp Authors, and is released under the terms of the MIT License (see below).
|
released under the terms of the MIT License (see below).
|
||||||
|
|
||||||
In jurisdictions which recognize Public Domain property, the user of this
|
In jurisdictions which recognize Public Domain property, the user of this
|
||||||
software may choose to accept it either as 1) Public Domain, 2) under the
|
software may choose to accept it either as 1) Public Domain, 2) under the
|
||||||
conditions of the MIT License (see below), or 3) under the terms of dual
|
conditions of the MIT License (see below), or 3) under the terms of dual
|
||||||
Public Domain/MIT License conditions described here, as they choose.
|
Public Domain/MIT License conditions described here, as they choose.
|
||||||
|
|
||||||
The MIT License is about as close to Public Domain as a license can get, and is
|
The MIT License is about as close to Public Domain as a license can get, and is
|
||||||
described in clear, concise terms at:
|
described in clear, concise terms at:
|
||||||
|
|
||||||
http://en.wikipedia.org/wiki/MIT_License
|
http://en.wikipedia.org/wiki/MIT_License
|
||||||
|
|
||||||
The full text of the MIT License follows:
|
The full text of the MIT License follows:
|
||||||
|
|
||||||
========================================================================
|
========================================================================
|
||||||
Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
Copyright (c) 2007-2010 Baptiste Lepilleur
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation
|
obtaining a copy of this software and associated documentation
|
||||||
@ -83,16 +83,13 @@ license you like.
|
|||||||
// Beginning of content of file: include/json/config.h
|
// Beginning of content of file: include/json/config.h
|
||||||
// //////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
// Copyright 2007-2010 Baptiste Lepilleur
|
||||||
// Distributed under MIT license, or public domain if desired and
|
// Distributed under MIT license, or public domain if desired and
|
||||||
// recognized in your jurisdiction.
|
// recognized in your jurisdiction.
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
||||||
|
|
||||||
#ifndef JSON_CONFIG_H_INCLUDED
|
#ifndef JSON_CONFIG_H_INCLUDED
|
||||||
#define JSON_CONFIG_H_INCLUDED
|
#define JSON_CONFIG_H_INCLUDED
|
||||||
#include <stddef.h>
|
|
||||||
#include <string> //typedef String
|
|
||||||
#include <stdint.h> //typedef int64_t, uint64_t
|
|
||||||
|
|
||||||
/// If defined, indicates that json library is embedded in CppTL library.
|
/// If defined, indicates that json library is embedded in CppTL library.
|
||||||
//# define JSON_IN_CPPTL 1
|
//# define JSON_IN_CPPTL 1
|
||||||
@ -125,12 +122,12 @@ license you like.
|
|||||||
#ifdef JSON_IN_CPPTL
|
#ifdef JSON_IN_CPPTL
|
||||||
#define JSON_API CPPTL_API
|
#define JSON_API CPPTL_API
|
||||||
#elif defined(JSON_DLL_BUILD)
|
#elif defined(JSON_DLL_BUILD)
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER)
|
||||||
#define JSON_API __declspec(dllexport)
|
#define JSON_API __declspec(dllexport)
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||||
#endif // if defined(_MSC_VER)
|
#endif // if defined(_MSC_VER)
|
||||||
#elif defined(JSON_DLL)
|
#elif defined(JSON_DLL)
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
#if defined(_MSC_VER)
|
||||||
#define JSON_API __declspec(dllimport)
|
#define JSON_API __declspec(dllimport)
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
||||||
#endif // if defined(_MSC_VER)
|
#endif // if defined(_MSC_VER)
|
||||||
@ -139,101 +136,49 @@ license you like.
|
|||||||
#define JSON_API
|
#define JSON_API
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_HAS_UNIQUE_PTR)
|
||||||
|
#if __cplusplus >= 201103L
|
||||||
|
#define JSON_HAS_UNIQUE_PTR (1)
|
||||||
|
#elif _MSC_VER >= 1600
|
||||||
|
#define JSON_HAS_UNIQUE_PTR (1)
|
||||||
|
#else
|
||||||
|
#define JSON_HAS_UNIQUE_PTR (0)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
||||||
// integer
|
// integer
|
||||||
// Storages, and 64 bits integer support is disabled.
|
// Storages, and 64 bits integer support is disabled.
|
||||||
// #define JSON_NO_INT64 1
|
// #define JSON_NO_INT64 1
|
||||||
|
|
||||||
#if defined(_MSC_VER) // MSVC
|
#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6
|
||||||
# if _MSC_VER <= 1200 // MSVC 6
|
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
||||||
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
// (no conversion from unsigned __int64).
|
||||||
// (no conversion from unsigned __int64).
|
#define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
||||||
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
// characters in the debug information)
|
||||||
// characters in the debug information)
|
// All projects I've ever seen with VS6 were using this globally (not bothering
|
||||||
// All projects I've ever seen with VS6 were using this globally (not bothering
|
// with pragma push/pop).
|
||||||
// with pragma push/pop).
|
#pragma warning(disable : 4786)
|
||||||
# pragma warning(disable : 4786)
|
#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6
|
||||||
# endif // MSVC 6
|
|
||||||
|
|
||||||
# if _MSC_VER >= 1500 // MSVC 2008
|
#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008
|
||||||
/// Indicates that the following function is deprecated.
|
/// Indicates that the following function is deprecated.
|
||||||
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
||||||
# endif
|
#elif defined(__clang__) && defined(__has_feature)
|
||||||
|
#if __has_feature(attribute_deprecated_with_message)
|
||||||
#endif // defined(_MSC_VER)
|
#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||||
|
|
||||||
// In c++11 the override keyword allows you to explicity define that a function
|
|
||||||
// is intended to override the base-class version. This makes the code more
|
|
||||||
// managable and fixes a set of common hard-to-find bugs.
|
|
||||||
#if __cplusplus >= 201103L
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER >= 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#else
|
|
||||||
# define JSONCPP_OVERRIDE
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#endif
|
#endif
|
||||||
|
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
||||||
|
#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
|
#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // MSVC >= 2010
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
#if __has_feature(cxx_rvalue_references)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // has_feature
|
|
||||||
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // GXX_EXPERIMENTAL
|
|
||||||
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#endif // not defined JSON_HAS_RVALUE_REFERENCES
|
|
||||||
|
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 0
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
# if __has_extension(attribute_deprecated_with_message)
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# endif
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
|
||||||
# endif // GNUC version
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#if !defined(JSONCPP_DEPRECATED)
|
#if !defined(JSONCPP_DEPRECATED)
|
||||||
#define JSONCPP_DEPRECATED(message)
|
#define JSONCPP_DEPRECATED(message)
|
||||||
#endif // if !defined(JSONCPP_DEPRECATED)
|
#endif // if !defined(JSONCPP_DEPRECATED)
|
||||||
|
|
||||||
#if __GNUC__ >= 6
|
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
# include "version.h"
|
|
||||||
|
|
||||||
# if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
# include "allocator.h" //typedef Allocator
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
namespace Json {
|
namespace Json {
|
||||||
typedef int Int;
|
typedef int Int;
|
||||||
typedef unsigned int UInt;
|
typedef unsigned int UInt;
|
||||||
@ -247,26 +192,13 @@ typedef unsigned int LargestUInt;
|
|||||||
typedef __int64 Int64;
|
typedef __int64 Int64;
|
||||||
typedef unsigned __int64 UInt64;
|
typedef unsigned __int64 UInt64;
|
||||||
#else // if defined(_MSC_VER) // Other platforms, use long long
|
#else // if defined(_MSC_VER) // Other platforms, use long long
|
||||||
typedef int64_t Int64;
|
typedef long long int Int64;
|
||||||
typedef uint64_t UInt64;
|
typedef unsigned long long int UInt64;
|
||||||
#endif // if defined(_MSC_VER)
|
#endif // if defined(_MSC_VER)
|
||||||
typedef Int64 LargestInt;
|
typedef Int64 LargestInt;
|
||||||
typedef UInt64 LargestUInt;
|
typedef UInt64 LargestUInt;
|
||||||
#define JSON_HAS_INT64
|
#define JSON_HAS_INT64
|
||||||
#endif // if defined(JSON_NO_INT64)
|
#endif // if defined(JSON_NO_INT64)
|
||||||
#if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#else
|
|
||||||
#define JSONCPP_STRING std::string
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::ostringstream
|
|
||||||
#define JSONCPP_OSTREAM std::ostream
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::istringstream
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#endif // if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
} // end namespace Json
|
} // end namespace Json
|
||||||
|
|
||||||
#endif // JSON_CONFIG_H_INCLUDED
|
#endif // JSON_CONFIG_H_INCLUDED
|
||||||
@ -284,7 +216,7 @@ typedef UInt64 LargestUInt;
|
|||||||
// Beginning of content of file: include/json/forwards.h
|
// Beginning of content of file: include/json/forwards.h
|
||||||
// //////////////////////////////////////////////////////////////////////
|
// //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
// Copyright 2007-2010 Baptiste Lepilleur
|
||||||
// Distributed under MIT license, or public domain if desired and
|
// Distributed under MIT license, or public domain if desired and
|
||||||
// recognized in your jurisdiction.
|
// recognized in your jurisdiction.
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
||||||
|
530
third_party/jsoncpp/json/json.h
vendored
530
third_party/jsoncpp/json/json.h
vendored
File diff suppressed because it is too large
Load Diff
1180
third_party/jsoncpp/jsoncpp.cpp
vendored
1180
third_party/jsoncpp/jsoncpp.cpp
vendored
File diff suppressed because it is too large
Load Diff
41
third_party/mbedtls/.github/issue_template.md
vendored
Normal file
41
third_party/mbedtls/.github/issue_template.md
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Note: This is just a template, so feel free to use/remove the unnecessary things
|
||||||
|
|
||||||
|
### Description
|
||||||
|
- Type: Bug | Enhancement\Feature Request | Question
|
||||||
|
- Priority: Blocker | Major | Minor
|
||||||
|
|
||||||
|
---------------------------------------------------------------
|
||||||
|
## Bug
|
||||||
|
|
||||||
|
**OS**
|
||||||
|
Mbed OS|linux|windows|
|
||||||
|
|
||||||
|
**mbed TLS build:**
|
||||||
|
Version: x.x.x or git commit id
|
||||||
|
OS version: x.x.x
|
||||||
|
Configuration: please attach config.h file where possible
|
||||||
|
Compiler and options (if you used a pre-built binary, please indicate how you obtained it):
|
||||||
|
Additional environment information:
|
||||||
|
|
||||||
|
**Peer device TLS stack and version**
|
||||||
|
OpenSSL|GnuTls|Chrome|NSS(Firefox)|SecureChannel (IIS/Internet Explorer/Edge)|Other
|
||||||
|
Version:
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
|
||||||
|
**Actual behavior**
|
||||||
|
|
||||||
|
**Steps to reproduce**
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
## Enhancement\Feature Request
|
||||||
|
|
||||||
|
**Justification - why does the library need this feature?**
|
||||||
|
|
||||||
|
**Suggested enhancement**
|
||||||
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
|
||||||
|
## Question
|
||||||
|
|
||||||
|
**Please first check for answers in the [Mbed TLS knowledge Base](https://tls.mbed.org/kb), and preferably file an issue in the [Mbed TLS support forum](https://forums.mbed.com/c/mbed-tls)**
|
39
third_party/mbedtls/.github/pull_request_template.md
vendored
Normal file
39
third_party/mbedtls/.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
Notes:
|
||||||
|
* Pull requests cannot be accepted until:
|
||||||
|
- The submitter has [accepted the online agreement here with a click through](https://developer.mbed.org/contributor_agreement/)
|
||||||
|
or for companies or those that do not wish to create an mbed account, a slightly different agreement can be found [here](https://www.mbed.com/en/about-mbed/contributor-license-agreements/)
|
||||||
|
- The PR follows the [mbed TLS coding standards](https://tls.mbed.org/kb/development/mbedtls-coding-standards)
|
||||||
|
* This is just a template, so feel free to use/remove the unnecessary things
|
||||||
|
## Description
|
||||||
|
A few sentences describing the overall goals of the pull request's commits.
|
||||||
|
|
||||||
|
|
||||||
|
## Status
|
||||||
|
**READY/IN DEVELOPMENT/HOLD**
|
||||||
|
|
||||||
|
## Requires Backporting
|
||||||
|
When there is a bug fix, it should be backported to all maintained and supported branches.
|
||||||
|
Changes do not have to be backported if:
|
||||||
|
- This PR is a new feature\enhancement
|
||||||
|
- This PR contains changes in the API. If this is true, and there is a need for the fix to be backported, the fix should be handled differently in the legacy branch
|
||||||
|
|
||||||
|
Yes | NO
|
||||||
|
Which branch?
|
||||||
|
|
||||||
|
## Migrations
|
||||||
|
If there is any API change, what's the incentive and logic for it.
|
||||||
|
|
||||||
|
YES | NO
|
||||||
|
|
||||||
|
## Additional comments
|
||||||
|
Any additional information that could be of interest
|
||||||
|
|
||||||
|
## Todos
|
||||||
|
- [ ] Tests
|
||||||
|
- [ ] Documentation
|
||||||
|
- [ ] Changelog updated
|
||||||
|
- [ ] Backported
|
||||||
|
|
||||||
|
|
||||||
|
## Steps to test or reproduce
|
||||||
|
Outline the steps to test or reproduce the PR here.
|
43
third_party/mbedtls/.gitignore
vendored
Normal file
43
third_party/mbedtls/.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Random seed file created by test scripts and sample programs
|
||||||
|
seedfile
|
||||||
|
|
||||||
|
# CMake build artifacts:
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CTestTestfile.cmake
|
||||||
|
cmake_install.cmake
|
||||||
|
Testing
|
||||||
|
# CMake generates *.dir/ folders for in-tree builds (used by MSVC projects), ignore all of those:
|
||||||
|
*.dir/
|
||||||
|
# MSVC files generated by CMake:
|
||||||
|
/*.sln
|
||||||
|
/*.vcxproj
|
||||||
|
/*.filters
|
||||||
|
|
||||||
|
# Test coverage build artifacts:
|
||||||
|
Coverage
|
||||||
|
*.gcno
|
||||||
|
*.gcda
|
||||||
|
|
||||||
|
# generated by scripts/memory.sh
|
||||||
|
massif-*
|
||||||
|
|
||||||
|
# MSVC build artifacts:
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.lib
|
||||||
|
|
||||||
|
# Python build artifacts:
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Generated documentation:
|
||||||
|
/apidoc
|
||||||
|
|
||||||
|
# Editor navigation files:
|
||||||
|
/GPATH
|
||||||
|
/GRTAGS
|
||||||
|
/GSYMS
|
||||||
|
/GTAGS
|
||||||
|
/TAGS
|
||||||
|
/tags
|
3
third_party/mbedtls/.globalrc
vendored
Normal file
3
third_party/mbedtls/.globalrc
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
default:\
|
||||||
|
:langmap=c\:.c.h.function:\
|
||||||
|
|
52
third_party/mbedtls/.pylintrc
vendored
Normal file
52
third_party/mbedtls/.pylintrc
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
[BASIC]
|
||||||
|
# We're ok with short funtion argument names.
|
||||||
|
# [invalid-name]
|
||||||
|
argument-rgx=[a-z_][a-z0-9_]*$
|
||||||
|
|
||||||
|
# Allow filter and map.
|
||||||
|
# [bad-builtin]
|
||||||
|
bad-functions=input
|
||||||
|
|
||||||
|
# We prefer docstrings, but we don't require them on all functions.
|
||||||
|
# Require them only on long functions (for some value of long).
|
||||||
|
# [missing-docstring]
|
||||||
|
docstring-min-length=10
|
||||||
|
|
||||||
|
# Allow longer methods than the default.
|
||||||
|
# [invalid-name]
|
||||||
|
method-rgx=[a-z_][a-z0-9_]{2,35}$
|
||||||
|
|
||||||
|
# Allow module names containing a dash (but no underscore or uppercase letter).
|
||||||
|
# They are whole programs, not meant to be included by another module.
|
||||||
|
# [invalid-name]
|
||||||
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|[a-z][-0-9a-z]+)$
|
||||||
|
|
||||||
|
# Some functions don't need docstrings.
|
||||||
|
# [missing-docstring]
|
||||||
|
no-docstring-rgx=(run_)?main$
|
||||||
|
|
||||||
|
# We're ok with short local or global variable names.
|
||||||
|
# [invalid-name]
|
||||||
|
variable-rgx=[a-z_][a-z0-9_]*$
|
||||||
|
|
||||||
|
[DESIGN]
|
||||||
|
# Allow more than the default 7 attributes.
|
||||||
|
# [too-many-instance-attributes]
|
||||||
|
max-attributes=15
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
# Allow longer modules than the default recommended maximum.
|
||||||
|
# [too-many-lines]
|
||||||
|
max-module-lines=2000
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=
|
||||||
|
|
||||||
|
[REPORTS]
|
||||||
|
# Don't diplay statistics. Just the facts.
|
||||||
|
reports=no
|
||||||
|
|
||||||
|
[VARIABLES]
|
||||||
|
# Allow unused variables if their name starts with an underscore.
|
||||||
|
# [unused-argument]
|
||||||
|
dummy-variables-rgx=_.*
|
48
third_party/mbedtls/.travis.yml
vendored
Normal file
48
third_party/mbedtls/.travis.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
language: c
|
||||||
|
compiler:
|
||||||
|
- clang
|
||||||
|
- gcc
|
||||||
|
sudo: false
|
||||||
|
cache: ccache
|
||||||
|
|
||||||
|
# blocklist
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- development-psa
|
||||||
|
- coverity_scan
|
||||||
|
|
||||||
|
script:
|
||||||
|
- tests/scripts/recursion.pl library/*.c
|
||||||
|
- tests/scripts/check-generated-files.sh
|
||||||
|
- tests/scripts/check-doxy-blocks.pl
|
||||||
|
- tests/scripts/check-names.sh
|
||||||
|
- tests/scripts/check-files.py
|
||||||
|
- tests/scripts/doxygen.sh
|
||||||
|
- cmake -D CMAKE_BUILD_TYPE:String="Check" .
|
||||||
|
- make
|
||||||
|
- make test
|
||||||
|
- programs/test/selftest
|
||||||
|
- OSSL_NO_DTLS=1 tests/compat.sh
|
||||||
|
- tests/ssl-opt.sh -e '\(DTLS\|SCSV\).*openssl'
|
||||||
|
- tests/scripts/test-ref-configs.pl
|
||||||
|
- tests/scripts/curves.pl
|
||||||
|
- tests/scripts/key-exchanges.pl
|
||||||
|
after_failure:
|
||||||
|
- tests/scripts/travis-log-failure.sh
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- SEED=1
|
||||||
|
- secure: "barHldniAfXyoWOD/vcO+E6/Xm4fmcaUoC9BeKW+LwsHqlDMLvugaJnmLXkSpkbYhVL61Hzf3bo0KPJn88AFc5Rkf8oYHPjH4adMnVXkf3B9ghHCgznqHsAH3choo6tnPxaFgOwOYmLGb382nQxfE5lUdvnM/W/psQjWt66A1+k="
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- doxygen
|
||||||
|
- graphviz
|
||||||
|
coverity_scan:
|
||||||
|
project:
|
||||||
|
name: "ARMmbed/mbedtls"
|
||||||
|
notification_email: simon.butcher@arm.com
|
||||||
|
build_command_prepend:
|
||||||
|
build_command: make
|
||||||
|
branch_pattern: coverity_scan
|
246
third_party/mbedtls/CMakeLists.txt
vendored
Normal file
246
third_party/mbedtls/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.6)
|
||||||
|
if(TEST_CPP)
|
||||||
|
project("mbed TLS" C CXX)
|
||||||
|
else()
|
||||||
|
project("mbed TLS" C)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(MBEDTLS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
option(USE_PKCS11_HELPER_LIBRARY "Build mbed TLS with the pkcs11-helper library." OFF)
|
||||||
|
option(ENABLE_ZLIB_SUPPORT "Build mbed TLS with zlib library." OFF)
|
||||||
|
|
||||||
|
option(ENABLE_PROGRAMS "Build mbed TLS programs." OFF)
|
||||||
|
|
||||||
|
option(UNSAFE_BUILD "Allow unsafe builds. These builds ARE NOT SECURE." OFF)
|
||||||
|
|
||||||
|
# export the submodule flag so that crypto knows it's being built as a submodule
|
||||||
|
set( USE_CRYPTO_SUBMODULE ON )
|
||||||
|
|
||||||
|
string(REGEX MATCH "Clang" CMAKE_COMPILER_IS_CLANG "${CMAKE_C_COMPILER_ID}")
|
||||||
|
string(REGEX MATCH "GNU" CMAKE_COMPILER_IS_GNU "${CMAKE_C_COMPILER_ID}")
|
||||||
|
string(REGEX MATCH "IAR" CMAKE_COMPILER_IS_IAR "${CMAKE_C_COMPILER_ID}")
|
||||||
|
string(REGEX MATCH "MSVC" CMAKE_COMPILER_IS_MSVC "${CMAKE_C_COMPILER_ID}")
|
||||||
|
|
||||||
|
# the test suites currently have compile errors with MSVC
|
||||||
|
if(CMAKE_COMPILER_IS_MSVC)
|
||||||
|
option(ENABLE_TESTING "Build mbed TLS tests." OFF)
|
||||||
|
else()
|
||||||
|
option(ENABLE_TESTING "Build mbed TLS tests." OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Warning string - created as a list for compatibility with CMake 2.8
|
||||||
|
set(WARNING_BORDER "*******************************************************\n")
|
||||||
|
set(NULL_ENTROPY_WARN_L1 "**** WARNING! MBEDTLS_TEST_NULL_ENTROPY defined!\n")
|
||||||
|
set(NULL_ENTROPY_WARN_L2 "**** THIS BUILD HAS NO DEFINED ENTROPY SOURCES\n")
|
||||||
|
set(NULL_ENTROPY_WARN_L3 "**** AND IS *NOT* SUITABLE FOR PRODUCTION USE\n")
|
||||||
|
|
||||||
|
set(NULL_ENTROPY_WARNING "${WARNING_BORDER}"
|
||||||
|
"${NULL_ENTROPY_WARN_L1}"
|
||||||
|
"${NULL_ENTROPY_WARN_L2}"
|
||||||
|
"${NULL_ENTROPY_WARN_L3}"
|
||||||
|
"${WARNING_BORDER}")
|
||||||
|
|
||||||
|
set(CTR_DRBG_128_BIT_KEY_WARN_L1 "**** WARNING! MBEDTLS_CTR_DRBG_USE_128_BIT_KEY defined!\n")
|
||||||
|
set(CTR_DRBG_128_BIT_KEY_WARN_L2 "**** Using 128-bit keys for CTR_DRBG limits the security of generated\n")
|
||||||
|
set(CTR_DRBG_128_BIT_KEY_WARN_L3 "**** keys and operations that use random values generated to 128-bit security\n")
|
||||||
|
|
||||||
|
set(CTR_DRBG_128_BIT_KEY_WARNING "${WARNING_BORDER}"
|
||||||
|
"${CTR_DRBG_128_BIT_KEY_WARN_L1}"
|
||||||
|
"${CTR_DRBG_128_BIT_KEY_WARN_L2}"
|
||||||
|
"${CTR_DRBG_128_BIT_KEY_WARN_L3}"
|
||||||
|
"${WARNING_BORDER}")
|
||||||
|
|
||||||
|
find_package(PythonInterp)
|
||||||
|
find_package(Perl)
|
||||||
|
if(PERL_FOUND)
|
||||||
|
|
||||||
|
# If 128-bit keys are configured for CTR_DRBG, display an appropriate warning
|
||||||
|
execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.pl -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_CTR_DRBG_USE_128_BIT_KEY
|
||||||
|
RESULT_VARIABLE result)
|
||||||
|
if(${result} EQUAL 0)
|
||||||
|
message(WARNING ${CTR_DRBG_128_BIT_KEY_WARNING})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# If NULL Entropy is configured, display an appropriate warning
|
||||||
|
execute_process(COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/config.pl -f ${CMAKE_CURRENT_SOURCE_DIR}/include/mbedtls/config.h get MBEDTLS_TEST_NULL_ENTROPY
|
||||||
|
RESULT_VARIABLE result)
|
||||||
|
if(${result} EQUAL 0)
|
||||||
|
message(WARNING ${NULL_ENTROPY_WARNING})
|
||||||
|
|
||||||
|
if(NOT UNSAFE_BUILD)
|
||||||
|
message(FATAL_ERROR "\
|
||||||
|
\n\
|
||||||
|
Warning! You have enabled MBEDTLS_TEST_NULL_ENTROPY. \
|
||||||
|
This option is not safe for production use and negates all security \
|
||||||
|
It is intended for development use only. \
|
||||||
|
\n\
|
||||||
|
To confirm you want to build with this option, re-run cmake with the \
|
||||||
|
option: \n\
|
||||||
|
cmake -DUNSAFE_BUILD=ON ")
|
||||||
|
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}
|
||||||
|
CACHE STRING "Choose the type of build: None Debug Release Coverage ASan ASanDbg MemSan MemSanDbg Check CheckFull"
|
||||||
|
FORCE)
|
||||||
|
|
||||||
|
# Create a symbolic link from ${base_name} in the binary directory
|
||||||
|
# to the corresponding path in the source directory.
|
||||||
|
function(link_to_source base_name)
|
||||||
|
# Get OS dependent path to use in `execute_process`
|
||||||
|
if (CMAKE_HOST_WIN32)
|
||||||
|
#mklink is an internal command of cmd.exe it can only work with \
|
||||||
|
string(REPLACE "/" "\\" link "${CMAKE_CURRENT_BINARY_DIR}/${base_name}")
|
||||||
|
string(REPLACE "/" "\\" target "${CMAKE_CURRENT_SOURCE_DIR}/${base_name}")
|
||||||
|
else()
|
||||||
|
set(link "${CMAKE_CURRENT_BINARY_DIR}/${base_name}")
|
||||||
|
set(target "${CMAKE_CURRENT_SOURCE_DIR}/${base_name}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT EXISTS ${link})
|
||||||
|
if (CMAKE_HOST_UNIX)
|
||||||
|
set(command ln -s ${target} ${link})
|
||||||
|
else()
|
||||||
|
if (IS_DIRECTORY ${target})
|
||||||
|
set(command cmd.exe /c mklink /j ${link} ${target})
|
||||||
|
else()
|
||||||
|
set(command cmd.exe /c mklink /h ${link} ${target})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
execute_process(COMMAND ${command}
|
||||||
|
RESULT_VARIABLE result
|
||||||
|
ERROR_VARIABLE output)
|
||||||
|
|
||||||
|
if (NOT ${result} EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Could not create symbolic link for: ${target} --> ${output}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction(link_to_source)
|
||||||
|
|
||||||
|
string(REGEX MATCH "Clang" CMAKE_COMPILER_IS_CLANG "${CMAKE_C_COMPILER_ID}")
|
||||||
|
|
||||||
|
if(CMAKE_COMPILER_IS_GNU)
|
||||||
|
# some warnings we want are not available with old GCC versions
|
||||||
|
# note: starting with CMake 2.8 we could use CMAKE_C_COMPILER_VERSION
|
||||||
|
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion
|
||||||
|
OUTPUT_VARIABLE GCC_VERSION)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -W -Wdeclaration-after-statement -Wwrite-strings")
|
||||||
|
if (GCC_VERSION VERSION_GREATER 4.5 OR GCC_VERSION VERSION_EQUAL 4.5)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wlogical-op")
|
||||||
|
endif()
|
||||||
|
if (GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "-O2")
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "-O0 -g3")
|
||||||
|
set(CMAKE_C_FLAGS_COVERAGE "-O0 -g3 --coverage")
|
||||||
|
set(CMAKE_C_FLAGS_ASAN "-Werror -fsanitize=address -fno-common -O3")
|
||||||
|
set(CMAKE_C_FLAGS_ASANDBG "-Werror -fsanitize=address -fno-common -O1 -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls ")
|
||||||
|
set(CMAKE_C_FLAGS_CHECK "-Werror -Os")
|
||||||
|
set(CMAKE_C_FLAGS_CHECKFULL "${CMAKE_C_FLAGS_CHECK} -Wcast-qual")
|
||||||
|
endif(CMAKE_COMPILER_IS_GNU)
|
||||||
|
|
||||||
|
if(CMAKE_COMPILER_IS_CLANG)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -W -Wdeclaration-after-statement -Wwrite-strings -Wpointer-arith -Wimplicit-fallthrough -Wshadow")
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "-O2")
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "-O0 -g3")
|
||||||
|
set(CMAKE_C_FLAGS_COVERAGE "-O0 -g3 --coverage")
|
||||||
|
set(CMAKE_C_FLAGS_ASAN "-Werror -fsanitize=address -fno-common -fsanitize=undefined -fno-sanitize-recover=all -O3")
|
||||||
|
set(CMAKE_C_FLAGS_ASANDBG "-Werror -fsanitize=address -fno-common -fsanitize=undefined -fno-sanitize-recover=all -O1 -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls ")
|
||||||
|
set(CMAKE_C_FLAGS_MEMSAN "-Werror -fsanitize=memory -O3")
|
||||||
|
set(CMAKE_C_FLAGS_MEMSANDBG "-Werror -fsanitize=memory -O1 -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2")
|
||||||
|
set(CMAKE_C_FLAGS_CHECK "-Werror -Os")
|
||||||
|
endif(CMAKE_COMPILER_IS_CLANG)
|
||||||
|
|
||||||
|
if(CMAKE_COMPILER_IS_IAR)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --warn_about_c_style_casts --warnings_are_errors -Ohz")
|
||||||
|
endif(CMAKE_COMPILER_IS_IAR)
|
||||||
|
|
||||||
|
if(CMAKE_COMPILER_IS_MSVC)
|
||||||
|
# Strictest warnings, and treat as errors
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W3")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX")
|
||||||
|
endif(CMAKE_COMPILER_IS_MSVC)
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
|
||||||
|
if(CMAKE_COMPILER_IS_GNU OR CMAKE_COMPILER_IS_CLANG)
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS "--coverage")
|
||||||
|
endif(CMAKE_COMPILER_IS_GNU OR CMAKE_COMPILER_IS_CLANG)
|
||||||
|
endif(CMAKE_BUILD_TYPE STREQUAL "Coverage")
|
||||||
|
|
||||||
|
if(LIB_INSTALL_DIR)
|
||||||
|
else()
|
||||||
|
set(LIB_INSTALL_DIR lib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_ZLIB_SUPPORT)
|
||||||
|
find_package(ZLIB)
|
||||||
|
|
||||||
|
if(ZLIB_FOUND)
|
||||||
|
include_directories(${ZLIB_INCLUDE_DIR})
|
||||||
|
endif(ZLIB_FOUND)
|
||||||
|
endif(ENABLE_ZLIB_SUPPORT)
|
||||||
|
|
||||||
|
add_subdirectory(library)
|
||||||
|
add_subdirectory(include)
|
||||||
|
add_subdirectory(crypto/library)
|
||||||
|
add_subdirectory(crypto/include)
|
||||||
|
|
||||||
|
if(ENABLE_PROGRAMS)
|
||||||
|
add_subdirectory(programs)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ADD_CUSTOM_TARGET(apidoc
|
||||||
|
COMMAND doxygen mbedtls.doxyfile
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doxygen)
|
||||||
|
|
||||||
|
if(ENABLE_TESTING)
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_subdirectory(tests)
|
||||||
|
add_subdirectory(crypto/tests)
|
||||||
|
|
||||||
|
# additional convenience targets for Unix only
|
||||||
|
if(UNIX)
|
||||||
|
|
||||||
|
ADD_CUSTOM_TARGET(covtest
|
||||||
|
COMMAND make test
|
||||||
|
COMMAND programs/test/selftest
|
||||||
|
COMMAND tests/compat.sh
|
||||||
|
COMMAND tests/ssl-opt.sh
|
||||||
|
)
|
||||||
|
|
||||||
|
ADD_CUSTOM_TARGET(lcov
|
||||||
|
COMMAND rm -rf Coverage
|
||||||
|
COMMAND lcov --capture --initial --directory library/CMakeFiles/mbedtls.dir -o files.info
|
||||||
|
COMMAND lcov --capture --directory library/CMakeFiles/mbedtls.dir -o tests.info
|
||||||
|
COMMAND lcov --add-tracefile files.info --add-tracefile tests.info -o all.info
|
||||||
|
COMMAND lcov --remove all.info -o final.info '*.h'
|
||||||
|
COMMAND gendesc tests/Descriptions.txt -o descriptions
|
||||||
|
COMMAND genhtml --title "mbed TLS" --description-file descriptions --keep-descriptions --legend --no-branch-coverage -o Coverage final.info
|
||||||
|
COMMAND rm -f files.info tests.info all.info final.info descriptions
|
||||||
|
)
|
||||||
|
|
||||||
|
ADD_CUSTOM_TARGET(memcheck
|
||||||
|
COMMAND sed -i.bak s+/usr/bin/valgrind+`which valgrind`+ DartConfiguration.tcl
|
||||||
|
COMMAND ctest -O memcheck.log -D ExperimentalMemCheck
|
||||||
|
COMMAND tail -n1 memcheck.log | grep 'Memory checking results:' > /dev/null
|
||||||
|
COMMAND rm -f memcheck.log
|
||||||
|
COMMAND mv DartConfiguration.tcl.bak DartConfiguration.tcl
|
||||||
|
)
|
||||||
|
endif(UNIX)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Make scripts needed for testing available in an out-of-source build.
|
||||||
|
if (NOT ${CMAKE_CURRENT_BINARY_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
link_to_source(scripts)
|
||||||
|
# Copy (don't link) DartConfiguration.tcl, needed for memcheck, to
|
||||||
|
# keep things simple with the sed commands in the memcheck target.
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/DartConfiguration.tcl
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl COPYONLY)
|
||||||
|
endif()
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user