Compare commits
177 Commits
Author | SHA1 | Date | |
---|---|---|---|
0520329350 | |||
ba88a05b74 | |||
72f8e76369 | |||
0389b0b1a3 | |||
ac0c218455 | |||
299dc0452e | |||
f4af84dc06 | |||
6522bc06ba | |||
50bea7dffa | |||
c4e9abfe80 | |||
a805270d02 | |||
e13b57c73b | |||
5be84926ef | |||
33e7271b85 | |||
d72e5e70f6 | |||
e2c5f751bd | |||
351b86e266 | |||
d0cbff4f4e | |||
cbfc9b9f94 | |||
ca816d801f | |||
2f354d31eb | |||
2c6c1edd37 | |||
9799e7e84b | |||
81be970679 | |||
52221906f6 | |||
3e786fe23a | |||
de24aac7d5 | |||
cd4b0ccf6f | |||
4e1888ac19 | |||
237ede56aa | |||
ba3b1c1a0f | |||
c60c606e0f | |||
5897de6bd9 | |||
cca304fc18 | |||
432624df0d | |||
9c047fac72 | |||
615df9cef0 | |||
094d16304d | |||
c2377e8747 | |||
a63c0d6e78 | |||
aa3bface30 | |||
5d75a3aac3 | |||
c75959fcb5 | |||
bfe0212250 | |||
af1a54f2ad | |||
8a385449ce | |||
396a6985ae | |||
92db53c470 | |||
49865fed0a | |||
f3f71314d9 | |||
2d593dd63b | |||
779b1e6077 | |||
6054dd4b49 | |||
69aa3839bf | |||
6808a0b500 | |||
a7df6120d5 | |||
d20ab19fa9 | |||
b946cda65e | |||
2cfadd93b5 | |||
8607dc1a4a | |||
521286ae88 | |||
d122e12a1f | |||
c26c3b6892 | |||
ed75d14c86 | |||
0841fcec44 | |||
4e717abdb8 | |||
451d2b4253 | |||
10b4ee353d | |||
07822625b7 | |||
c943e72c7b | |||
ebb31b4e87 | |||
6904dd3f4c | |||
0e73fe51e9 | |||
7e67598360 | |||
91a95dc5f6 | |||
c40033b6d9 | |||
adf83f3255 | |||
8fda7cb131 | |||
0e9cf863cf | |||
279f6fbfed | |||
f8e7b34bf0 | |||
d2cf616737 | |||
11a3b64657 | |||
bab2295fc3 | |||
adcbf0d208 | |||
19150115bb | |||
d93bd9b58b | |||
13801dff8a | |||
de87fa34dc | |||
d60f5de231 | |||
22b4e6a8fb | |||
1ed39677ce | |||
562d7484e4 | |||
58d6e4bb26 | |||
0539d2df2e | |||
e023dd9c36 | |||
a95cf727b1 | |||
b96a65031e | |||
2a838d01a7 | |||
b0afd36cec | |||
77863c0e8b | |||
2229159bd2 | |||
89d2606b1d | |||
a7a41c51d9 | |||
4de7cb191b | |||
b3784b4c60 | |||
816c53e3a3 | |||
28c4b83ab9 | |||
3a91894d62 | |||
3c8cd6289b | |||
06297ac756 | |||
1b6584ccba | |||
0499a80c55 | |||
f18980d010 | |||
2fb0ebb05b | |||
7495c9ebb8 | |||
b26d463bad | |||
f8a581aa69 | |||
01f3340718 | |||
a9b8b6decd | |||
ea83327261 | |||
39c0fb0072 | |||
733b414b3b | |||
c32067013a | |||
fbf80f4ab1 | |||
8f8385f8f8 | |||
122118196b | |||
6f2fe49a7b | |||
b667c0ad40 | |||
283cf83d47 | |||
ab1b5cd665 | |||
dbf6d00249 | |||
d0963f4af0 | |||
dd01f734c6 | |||
1769199d32 | |||
8821183aea | |||
a7cf151639 | |||
f7a12f52f8 | |||
1be3b8f4b1 | |||
0b844d8361 | |||
57086e28d8 | |||
a55d4cdb76 | |||
40a45717db | |||
e853d9ac60 | |||
4ec0d9b113 | |||
0fde169aa4 | |||
c09015e870 | |||
7bfa6e8478 | |||
983df2d8f9 | |||
6beba16ca7 | |||
48cefe5525 | |||
ae3856c10f | |||
260a94d3b0 | |||
88c6d6c4cb | |||
d5a4931c92 | |||
11f4e90bc6 | |||
2ce65e7a77 | |||
e81c2c3e5c | |||
e40dda7549 | |||
d959d73261 | |||
07b7e37a92 | |||
eb7888347a | |||
d8664f4988 | |||
5e94791b13 | |||
3e3f7171fc | |||
308fda0b37 | |||
66ed7577b1 | |||
cae23c764f | |||
f25b2af6eb | |||
508d372df1 | |||
12c3275c36 | |||
98189c23dc | |||
ec55b4a82a | |||
5d58982f77 | |||
57665ca825 | |||
deaa753657 | |||
7c7c877621 |
@ -11,7 +11,7 @@ AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
@ -42,5 +42,6 @@ NamespaceIndentation: All
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Left
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAfterCStyleCast: true
|
||||
Standard: Cpp11
|
||||
UseTab: Never
|
||||
|
50
.github/workflows/ccpp.yml
vendored
Normal file
50
.github/workflows/ccpp.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make test
|
||||
run: make test
|
||||
|
||||
mac:
|
||||
runs-on: macOS-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: install redis
|
||||
run: brew install redis
|
||||
|
||||
- name: start redis server
|
||||
run: brew services start redis
|
||||
|
||||
- name: make test
|
||||
run: make test
|
||||
|
||||
# # Windows does not work yet, I'm stuck at getting CMake to run + finding vcpkg
|
||||
# win:
|
||||
# runs-on: windows-2016
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
#
|
||||
# - name: run cmake
|
||||
# run: |
|
||||
# "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
# mkdir build
|
||||
# cd build
|
||||
# cmake -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%\scripts\buildsystems\vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" ..
|
||||
# - name: build
|
||||
# run: |
|
||||
# "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
# cd build
|
||||
# nmake
|
||||
# - name: run tests
|
||||
# run:
|
||||
# cd test
|
||||
# ..\build\test\ixwebsocket_unittest.exe
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
build
|
||||
*.pyc
|
||||
venv
|
||||
ixsnake/ixsnake/.certs/
|
||||
site/
|
||||
ws/.certs/
|
||||
ws/.srl
|
||||
|
@ -5,8 +5,3 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/pocc/pre-commit-hooks
|
||||
rev: ''
|
||||
hooks:
|
||||
- id: clang-format
|
||||
|
25
.travis.yml
25
.travis.yml
@ -6,23 +6,26 @@ language: bash
|
||||
matrix:
|
||||
include:
|
||||
# macOS
|
||||
- os: osx
|
||||
env:
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
compiler: clang
|
||||
script:
|
||||
- brew install mbedtls
|
||||
- python test/run.py
|
||||
- make ws
|
||||
# - os: osx
|
||||
# env:
|
||||
# - HOMEBREW_NO_AUTO_UPDATE=1
|
||||
# compiler: clang
|
||||
# script:
|
||||
# - brew install redis
|
||||
# - brew services start redis
|
||||
# - brew install mbedtls
|
||||
# - python test/run.py
|
||||
# - make ws
|
||||
|
||||
# Linux
|
||||
Linux
|
||||
- os: linux
|
||||
dist: bionic
|
||||
before_install:
|
||||
- sudo apt-get install -y libmbedtls-dev
|
||||
- sudo apt-get install -y redis-server
|
||||
script:
|
||||
- python test/run.py
|
||||
- make ws
|
||||
- python test/run.py
|
||||
# - make ws
|
||||
env:
|
||||
- CC=gcc
|
||||
- CXX=g++
|
||||
|
19
CMake/FindJsonCpp.cmake
Normal file
19
CMake/FindJsonCpp.cmake
Normal file
@ -0,0 +1,19 @@
|
||||
# Find package structure taken from libcurl
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
||||
find_library(JSONCPP_LIBRARY jsoncpp)
|
||||
|
||||
find_package_handle_standard_args(JSONCPP
|
||||
FOUND_VAR
|
||||
JSONCPP_FOUND
|
||||
REQUIRED_VARS
|
||||
JSONCPP_LIBRARY
|
||||
JSONCPP_INCLUDE_DIRS
|
||||
FAIL_MESSAGE
|
||||
"Could NOT find jsoncpp"
|
||||
)
|
||||
|
||||
set(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIRS})
|
||||
set(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY})
|
@ -12,8 +12,7 @@ set (CMAKE_CXX_STANDARD 14)
|
||||
set (CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# -Wshorten-64-to-32 does not work with clang
|
||||
if (NOT WIN32)
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||
endif()
|
||||
|
||||
@ -36,6 +35,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
ixwebsocket/IXSocketConnect.cpp
|
||||
ixwebsocket/IXSocketFactory.cpp
|
||||
ixwebsocket/IXSocketServer.cpp
|
||||
ixwebsocket/IXSocketTLSOptions.cpp
|
||||
ixwebsocket/IXUrlParser.cpp
|
||||
ixwebsocket/IXUserAgent.cpp
|
||||
ixwebsocket/IXWebSocket.cpp
|
||||
@ -68,7 +68,9 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXSocketConnect.h
|
||||
ixwebsocket/IXSocketFactory.h
|
||||
ixwebsocket/IXSocketServer.h
|
||||
ixwebsocket/IXSocketTLSOptions.h
|
||||
ixwebsocket/IXUrlParser.h
|
||||
ixwebsocket/IXUtf8Validator.h
|
||||
ixwebsocket/IXUserAgent.h
|
||||
ixwebsocket/IXWebSocket.h
|
||||
ixwebsocket/IXWebSocketCloseConstants.h
|
||||
@ -76,6 +78,7 @@ set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXWebSocketErrorInfo.h
|
||||
ixwebsocket/IXWebSocketHandshake.h
|
||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||
ixwebsocket/IXWebSocketInitResult.h
|
||||
ixwebsocket/IXWebSocketMessage.h
|
||||
ixwebsocket/IXWebSocketMessageQueue.h
|
||||
ixwebsocket/IXWebSocketMessageType.h
|
||||
@ -102,6 +105,8 @@ if (APPLE)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
||||
elseif (WIN32)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
||||
elseif (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/freebsd/IXSetThreadName_freebsd.cpp)
|
||||
else()
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
||||
@ -112,22 +117,17 @@ if (WIN32)
|
||||
set(USE_MBED_TLS TRUE)
|
||||
endif()
|
||||
|
||||
set(USE_OPEN_SSL FALSE)
|
||||
if (USE_TLS)
|
||||
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
||||
|
||||
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)
|
||||
elseif (APPLE AND NOT USE_OPEN_SSL)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||
elseif (WIN32)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
||||
else()
|
||||
add_definitions(-DIXWEBSOCKET_USE_OPEN_SSL)
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||
@ -139,6 +139,19 @@ add_library( ixwebsocket STATIC
|
||||
${IXWEBSOCKET_HEADERS}
|
||||
)
|
||||
|
||||
if (USE_TLS)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_TLS)
|
||||
if (USE_MBED_TLS)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_MBED_TLS)
|
||||
elseif (USE_OPEN_SSL)
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
|
||||
elseif (APPLE)
|
||||
elseif (WIN32)
|
||||
else()
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_OPEN_SSL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
|
||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||
endif()
|
||||
@ -154,6 +167,13 @@ if (UNIX)
|
||||
endif()
|
||||
|
||||
if (USE_TLS AND USE_OPEN_SSL)
|
||||
|
||||
# Help finding Homebrew's OpenSSL on macOS
|
||||
if (APPLE)
|
||||
set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /usr/local/opt/openssl/lib)
|
||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||
endif()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
@ -162,6 +182,7 @@ if (USE_TLS AND USE_OPEN_SSL)
|
||||
endif()
|
||||
|
||||
if (USE_TLS AND USE_MBED_TLS)
|
||||
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
|
||||
if (USE_VENDORED_THIRD_PARTY)
|
||||
set (ENABLE_PROGRAMS OFF)
|
||||
add_subdirectory(third_party/mbedtls)
|
||||
@ -170,7 +191,7 @@ if (USE_TLS AND USE_MBED_TLS)
|
||||
target_link_libraries(ixwebsocket mbedtls)
|
||||
else()
|
||||
find_package(MbedTLS REQUIRED)
|
||||
include_directories(${MBEDTLS_INCLUDE_DIRS})
|
||||
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
@ -203,6 +224,19 @@ install(TARGETS ixwebsocket
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
||||
)
|
||||
|
||||
if (USE_WS)
|
||||
add_subdirectory(ws)
|
||||
if (USE_WS OR USE_TEST)
|
||||
add_subdirectory(ixcore)
|
||||
add_subdirectory(ixcrypto)
|
||||
add_subdirectory(ixcobra)
|
||||
add_subdirectory(ixsnake)
|
||||
add_subdirectory(ixsentry)
|
||||
|
||||
add_subdirectory(third_party/spdlog spdlog)
|
||||
|
||||
if (USE_WS)
|
||||
add_subdirectory(ws)
|
||||
endif()
|
||||
if (USE_TEST)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -1 +0,0 @@
|
||||
5.1.5
|
@ -1 +0,0 @@
|
||||
docker/Dockerfile.alpine
|
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@ -0,0 +1,35 @@
|
||||
FROM alpine:3.11 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", "ws_install" ]
|
||||
|
||||
FROM alpine:3.11 as runtime
|
||||
|
||||
RUN apk add --no-cache libstdc++
|
||||
RUN apk add --no-cache strace
|
||||
|
||||
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"]
|
||||
EXPOSE 8008
|
||||
CMD ["--help"]
|
38
README.md
38
README.md
@ -1,13 +1,41 @@
|
||||
## Hello world
|
||||
|
||||

|
||||

|
||||
|
||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android).
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||
|
||||
Interested ? Go read the [docs](https://bsergean.github.io/IXWebSocket/site/) ! If things don't work as expected, please create an issue in github, or even better a pull request if you know how to fix your problem.
|
||||
```cpp
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
IXWebSocket is actively being developed, check out the [changelog](CHANGELOG.md) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra).
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
IXWebSocket is not yet autobahn compliant, but we are working on changing this. See the current compliance [test results](https://bsergean.github.io/IXWebSocket/autobahn/index.html).
|
||||
std::string url("ws://localhost:8080/");
|
||||
webSocket.setUrl(url);
|
||||
|
||||
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||
// when a message or an event (open, close, error) is received
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cout << msg->str << std::endl;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Now that our callback is setup, we can start our background thread and receive messages
|
||||
webSocket.start();
|
||||
|
||||
// Send a message to the server (default to TEXT mode)
|
||||
webSocket.send("hello world");
|
||||
```
|
||||
|
||||
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||
|
||||
IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra).
|
||||
|
||||
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/IXWebSocket/autobahn/index.html). Some tests are still failing in the server code.
|
||||
|
11
SECURITY.md
Normal file
11
SECURITY.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 7.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Users should send an email to bsergean@gmail.com to report a vulnerability.
|
16
appveyor.yml
16
appveyor.yml
@ -2,13 +2,21 @@ image:
|
||||
- Visual Studio 2017
|
||||
|
||||
install:
|
||||
- ls -al
|
||||
- cd C:\Tools\vcpkg
|
||||
- git pull
|
||||
- .\bootstrap-vcpkg.bat
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
- cd test
|
||||
- vcpkg install zlib:x64-windows
|
||||
- vcpkg install mbedtls:x64-windows
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -G"NMake Makefiles" ..
|
||||
- cmake -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" ..
|
||||
- nmake
|
||||
- ixwebsocket_unittest.exe
|
||||
- cd ..
|
||||
- cd test
|
||||
- ..\build\test\ixwebsocket_unittest.exe
|
||||
|
||||
cache: c:\tools\vcpkg\installed\
|
||||
|
||||
build: off
|
||||
|
@ -1,43 +1,67 @@
|
||||
version: "3"
|
||||
services:
|
||||
snake:
|
||||
image: bsergean/ws:build
|
||||
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
|
||||
ports:
|
||||
- "8765:8765"
|
||||
networks:
|
||||
- ws-net
|
||||
depends_on:
|
||||
- redis1
|
||||
# snake:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: ws snake --port 8767 --host 0.0.0.0 --redis_hosts redis1
|
||||
# ports:
|
||||
# - "8767:8767"
|
||||
# networks:
|
||||
# - ws-net
|
||||
# depends_on:
|
||||
# - redis1
|
||||
|
||||
ws:
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
# proxy:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: strace ws proxy_server --remote_host 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765 -v
|
||||
# ports:
|
||||
# - "8765:8765"
|
||||
# networks:
|
||||
# - ws-net
|
||||
|
||||
#pyproxy:
|
||||
# image: bsergean/ws_proxy:build
|
||||
# entrypoint: /usr/bin/ws_proxy.py --remote_url 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765
|
||||
# ports:
|
||||
# - "8765:8765"
|
||||
# networks:
|
||||
# - ws-net
|
||||
|
||||
# # ws:
|
||||
# # security_opt:
|
||||
# # - seccomp:unconfined
|
||||
# # cap_add:
|
||||
# # - SYS_PTRACE
|
||||
# # stdin_open: true
|
||||
# # tty: true
|
||||
# # image: bsergean/ws:build
|
||||
# # entrypoint: sh
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
# # depends_on:
|
||||
# # - redis1
|
||||
# #
|
||||
# # redis1:
|
||||
# # image: redis:alpine
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
# #
|
||||
# # statsd:
|
||||
# # image: jaconel/statsd
|
||||
# # ports:
|
||||
# # - "8125:8125"
|
||||
# # environment:
|
||||
# # - STATSD_DUMP_MSG=true
|
||||
# # - GRAPHITE_HOST=127.0.0.1
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
|
||||
compile:
|
||||
image: alpine
|
||||
entrypoint: sh
|
||||
stdin_open: true
|
||||
tty: true
|
||||
image: bsergean/ws:build
|
||||
entrypoint: bash
|
||||
networks:
|
||||
- ws-net
|
||||
depends_on:
|
||||
- redis1
|
||||
|
||||
redis1:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
- ws-net
|
||||
|
||||
statsd:
|
||||
image: jaconel/statsd
|
||||
ports:
|
||||
- "8125:8125"
|
||||
environment:
|
||||
- STATSD_DUMP_MSG=true
|
||||
- GRAPHITE_HOST=127.0.0.1
|
||||
networks:
|
||||
- ws-net
|
||||
volumes:
|
||||
- /Users/bsergeant/src/foss:/home/bsergean/src/foss
|
||||
|
||||
networks:
|
||||
ws-net:
|
||||
|
@ -14,7 +14,7 @@ COPY --chown=app:app . /opt
|
||||
WORKDIR /opt
|
||||
|
||||
USER app
|
||||
RUN [ "make" ]
|
||||
RUN [ "make", "ws_install" ]
|
||||
|
||||
FROM alpine as runtime
|
||||
|
||||
@ -30,4 +30,5 @@ USER app
|
||||
WORKDIR /home/app
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
EXPOSE 8008
|
||||
CMD ["--help"]
|
||||
|
@ -20,4 +20,5 @@ COPY . .
|
||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||
|
||||
RUN ["make", "test"]
|
||||
# RUN ["make", "test"]
|
||||
CMD ["sh"]
|
||||
|
@ -1,5 +1,304 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
All changes to this project will be documented in this file.
|
||||
|
||||
## [7.8.7] - 2019-12-28
|
||||
|
||||
(mbedtls) fix related to private key file parsing and initialization
|
||||
|
||||
## [7.8.6] - 2019-12-28
|
||||
|
||||
(ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd
|
||||
|
||||
## [7.8.5] - 2019-12-28
|
||||
|
||||
(ws cobra to sentry) handle null events for empty queues
|
||||
|
||||
## [7.8.4] - 2019-12-27
|
||||
|
||||
(ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events
|
||||
|
||||
## [7.8.3] - 2019-12-27
|
||||
|
||||
(ws cobra to sentry) refactor queue related code into a class
|
||||
|
||||
## [7.8.2] - 2019-12-25
|
||||
|
||||
(ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry
|
||||
|
||||
## [7.8.1] - 2019-12-25
|
||||
|
||||
(ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed)
|
||||
|
||||
## [7.8.0] - 2019-12-24
|
||||
|
||||
(ws client) all commands use spdlog instead of std::cerr or std::cout for logging
|
||||
|
||||
## [7.6.5] - 2019-12-24
|
||||
|
||||
(cobra client) send a websocket ping every 30s to keep the connection opened
|
||||
|
||||
## [7.6.4] - 2019-12-22
|
||||
|
||||
(client) error handling, quote url in error case when failing to parse one
|
||||
(ws) ws_cobra_publish: register callbacks before connecting
|
||||
(doc) mention mbedtls in supported ssl server backend
|
||||
|
||||
## [7.6.3] - 2019-12-20
|
||||
|
||||
(tls) add a simple description of the TLS configuration routine for debugging
|
||||
|
||||
## [7.6.2] - 2019-12-20
|
||||
|
||||
(mbedtls) correct support for using own certificate and private key
|
||||
|
||||
## [7.6.1] - 2019-12-20
|
||||
|
||||
(ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11
|
||||
|
||||
## [7.6.0] - 2019-12-19
|
||||
|
||||
(cobra) Add TLS options to all cobra commands and classes. Add example to the doc.
|
||||
|
||||
## [7.5.8] - 2019-12-18
|
||||
|
||||
(cobra-to-sentry) capture application version from device field
|
||||
|
||||
## [7.5.7] - 2019-12-18
|
||||
|
||||
(tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server)
|
||||
|
||||
## [7.5.6] - 2019-12-18
|
||||
|
||||
(tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode
|
||||
|
||||
## [7.5.5] - 2019-12-17
|
||||
|
||||
(tls options client) TLSOptions struct _validated member should be initialized to false
|
||||
|
||||
## [7.5.4] - 2019-12-16
|
||||
|
||||
(websocket client) improve the error message when connecting to a non websocket server
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
Connection error: Got bad status connecting to example.com:443, status: 200, HTTP Status line: HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
Connection error: Expecting status 101 (Switching Protocol), got 200 status connecting to example.com:443, HTTP Status line: HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
## [7.5.3] - 2019-12-12
|
||||
|
||||
(server) attempt at fixing #131 by using blocking writes in server mode
|
||||
|
||||
## [7.5.2] - 2019-12-11
|
||||
|
||||
(ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages
|
||||
|
||||
## [7.5.1] - 2019-12-06
|
||||
|
||||
(mac) convert SSL errors to utf8
|
||||
|
||||
## [7.5.0] - 2019-12-05
|
||||
|
||||
- (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry.
|
||||
|
||||
In the example below sentry we are sending data too fast, sentry asks us to slow down which we do. Notice how the sent count stop increasing, while we are waiting for 41 seconds.
|
||||
|
||||
```
|
||||
[2019-12-05 15:50:33.759] [info] messages received 2449 sent 3
|
||||
[2019-12-05 15:50:34.759] [info] messages received 5533 sent 7
|
||||
[2019-12-05 15:50:35.759] [info] messages received 8612 sent 11
|
||||
[2019-12-05 15:50:36.759] [info] messages received 11562 sent 15
|
||||
[2019-12-05 15:50:37.759] [info] messages received 14410 sent 19
|
||||
[2019-12-05 15:50:38.759] [info] messages received 17236 sent 23
|
||||
[2019-12-05 15:50:39.282] [error] Error sending data to sentry: 429
|
||||
[2019-12-05 15:50:39.282] [error] Body: {"exception":[{"stacktrace":{"frames":[{"filename":"WorldScene.lua","function":"WorldScene.lua:1935","lineno":1958},{"filename":"WorldScene.lua","function":"onUpdate_WorldCam","lineno":1921},{"filename":"WorldMapTile.lua","function":"__index","lineno":239}]},"value":"noisytypes: Attempt to call nil(nil,2224139838)!"}],"platform":"python","sdk":{"name":"ws","version":"1.0.0"},"tags":[["game","niso"],["userid","107638363"],["environment","live"]],"timestamp":"2019-12-05T23:50:39Z"}
|
||||
|
||||
[2019-12-05 15:50:39.282] [error] Response: {"error_name":"rate_limit","error":"Creation of this event was denied due to rate limiting"}
|
||||
[2019-12-05 15:50:39.282] [warning] Error 429 - Too Many Requests. ws will sleep and retry after 41 seconds
|
||||
[2019-12-05 15:50:39.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:40.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:41.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:42.761] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:43.762] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:44.763] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:45.768] [info] messages received 18839 sent 25
|
||||
```
|
||||
|
||||
## [7.4.5] - 2019-12-03
|
||||
|
||||
- (ws) #125 / fix build problem when jsoncpp is not installed locally
|
||||
|
||||
## [7.4.4] - 2019-12-03
|
||||
|
||||
- (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present
|
||||
|
||||
## [7.4.3] - 2019-12-03
|
||||
|
||||
- (http client) use std::unordered_map instead of std::map for HttpParameters and HttpFormDataParameters class aliases
|
||||
|
||||
## [7.4.2] - 2019-12-02
|
||||
|
||||
- (client) internal IXDNSLookup class requires a valid cancellation request function callback to be passed in
|
||||
|
||||
## [7.4.1] - 2019-12-02
|
||||
|
||||
- (client) fix an overflow in the exponential back off code
|
||||
|
||||
## [7.4.0] - 2019-11-25
|
||||
|
||||
- (http client) Add support for multipart HTTP POST upload
|
||||
- (ixsentry) Add support for uploading a minidump to sentry
|
||||
|
||||
## [7.3.5] - 2019-11-20
|
||||
|
||||
- On Darwin SSL, add ability to skip peer verification.
|
||||
|
||||
## [7.3.4] - 2019-11-20
|
||||
|
||||
- 32-bits compile fix, courtesy of @fcojavmc
|
||||
|
||||
## [7.3.1] - 2019-11-16
|
||||
|
||||
- ws proxy_server / remote server close not forwarded to the client
|
||||
|
||||
## [7.3.0] - 2019-11-15
|
||||
|
||||
- New ws command: `ws proxy_server`.
|
||||
|
||||
## [7.2.2] - 2019-11-01
|
||||
|
||||
- Tag a release + minor reformating.
|
||||
|
||||
## [7.2.1] - 2019-10-26
|
||||
|
||||
- Add unittest to IXSentryClient to lua backtrace parsing code
|
||||
|
||||
## [7.2.0] - 2019-10-24
|
||||
|
||||
- Add cobra_metrics_to_redis sub-command to create streams for each cobra metric event being received.
|
||||
|
||||
## [7.1.0] - 2019-10-13
|
||||
|
||||
- Add client support for websocket subprotocol. Look for the new addSubProtocol method for details.
|
||||
|
||||
## [7.0.0] - 2019-10-01
|
||||
|
||||
- TLS support in server code, only implemented for the OpenSSL SSL backend for now.
|
||||
|
||||
## [6.3.4] - 2019-09-30
|
||||
|
||||
- all ws subcommands propagate tls options to servers (unimplemented) or ws or http client (implemented) (contributed by Matt DeBoer)
|
||||
|
||||
## [6.3.3] - 2019-09-30
|
||||
|
||||
- ws has a --version option
|
||||
|
||||
## [6.3.2] - 2019-09-29
|
||||
|
||||
- (http + websocket clients) can specify cacert and some other tls options (not implemented on all backend). This makes it so that server certs can finally be validated on windows.
|
||||
|
||||
## [6.3.1] - 2019-09-29
|
||||
|
||||
- Add ability to use OpenSSL on apple platforms.
|
||||
|
||||
## [6.3.0] - 2019-09-28
|
||||
|
||||
- ixcobra / fix crash in CobraConnection::publishNext when the queue is empty + handle CobraConnection_PublishMode_Batch in CobraMetricsThreadedPublisher
|
||||
|
||||
## [6.2.9] - 2019-09-27
|
||||
|
||||
- mbedtls fixes / the unittest now pass on macOS, and hopefully will on Windows/AppVeyor as well.
|
||||
|
||||
## [6.2.8] - 2019-09-26
|
||||
|
||||
- Http server: add options to ws https to redirect all requests to a given url. POST requests will get a 200 and an empty response.
|
||||
|
||||
```
|
||||
ws httpd -L --redirect_url https://www.google.com
|
||||
```
|
||||
|
||||
## [6.2.7] - 2019-09-25
|
||||
|
||||
- Stop having ws send subcommand send a binary message in text mode, which would cause error in `make ws_test` shell script test.
|
||||
|
||||
## [6.2.6] - 2019-09-24
|
||||
|
||||
- Fix 2 race conditions detected with TSan, one in CobraMetricsPublisher::push and another one in WebSocketTransport::sendData (that one was bad).
|
||||
|
||||
## [6.2.5] - 2019-09-23
|
||||
|
||||
- Add simple Redis Server which is only capable of doing publish / subscribe. New ws redis_server sub-command to use it. The server is used in the unittest, so that we can run on CI in environment where redis isn not available like github actions env.
|
||||
|
||||
## [6.2.4] - 2019-09-22
|
||||
|
||||
- Add options to configure TLS ; contributed by Matt DeBoer. Only implemented for OpenSSL TLS backend for now.
|
||||
|
||||
## [6.2.3] - 2019-09-21
|
||||
|
||||
- Fix crash in the Linux unittest in the HTTP client code, in Socket::readBytes
|
||||
- Cobra Metrics Publisher code returns the message id of the message that got published, to be used to validated that it got sent properly when receiving an ack.
|
||||
|
||||
## [6.2.2] - 2019-09-19
|
||||
|
||||
- In DNS lookup code, make sure the weak pointer we use lives through the expected scope (if branch)
|
||||
|
||||
## [6.2.1] - 2019-09-17
|
||||
|
||||
- On error while doing a client handshake, additionally display port number next to the host name
|
||||
|
||||
## [6.2.0] - 2019-09-09
|
||||
|
||||
- websocket and http server: server does not close the bound client socket in many cases
|
||||
- improve some websocket error messages
|
||||
- add a utility function with unittest to parse status line and stop using scanf which triggers warnings on Windows
|
||||
- update ws CLI11 (our command line argument parsing library) to the latest, which fix a compiler bug about optional
|
||||
|
||||
## [6.1.0] - 2019-09-08
|
||||
|
||||
- move poll wrapper on top of select (only used on Windows) to the ix namespace
|
||||
|
||||
## [6.0.1] - 2019-09-05
|
||||
|
||||
- add cobra metrics publisher + server unittest
|
||||
- add cobra client + server unittest
|
||||
- ws snake (cobra simple server) add basic support for unsubscription + subscribe send the proper subscription data + redis client subscription can be cancelled
|
||||
- IXCobraConnection / pdu handlers can crash if they receive json data which is not an object
|
||||
|
||||
## [6.0.0] - 2019-09-04
|
||||
|
||||
- all client autobahn test should pass !
|
||||
- zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9/ (fix autobahn test 13.X which uses 8 for the windows size)
|
||||
|
||||
## [5.2.0] - 2019-09-04
|
||||
|
||||
- Fragmentation: for sent messages which are compressed, the continuation fragments should not have the rsv1 bit set (fix all autobahn tests for zlib compression 12.X)
|
||||
- Websocket Server / do a case insensitive string search when looking for an Upgrade header whose value is websocket. (some client use WebSocket with some upper-case characters)
|
||||
|
||||
## [5.1.9] - 2019-09-03
|
||||
|
||||
- ws autobahn / report progress with spdlog::info to get timing info
|
||||
- ws autobahn / use condition variables for stopping test case + add more logging on errors
|
||||
|
||||
## [5.1.8] - 2019-09-03
|
||||
|
||||
- Per message deflate/compression: handle fragmented messages (fix autobahn test: 12.1.X and probably others)
|
||||
|
||||
## [5.1.7] - 2019-09-03
|
||||
|
||||
- Receiving invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling)
|
||||
|
||||
## [5.1.6] - 2019-09-03
|
||||
|
||||
- Sending invalid UTF-8 TEXT message should fail and close the connection (fix remaining autobahn test: 6.X UTF-8 Handling)
|
||||
- Fix failing unittest which was sending binary data in text mode with WebSocket::send to call properly call WebSocket::sendBinary instead.
|
||||
- Validate that the reason is proper utf-8. (fix autobahn test 7.5.1)
|
||||
- Validate close codes. Autobahn 7.9.*
|
||||
|
||||
## [5.1.5] - 2019-09-03
|
||||
|
||||
@ -27,7 +326,7 @@ Close connections when reserved bits are used (fix autobahn test: 3.X Reserved B
|
||||
- add utf-8 validation code, not hooked up properly yet
|
||||
- Ping received with a payload too large (> 125 bytes) trigger a connection closure
|
||||
- cobra / add tracking about published messages
|
||||
- cobra / publish returns a message id, that can be used when
|
||||
- cobra / publish returns a message id, that can be used when
|
||||
- cobra / new message type in the message received handler when publish/ok is received (can be used to implement an ack system).
|
||||
|
||||
## [5.0.9] - 2019-08-30
|
||||
@ -41,7 +340,7 @@ ws connect --max_wait 5000 ws://example.com # will only wait 5 seconds max betwe
|
||||
|
||||
## [5.0.7] - 2019-08-23
|
||||
- WebSocket: add new option to pass in extra HTTP headers when connecting.
|
||||
- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting
|
||||
- `ws connect` add new option (-H, works like [curl](https://stackoverflow.com/questions/356705/how-to-send-a-header-using-a-http-request-through-a-curl-call)) to pass in extra HTTP headers when connecting
|
||||
|
||||
If you run against `ws echo_server` you will see the headers being received printed in the terminal.
|
||||
```
|
||||
|
@ -21,6 +21,8 @@ Options for building:
|
||||
* `-DUSE_MBED_TLS=1` will use [mbedlts](https://tls.mbed.org/) for the TLS support (default on Windows)
|
||||
* `-DUSE_WS=1` will build the ws interactive command line tool
|
||||
|
||||
If you are on Windows, look at the [appveyor](https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml) file that has instructions for building dependencies.
|
||||
|
||||
### vcpkg
|
||||
|
||||
It is possible to get IXWebSocket through Microsoft [vcpkg](https://github.com/microsoft/vcpkg).
|
||||
@ -31,7 +33,7 @@ vcpkg install ixwebsocket
|
||||
|
||||
### Conan
|
||||
|
||||
Support for building with conan was contributed by Olivia Zoe (thanks !). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`. The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
|
||||
Support for building with conan was contributed by Olivia Zoe (thanks!). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`, and a list of the uploaded versions is available on [Bintray](https://bintray.com/oliviazoe0/conan-packages/IXWebSocket%3ALunarWatcher). The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
|
||||
|
||||
```
|
||||
conan remote add remote_name_here https://api.bintray.com/conan/oliviazoe0/conan-packages
|
||||
@ -57,6 +59,3 @@ app@ca2340eb9106:~$ ws --help
|
||||
ws is a websocket tool
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
81
docs/cobra.md
Normal file
81
docs/cobra.md
Normal file
@ -0,0 +1,81 @@
|
||||
## General
|
||||
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messaging server. The `ws` utility can run a cobra server (named snake), and has client to publish and subscribe to a cobra server.
|
||||
|
||||
Bring up 3 terminals and run a server, a publisher and a subscriber in each one. As you publish data you should see it being received by the subscriber. You can run `redis-cli MONITOR` too to see how redis is being used.
|
||||
|
||||
### Server
|
||||
|
||||
You will need to have a redis server running locally. To run the server:
|
||||
|
||||
```bash
|
||||
$ cd <ixwebsocket-top-level-folder>/ixsnake/ixsnake
|
||||
$ ws snake
|
||||
{
|
||||
"apps": {
|
||||
"FC2F10139A2BAc53BB72D9db967b024f": {
|
||||
"roles": {
|
||||
"_sub": {
|
||||
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
|
||||
},
|
||||
"_pub": {
|
||||
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redis host: 127.0.0.1
|
||||
redis password:
|
||||
redis port: 6379
|
||||
```
|
||||
|
||||
### Publisher
|
||||
|
||||
```bash
|
||||
$ cd <ixwebsocket-top-level-folder>/ws
|
||||
$ ws cobra_publish --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel cobraMetricsSample.json
|
||||
[2019-11-27 09:06:12.980] [info] Publisher connected
|
||||
[2019-11-27 09:06:12.980] [info] Connection: Upgrade
|
||||
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Accept: zTtQKMKbvwjdivURplYXwCVUCWM=
|
||||
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-11-27 09:06:12.980] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
|
||||
[2019-11-27 09:06:12.980] [info] Upgrade: websocket
|
||||
[2019-11-27 09:06:12.982] [info] Publisher authenticated
|
||||
[2019-11-27 09:06:12.982] [info] Published msg 3
|
||||
[2019-11-27 09:06:12.982] [info] Published message id 3 acked
|
||||
```
|
||||
|
||||
### Subscriber
|
||||
|
||||
```bash
|
||||
$ ws cobra_subscribe --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel
|
||||
#messages 0 msg/s 0
|
||||
[2019-11-27 09:07:39.341] [info] Subscriber connected
|
||||
[2019-11-27 09:07:39.341] [info] Connection: Upgrade
|
||||
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Accept: 9vkQWofz49qMCUlTSptCCwHWm+Q=
|
||||
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-11-27 09:07:39.341] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
|
||||
[2019-11-27 09:07:39.341] [info] Upgrade: websocket
|
||||
[2019-11-27 09:07:39.342] [info] Subscriber authenticated
|
||||
[2019-11-27 09:07:39.345] [info] Subscriber: subscribed to channel test_channel
|
||||
#messages 0 msg/s 0
|
||||
#messages 0 msg/s 0
|
||||
#messages 0 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 1 msg/s 1
|
||||
#messages 1 msg/s 0
|
||||
#messages 1 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 3 msg/s 2
|
||||
#messages 3 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 4 msg/s 1
|
||||
^C
|
||||
```
|
@ -24,15 +24,14 @@ Large frames are broken up into smaller chunks or messages to avoid filling up t
|
||||
|
||||
The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries.
|
||||
|
||||
The unittest tries to be comprehensive, and has been running on multiple platoform, with different sanitizers such as thread sanitizer to catch data races or the undefined behavior sanitizer.
|
||||
The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.
|
||||
|
||||
The regression test is running after each commit on travis.
|
||||
|
||||
## Limitations
|
||||
|
||||
* On Windows TLS is not setup yet to validate certificates.
|
||||
* On Windows and Android certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. On Windows with mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
||||
* There is no convenient way to embed a ca cert.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
|
@ -9,14 +9,15 @@
|
||||
* Linux
|
||||
* Android
|
||||
* Windows
|
||||
* FreeBSD
|
||||
|
||||
## Example code
|
||||
|
||||
```
|
||||
# Required on Windows
|
||||
```cpp
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
# Our websocket object
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("ws://localhost:8080/");
|
||||
@ -39,8 +40,12 @@ webSocket.start();
|
||||
webSocket.send("hello world");
|
||||
```
|
||||
|
||||
## Why another library ?
|
||||
## Why another library?
|
||||
|
||||
There are 2 main reasons that explain why IXWebSocket got written. First, we needed a C++ cross-platform client library, which should have few dependencies. What looked like the most solid one, [websocketpp](https://github.com/zaphoyd/websocketpp) did depend on boost and this was not an option for us. Secondly, there were other available libraries with fewer dependencies (C ones), but they required calling an explicit poll routine periodically to know if a client had received data from a server, which was not elegant.
|
||||
|
||||
We started by solving those 2 problems, then we added server websocket code, then an HTTP client, and finally a very simple HTTP server.
|
||||
|
||||
## Contributing
|
||||
|
||||
IXWebSocket is developed on [GitHub](https://github.com/machinezone/IXWebSocket). We'd love to hear about how you use it; opening up an issue on GitHub is ok for that. If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||
|
@ -6,7 +6,7 @@ The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder cou
|
||||
|
||||
To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function.
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
|
||||
int main()
|
||||
@ -22,12 +22,12 @@ int main()
|
||||
|
||||
## WebSocket client API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
|
||||
...
|
||||
|
||||
# Our websocket object
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("ws://localhost:8080/");
|
||||
@ -82,9 +82,9 @@ If the connection was closed and sending failed, the return value will be set to
|
||||
|
||||
### Open and Close notifications
|
||||
|
||||
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
|
||||
The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
@ -115,7 +115,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Error)
|
||||
@ -140,7 +140,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
|
||||
|
||||
```
|
||||
```cpp
|
||||
std::string url("wss://example.com");
|
||||
websocket.configure(url);
|
||||
```
|
||||
@ -149,7 +149,7 @@ websocket.configure(url);
|
||||
|
||||
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Ping ||
|
||||
@ -163,7 +163,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
A ping message can be sent to the server, with an optional data string.
|
||||
|
||||
```
|
||||
```cpp
|
||||
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
|
||||
```
|
||||
|
||||
@ -173,7 +173,7 @@ You can configure an optional heart beat / keep-alive, sent every 45 seconds
|
||||
when there is no any traffic to make sure that load balancers do not kill an
|
||||
idle connection.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setHeartBeatPeriod(45);
|
||||
```
|
||||
|
||||
@ -181,17 +181,32 @@ webSocket.setHeartBeatPeriod(45);
|
||||
|
||||
You can set extra HTTP headers to be sent during the WebSocket handshake.
|
||||
|
||||
```
|
||||
```cpp
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["foo"] = "bar";
|
||||
webSocket.setExtraHeaders(headers);
|
||||
```
|
||||
|
||||
### Subprotocols
|
||||
|
||||
You can specify subprotocols to be set during the WebSocket handshake. For more info you can refer to [this doc](https://hpbn.co/websocket/#subprotocol-negotiation).
|
||||
|
||||
```cpp
|
||||
webSocket.addSubprotocol("appProtocol-v1");
|
||||
webSocket.addSubprotocol("appProtocol-v2");
|
||||
```
|
||||
|
||||
The protocol that the server did accept is available in the open info `protocol` field.
|
||||
|
||||
```cpp
|
||||
std::cout << "protocol: " << msg->openInfo.protocol << std::endl;
|
||||
```
|
||||
|
||||
### Automatic reconnection
|
||||
|
||||
Automatic reconnection kicks in when the connection is disconnected without the user consent. This feature is on by default and can be turned off.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.enableAutomaticReconnection(); // turn on
|
||||
webSocket.disableAutomaticReconnection(); // turn off
|
||||
bool enabled = webSocket.isAutomaticReconnectionEnabled(); // query state
|
||||
@ -224,14 +239,47 @@ Wait time(ms): 10000
|
||||
|
||||
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried.
|
||||
|
||||
```
|
||||
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
|
||||
```cpp
|
||||
webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
|
||||
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
|
||||
```
|
||||
|
||||
### TLS support and configuration
|
||||
|
||||
To leverage TLS features, the library must be compiled with the option `USE_TLS=1`.
|
||||
|
||||
Then, secure sockets are automatically used when connecting to a `wss://*` url.
|
||||
|
||||
Additional TLS options can be configured by passing a `ix::SocketTLSOptions` instance to the
|
||||
`setTLSOptions` on `ix::WebSocket` (or `ix::WebSocketServer` or `ix::HttpServer`)
|
||||
|
||||
```cpp
|
||||
webSocket.setTLSOptions({
|
||||
.certFile = "path/to/cert/file.pem",
|
||||
.keyFile = "path/to/key/file.pem",
|
||||
.caFile = "path/to/trust/bundle/file.pem"
|
||||
});
|
||||
```
|
||||
|
||||
Specifying `certFile` and `keyFile` configures the certificate that will be used to communicate with TLS peers.
|
||||
|
||||
On a client, this is only necessary for connecting to servers that require a client certificate.
|
||||
|
||||
On a server, this is necessary for TLS support.
|
||||
|
||||
Specifying `caFile` configures the trusted roots bundle file (in PEM format) that will be used to verify peer certificates.
|
||||
- The special value of `SYSTEM` (the default) indicates that the system-configured trust bundle should be used; this is generally what you want when connecting to any publicly exposed API/server.
|
||||
- The special value of `NONE` can be used to disable peer verification; this is only recommended to rule out certificate verification when testing connectivity.
|
||||
|
||||
For a client, specifying `caFile` can be used if connecting to a server that uses a self-signed cert, or when using a custom CA in an internal environment.
|
||||
|
||||
For a server, specifying `caFile` implies that:
|
||||
1. You require clients to present a certificate
|
||||
1. It must be signed by one of the trusted roots in the file
|
||||
|
||||
## WebSocket server API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
...
|
||||
@ -296,7 +344,7 @@ server.wait();
|
||||
|
||||
## HTTP client API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
|
||||
...
|
||||
@ -379,7 +427,7 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
||||
|
||||
## HTTP server API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
|
||||
ix::HttpServer server(port, hostname);
|
||||
@ -397,7 +445,7 @@ server.wait();
|
||||
|
||||
If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example.
|
||||
|
||||
```
|
||||
```cpp
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||
|
300
docs/ws.md
300
docs/ws.md
@ -29,6 +29,181 @@ Subcommands:
|
||||
httpd HTTP server
|
||||
```
|
||||
|
||||
## curl
|
||||
|
||||
The curl subcommand try to be compatible with the curl syntax, to fetch http pages.
|
||||
|
||||
Making a HEAD request with the -I parameter.
|
||||
|
||||
```
|
||||
$ ws curl -I https://www.google.com/
|
||||
|
||||
Accept-Ranges: none
|
||||
Alt-Svc: quic=":443"; ma=2592000; v="46,43",h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000
|
||||
Cache-Control: private, max-age=0
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
Date: Tue, 08 Oct 2019 21:36:57 GMT
|
||||
Expires: -1
|
||||
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
|
||||
Server: gws
|
||||
Set-Cookie: NID=188=ASwfz8GrXQrHCLqAz-AndLOMLcz0rC9yecnf3h0yXZxRL3rTufTU_GDDwERp7qQL7LZ_EB8gCRyPXGERyOSAgaqgnrkoTmvWrwFemRLMaOZ896GrHobi5fV7VLklnSG2w48Gj8xMlwxfP7Z-bX-xR9UZxep1tHM6UmFQdD_GkBE; expires=Wed, 08-Apr-2020 21:36:57 GMT; path=/; domain=.google.com; HttpOnly
|
||||
Transfer-Encoding: chunked
|
||||
Vary: Accept-Encoding
|
||||
X-Frame-Options: SAMEORIGIN
|
||||
X-XSS-Protection: 0
|
||||
Upload size: 143
|
||||
Download size: 0
|
||||
Status: 200
|
||||
```
|
||||
|
||||
Making a POST request with the -F parameter.
|
||||
|
||||
```
|
||||
$ ws curl -F foo=bar https://httpbin.org/post
|
||||
foo: bar
|
||||
Downloaded 438 bytes out of 438
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Origin: *
|
||||
Connection: keep-alive
|
||||
Content-Encoding:
|
||||
Content-Length: 438
|
||||
Content-Type: application/json
|
||||
Date: Tue, 08 Oct 2019 21:47:54 GMT
|
||||
Referrer-Policy: no-referrer-when-downgrade
|
||||
Server: nginx
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: DENY
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Upload size: 219
|
||||
Download size: 438
|
||||
Status: 200
|
||||
payload: {
|
||||
"args": {},
|
||||
"data": "",
|
||||
"files": {},
|
||||
"form": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Content-Length": "7",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Host": "httpbin.org",
|
||||
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
|
||||
},
|
||||
"json": null,
|
||||
"origin": "155.94.127.118, 155.94.127.118",
|
||||
"url": "https://httpbin.org/post"
|
||||
}
|
||||
```
|
||||
|
||||
Passing in a custom header with -H.
|
||||
|
||||
```
|
||||
$ ws curl -F foo=bar -H 'my_custom_header: baz' https://httpbin.org/post
|
||||
my_custom_header: baz
|
||||
foo: bar
|
||||
Downloaded 470 bytes out of 470
|
||||
Access-Control-Allow-Credentials: true
|
||||
Access-Control-Allow-Origin: *
|
||||
Connection: keep-alive
|
||||
Content-Encoding:
|
||||
Content-Length: 470
|
||||
Content-Type: application/json
|
||||
Date: Tue, 08 Oct 2019 21:50:25 GMT
|
||||
Referrer-Policy: no-referrer-when-downgrade
|
||||
Server: nginx
|
||||
X-Content-Type-Options: nosniff
|
||||
X-Frame-Options: DENY
|
||||
X-XSS-Protection: 1; mode=block
|
||||
Upload size: 243
|
||||
Download size: 470
|
||||
Status: 200
|
||||
payload: {
|
||||
"args": {},
|
||||
"data": "",
|
||||
"files": {},
|
||||
"form": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"headers": {
|
||||
"Accept": "*/*",
|
||||
"Content-Length": "7",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Host": "httpbin.org",
|
||||
"My-Custom-Header": "baz",
|
||||
"User-Agent": "ixwebsocket/7.0.0 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11"
|
||||
},
|
||||
"json": null,
|
||||
"origin": "155.94.127.118, 155.94.127.118",
|
||||
"url": "https://httpbin.org/post"
|
||||
}
|
||||
```
|
||||
|
||||
## connect
|
||||
|
||||
The connect command connects to a websocket endpoint, and starts an interactive prompt. Line editing, such as using the direction keys to fetch the last thing you tried to type) is provided. That command is pretty useful to try to send random data to an endpoint and verify that the service handles it with grace (such as sending invalid json).
|
||||
|
||||
```
|
||||
ws connect wss://echo.websocket.org
|
||||
Type Ctrl-D to exit prompt...
|
||||
Connecting to url: wss://echo.websocket.org
|
||||
> ws_connect: connected
|
||||
Uri: /
|
||||
Handshake Headers:
|
||||
Connection: Upgrade
|
||||
Date: Tue, 08 Oct 2019 21:38:44 GMT
|
||||
Sec-WebSocket-Accept: 2j6LBScZveqrMx1W/GJkCWvZo3M=
|
||||
sec-websocket-extensions:
|
||||
Server: Kaazing Gateway
|
||||
Upgrade: websocket
|
||||
Received ping
|
||||
Received ping
|
||||
Received ping
|
||||
Hello world !
|
||||
> Received 13 bytes
|
||||
ws_connect: received message: Hello world !
|
||||
> Hello world !
|
||||
> Received 13 bytes
|
||||
ws_connect: received message: Hello world !
|
||||
```
|
||||
|
||||
```
|
||||
ws connect 'ws://jeanserge.com/v2?appkey=_pubsub'
|
||||
Type Ctrl-D to exit prompt...
|
||||
Connecting to url: ws://jeanserge.com/v2?appkey=_pubsub
|
||||
> ws_connect: connected
|
||||
Uri: /v2?appkey=_pubsub
|
||||
Handshake Headers:
|
||||
Connection: Upgrade
|
||||
Date: Tue, 08 Oct 2019 21:45:28 GMT
|
||||
Sec-WebSocket-Accept: LYHmjh9Gsu/Yw7aumQqyPObOEV4=
|
||||
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
Server: Python/3.7 websockets/8.0.2
|
||||
Upgrade: websocket
|
||||
bababababababab
|
||||
> ws_connect: connection closed: code 1000 reason
|
||||
|
||||
ws_connect: connected
|
||||
Uri: /v2?appkey=_pubsub
|
||||
Handshake Headers:
|
||||
Connection: Upgrade
|
||||
Date: Tue, 08 Oct 2019 21:45:44 GMT
|
||||
Sec-WebSocket-Accept: I1rqxdLgTU+opPi5/zKPBTuXdLw=
|
||||
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
Server: Python/3.7 websockets/8.0.2
|
||||
Upgrade: websocket
|
||||
```
|
||||
|
||||
## Websocket proxy
|
||||
|
||||
```
|
||||
ws proxy_server --remote_host ws://127.0.0.1:9000 -v
|
||||
Listening on 127.0.0.1:8008
|
||||
```
|
||||
|
||||
If you connect to ws://127.0.0.1:8008, the proxy will connect to ws://127.0.0.1:9000 and pass all traffic to this server.
|
||||
|
||||
## File transfer
|
||||
|
||||
```
|
||||
@ -68,6 +243,127 @@ Options:
|
||||
--transfer-timeout INT Transfer timeout
|
||||
```
|
||||
|
||||
## Cobra Client
|
||||
## Cobra client and server
|
||||
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has sub-command to interacti with cobra.
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has several sub-command to interact with cobra. There is also a minimal cobra compatible server named snake available.
|
||||
|
||||
Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL and the Mbed TLS backend for now).
|
||||
|
||||
First, generate certificates.
|
||||
|
||||
```
|
||||
$ cd /path/to/IXWebSocket
|
||||
$ cd ixsnake/ixsnake
|
||||
$ bash ../../ws/generate_certs.sh
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
.....+++
|
||||
.................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-ca-key.pem
|
||||
generated ./.certs/trusted-ca-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..+++
|
||||
.......................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-server-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=trusted-server
|
||||
Getting CA Private Key
|
||||
generated ./.certs/trusted-server-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
...................................+++
|
||||
..................................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=trusted-client
|
||||
Getting CA Private Key
|
||||
generated ./.certs/trusted-client-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..............+++
|
||||
.......................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/untrusted-ca-key.pem
|
||||
generated ./.certs/untrusted-ca-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..........+++
|
||||
................................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/untrusted-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=untrusted-client
|
||||
Getting CA Private Key
|
||||
generated ./.certs/untrusted-client-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
.....................................................................................+++
|
||||
...........+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/selfsigned-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=selfsigned-client
|
||||
Getting Private key
|
||||
generated ./.certs/selfsigned-client-crt.pem
|
||||
```
|
||||
|
||||
Now run the snake server.
|
||||
|
||||
```
|
||||
$ export certs=.certs
|
||||
$ ws snake --tls --port 8765 --cert-file ${certs}/trusted-server-crt.pem --key-file ${certs}/trusted-server-key.pem --ca-file ${certs}/trusted-ca-crt.pem
|
||||
{
|
||||
"apps": {
|
||||
"FC2F10139A2BAc53BB72D9db967b024f": {
|
||||
"roles": {
|
||||
"_sub": {
|
||||
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
|
||||
},
|
||||
"_pub": {
|
||||
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redis host: 127.0.0.1
|
||||
redis password:
|
||||
redis port: 6379
|
||||
```
|
||||
|
||||
As a new connection comes in, such output should be printed
|
||||
|
||||
```
|
||||
[2019-12-19 20:27:19.724] [info] New connection
|
||||
id: 0
|
||||
Uri: /v2?appkey=_health
|
||||
Headers:
|
||||
Connection: Upgrade
|
||||
Host: 127.0.0.1:8765
|
||||
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
Sec-WebSocket-Key: d747B0fE61Db73f7Eh47c0==
|
||||
Sec-WebSocket-Protocol: json
|
||||
Sec-WebSocket-Version: 13
|
||||
Upgrade: websocket
|
||||
User-Agent: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
|
||||
```
|
||||
|
||||
To connect and publish a message, do:
|
||||
|
||||
```
|
||||
$ export certs=.certs
|
||||
$ cd /path/to/ws/folder
|
||||
$ ls cobraMetricsSample.json
|
||||
cobraMetricsSample.json
|
||||
$ ws cobra_publish --endpoint wss://127.0.0.1:8765 --appkey FC2F10139A2BAc53BB72D9db967b024f --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db --channel foo --cert-file ${certs}/trusted-client-crt.pem --key-file ${certs}/trusted-client-key.pem --ca-file ${certs}/trusted-ca-crt.pem cobraMetricsSample.json
|
||||
[2019-12-19 20:46:42.656] [info] Publisher connected
|
||||
[2019-12-19 20:46:42.657] [info] Connection: Upgrade
|
||||
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Accept: rs99IFThoBrhSg+k8G4ixH9yaq4=
|
||||
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-12-19 20:46:42.657] [info] Server: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
|
||||
[2019-12-19 20:46:42.657] [info] Upgrade: websocket
|
||||
[2019-12-19 20:46:42.658] [info] Publisher authenticated
|
||||
[2019-12-19 20:46:42.658] [info] Published msg 3
|
||||
[2019-12-19 20:46:42.659] [info] Published message id 3 acked
|
||||
```
|
||||
|
||||
To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew. Use `make ws_mbedtls` accordingly to use MbedTLS.
|
||||
|
35
ixcobra/CMakeLists.txt
Normal file
35
ixcobra/CMakeLists.txt
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXCOBRA_SOURCES
|
||||
ixcobra/IXCobraConnection.cpp
|
||||
ixcobra/IXCobraMetricsThreadedPublisher.cpp
|
||||
ixcobra/IXCobraMetricsPublisher.cpp
|
||||
)
|
||||
|
||||
set (IXCOBRA_HEADERS
|
||||
ixcobra/IXCobraConnection.h
|
||||
ixcobra/IXCobraMetricsThreadedPublisher.h
|
||||
ixcobra/IXCobraMetricsPublisher.h
|
||||
)
|
||||
|
||||
add_library(ixcobra STATIC
|
||||
${IXCOBRA_SOURCES}
|
||||
${IXCOBRA_HEADERS}
|
||||
)
|
||||
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
set(IXCOBRA_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
../ixcrypto
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories( ixcobra PUBLIC ${IXCOBRA_INCLUDE_DIRS} )
|
@ -7,12 +7,15 @@
|
||||
#include "IXCobraConnection.h"
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -20,16 +23,19 @@ namespace ix
|
||||
TrafficTrackerCallback CobraConnection::_trafficTrackerCallback = nullptr;
|
||||
PublishTrackerCallback CobraConnection::_publishTrackerCallback = nullptr;
|
||||
constexpr size_t CobraConnection::kQueueMaxSize;
|
||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||
constexpr int CobraConnection::kPingIntervalSecs;
|
||||
|
||||
CobraConnection::CobraConnection() :
|
||||
_webSocket(new WebSocket()),
|
||||
_publishMode(CobraConnection_PublishMode_Immediate),
|
||||
_authenticated(false),
|
||||
_eventCallback(nullptr),
|
||||
_id(0)
|
||||
_id(1)
|
||||
{
|
||||
_pdu["action"] = "rtm/publish";
|
||||
|
||||
_webSocket->addSubProtocol("json");
|
||||
initWebSocketOnMessageCallback();
|
||||
}
|
||||
|
||||
@ -191,7 +197,7 @@ namespace ix
|
||||
{
|
||||
if (!handleUnsubscriptionResponse(data))
|
||||
{
|
||||
invokeErrorCallback("Error processing subscribe response", msg->str);
|
||||
invokeErrorCallback("Error processing unsubscribe response", msg->str);
|
||||
}
|
||||
}
|
||||
else if (action == "rtm/unsubscribe/error")
|
||||
@ -223,6 +229,10 @@ namespace ix
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
invokeErrorCallback(ss.str(), std::string());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Pong);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -231,11 +241,17 @@ namespace ix
|
||||
_publishMode = publishMode;
|
||||
}
|
||||
|
||||
CobraConnectionPublishMode CobraConnection::getPublishMode()
|
||||
{
|
||||
return _publishMode;
|
||||
}
|
||||
|
||||
void CobraConnection::configure(const std::string& appkey,
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions)
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_roleName = rolename;
|
||||
_roleSecret = rolesecret;
|
||||
@ -248,6 +264,8 @@ namespace ix
|
||||
std::string url = ss.str();
|
||||
_webSocket->setUrl(url);
|
||||
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||
_webSocket->setTLSOptions(socketTLSOptions);
|
||||
_webSocket->setPingInterval(kPingIntervalSecs);
|
||||
}
|
||||
|
||||
//
|
||||
@ -300,6 +318,8 @@ namespace ix
|
||||
//
|
||||
bool CobraConnection::handleHandshakeResponse(const Json::Value& pdu)
|
||||
{
|
||||
if (!pdu.isObject()) return false;
|
||||
|
||||
if (!pdu.isMember("body")) return false;
|
||||
Json::Value body = pdu["body"];
|
||||
|
||||
@ -349,6 +369,8 @@ namespace ix
|
||||
|
||||
bool CobraConnection::handleSubscriptionResponse(const Json::Value& pdu)
|
||||
{
|
||||
if (!pdu.isObject()) return false;
|
||||
|
||||
if (!pdu.isMember("body")) return false;
|
||||
Json::Value body = pdu["body"];
|
||||
|
||||
@ -365,6 +387,8 @@ namespace ix
|
||||
|
||||
bool CobraConnection::handleUnsubscriptionResponse(const Json::Value& pdu)
|
||||
{
|
||||
if (!pdu.isObject()) return false;
|
||||
|
||||
if (!pdu.isMember("body")) return false;
|
||||
Json::Value body = pdu["body"];
|
||||
|
||||
@ -381,6 +405,8 @@ namespace ix
|
||||
|
||||
bool CobraConnection::handleSubscriptionData(const Json::Value& pdu)
|
||||
{
|
||||
if (!pdu.isObject()) return false;
|
||||
|
||||
if (!pdu.isMember("body")) return false;
|
||||
Json::Value body = pdu["body"];
|
||||
|
||||
@ -407,6 +433,8 @@ namespace ix
|
||||
|
||||
bool CobraConnection::handlePublishResponse(const Json::Value& pdu)
|
||||
{
|
||||
if (!pdu.isObject()) return false;
|
||||
|
||||
if (!pdu.isMember("id")) return false;
|
||||
Json::Value id = pdu["id"];
|
||||
|
||||
@ -445,14 +473,17 @@ namespace ix
|
||||
return _jsonWriter.write(value);
|
||||
}
|
||||
|
||||
//
|
||||
// publish is not thread safe as we are trying to reuse some Json objects.
|
||||
//
|
||||
CobraConnection::MsgId CobraConnection::publish(const Json::Value& channels,
|
||||
const Json::Value& msg)
|
||||
std::pair<CobraConnection::MsgId, std::string> CobraConnection::prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_prePublishMutex);
|
||||
|
||||
invokePublishTrackerCallback(true, false);
|
||||
|
||||
CobraConnection::MsgId msgId = _id;
|
||||
|
||||
_body["channels"] = channels;
|
||||
_body["message"] = msg;
|
||||
_pdu["body"] = _body;
|
||||
@ -460,27 +491,55 @@ namespace ix
|
||||
|
||||
std::string serializedJson = serializeJson(_pdu);
|
||||
|
||||
if (_publishMode == CobraConnection_PublishMode_Batch)
|
||||
if (addToQueue)
|
||||
{
|
||||
enqueue(serializedJson);
|
||||
return _id - 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Fast path. We are authenticated and the publishing succeed
|
||||
// This should happen for 99% of the cases.
|
||||
//
|
||||
if (_authenticated && publishMessage(serializedJson))
|
||||
return std::make_pair(msgId, serializedJson);
|
||||
}
|
||||
|
||||
bool CobraConnection::publishNext()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
|
||||
if (_messageQueue.empty()) return true;
|
||||
|
||||
auto&& msg = _messageQueue.back();
|
||||
if (!publishMessage(msg))
|
||||
{
|
||||
return _id - 1;
|
||||
return false;
|
||||
}
|
||||
else // Or else we enqueue
|
||||
// Slow code path is when we haven't connected yet (startup),
|
||||
// or when the connection drops for some reason.
|
||||
_messageQueue.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// publish is not thread safe as we are trying to reuse some Json objects.
|
||||
//
|
||||
CobraConnection::MsgId CobraConnection::publish(const Json::Value& channels,
|
||||
const Json::Value& msg)
|
||||
{
|
||||
auto p = prePublish(channels, msg, false);
|
||||
auto msgId = p.first;
|
||||
auto serializedJson = p.second;
|
||||
|
||||
//
|
||||
// 1. When we use batch mode, we just enqueue and will do the flush explicitely
|
||||
// 2. When we aren't authenticated yet to the cobra server, we need to enqueue
|
||||
// and retry later
|
||||
// 3. If the network connection was droped (WebSocket::send will return false),
|
||||
// it means the message won't be sent so we need to enqueue as well.
|
||||
//
|
||||
// The order of the conditionals is important.
|
||||
//
|
||||
if (_publishMode == CobraConnection_PublishMode_Batch || !_authenticated ||
|
||||
!publishMessage(serializedJson))
|
||||
{
|
||||
enqueue(serializedJson);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return msgId;
|
||||
}
|
||||
|
||||
void CobraConnection::subscribe(const std::string& channel,
|
||||
@ -559,22 +618,21 @@ namespace ix
|
||||
//
|
||||
bool CobraConnection::flushQueue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
|
||||
while (!_messageQueue.empty())
|
||||
while (!isQueueEmpty())
|
||||
{
|
||||
auto&& msg = _messageQueue.back();
|
||||
if (!publishMessage(msg))
|
||||
{
|
||||
_messageQueue.push_back(msg);
|
||||
return false;
|
||||
}
|
||||
_messageQueue.pop_back();
|
||||
bool ok = publishNext();
|
||||
if (!ok) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CobraConnection::isQueueEmpty()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_queueMutex);
|
||||
return _messageQueue.empty();
|
||||
}
|
||||
|
||||
bool CobraConnection::publishMessage(const std::string& serializedJson)
|
||||
{
|
||||
auto webSocketSendInfo = _webSocket->send(serializedJson);
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@ -20,6 +20,7 @@
|
||||
namespace ix
|
||||
{
|
||||
class WebSocket;
|
||||
struct SocketTLSOptions;
|
||||
|
||||
enum CobraConnectionEventType
|
||||
{
|
||||
@ -29,7 +30,8 @@ namespace ix
|
||||
CobraConnection_EventType_Closed = 3,
|
||||
CobraConnection_EventType_Subscribed = 4,
|
||||
CobraConnection_EventType_UnSubscribed = 5,
|
||||
CobraConnection_EventType_Published = 6
|
||||
CobraConnection_EventType_Published = 6,
|
||||
CobraConnection_EventType_Pong = 7
|
||||
};
|
||||
|
||||
enum CobraConnectionPublishMode
|
||||
@ -62,7 +64,8 @@ namespace ix
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions);
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Set the traffic tracker callback
|
||||
static void setTrafficTrackerCallback(const TrafficTrackerCallback& callback);
|
||||
@ -114,10 +117,26 @@ namespace ix
|
||||
/// Set the publish mode
|
||||
void setPublishMode(CobraConnectionPublishMode publishMode);
|
||||
|
||||
/// Query the publish mode
|
||||
CobraConnectionPublishMode getPublishMode();
|
||||
|
||||
/// Lifecycle management. Free resources when backgrounding
|
||||
void suspend();
|
||||
void resume();
|
||||
|
||||
/// Prepare a message for transmission
|
||||
/// (update the pdu, compute a msgId, serialize json to a string)
|
||||
std::pair<CobraConnection::MsgId, std::string> prePublish(
|
||||
const Json::Value& channels,
|
||||
const Json::Value& msg,
|
||||
bool addToQueue);
|
||||
|
||||
/// Attempt to send next message from the internal queue
|
||||
bool publishNext();
|
||||
|
||||
// An invalid message id, signifying an error.
|
||||
static constexpr MsgId kInvalidMsgId = 0;
|
||||
|
||||
private:
|
||||
bool sendHandshakeMessage();
|
||||
bool handleHandshakeResponse(const Json::Value& data);
|
||||
@ -147,6 +166,9 @@ namespace ix
|
||||
uint64_t msgId = std::numeric_limits<uint64_t>::max());
|
||||
void invokeErrorCallback(const std::string& errorMsg, const std::string& serializedPdu);
|
||||
|
||||
/// Tells whether the internal queue is empty or not
|
||||
bool isQueueEmpty();
|
||||
|
||||
///
|
||||
/// Member variables
|
||||
///
|
||||
@ -165,6 +187,7 @@ namespace ix
|
||||
Json::Value _pdu;
|
||||
Json::FastWriter _jsonWriter;
|
||||
mutable std::mutex _jsonWriterMutex;
|
||||
std::mutex _prePublishMutex;
|
||||
|
||||
/// Traffic tracker callback
|
||||
static TrafficTrackerCallback _trafficTrackerCallback;
|
||||
@ -193,6 +216,9 @@ namespace ix
|
||||
|
||||
// Each pdu sent should have an incremental unique id
|
||||
std::atomic<uint64_t> _id;
|
||||
|
||||
// Frequency at which we send a websocket ping to the backing cobra connection
|
||||
static constexpr int kPingIntervalSecs = 30;
|
||||
};
|
||||
|
||||
} // namespace ix
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsPublisher.h"
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
@ -31,14 +32,15 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
// Configure the satori connection and start its publish background thread
|
||||
_cobra_metrics_theaded_publisher.start();
|
||||
|
||||
_cobra_metrics_theaded_publisher.configure(appkey, endpoint, channel,
|
||||
rolename, rolesecret,
|
||||
enablePerMessageDeflate);
|
||||
enablePerMessageDeflate, socketTLSOptions);
|
||||
}
|
||||
|
||||
Json::Value& CobraMetricsPublisher::getGenericAttributes()
|
||||
@ -135,23 +137,23 @@ namespace ix
|
||||
return ms;
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::push(const std::string& id,
|
||||
const std::string& data,
|
||||
bool shouldPushTest)
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
||||
const std::string& data,
|
||||
bool shouldPushTest)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
if (!_enabled) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
Json::Value root;
|
||||
Json::Reader reader;
|
||||
if (!reader.parse(data, root)) return;
|
||||
if (!reader.parse(data, root)) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
push(id, root, shouldPushTest);
|
||||
return push(id, root, shouldPushTest);
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::push(const std::string& id,
|
||||
const CobraMetricsPublisher::Message& data)
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(const std::string& id,
|
||||
const CobraMetricsPublisher::Message& data)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
if (!_enabled) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
Json::Value root;
|
||||
for (auto it : data)
|
||||
@ -159,7 +161,7 @@ namespace ix
|
||||
root[it.first] = it.second;
|
||||
}
|
||||
|
||||
push(id, root);
|
||||
return push(id, root);
|
||||
}
|
||||
|
||||
bool CobraMetricsPublisher::shouldPush(const std::string& id) const
|
||||
@ -171,11 +173,12 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::push(const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
CobraConnection::MsgId CobraMetricsPublisher::push(
|
||||
const std::string& id,
|
||||
const Json::Value& data,
|
||||
bool shouldPushTest)
|
||||
{
|
||||
if (shouldPushTest && !shouldPush(id)) return;
|
||||
if (shouldPushTest && !shouldPush(id)) return CobraConnection::kInvalidMsgId;
|
||||
|
||||
setLastUpdate(id);
|
||||
|
||||
@ -205,7 +208,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Now actually enqueue the task
|
||||
_cobra_metrics_theaded_publisher.push(msg);
|
||||
return _cobra_metrics_theaded_publisher.push(msg);
|
||||
}
|
||||
|
||||
void CobraMetricsPublisher::setPublishMode(CobraConnectionPublishMode publishMode)
|
@ -9,12 +9,14 @@
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct SocketTLSOptions;
|
||||
|
||||
class CobraMetricsPublisher
|
||||
{
|
||||
public:
|
||||
@ -43,7 +45,8 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Setter for the list of blacklisted metrics ids.
|
||||
/// That list is sorted internally for fast lookups
|
||||
@ -59,8 +62,9 @@ namespace ix
|
||||
|
||||
/// Simple interface, list of key value pairs where typeof(key) == typeof(value) == string
|
||||
typedef std::unordered_map<std::string, std::string> Message;
|
||||
void push(const std::string& id,
|
||||
const CobraMetricsPublisher::Message& data = CobraMetricsPublisher::Message());
|
||||
CobraConnection::MsgId push(
|
||||
const std::string& id,
|
||||
const CobraMetricsPublisher::Message& data = CobraMetricsPublisher::Message());
|
||||
|
||||
/// Richer interface using json, which supports types (bool, int, float) and hierarchies of
|
||||
/// elements
|
||||
@ -69,10 +73,10 @@ namespace ix
|
||||
/// shouldPush method for places where we want to be as lightweight as possible when
|
||||
/// collecting metrics. When set to false, it is used so that we don't do double work when
|
||||
/// computing whether a metrics should be sent or not.
|
||||
void push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
|
||||
CobraConnection::MsgId push(const std::string& id, const Json::Value& data, bool shouldPushTest = true);
|
||||
|
||||
/// Interface used by lua. msg is a json encoded string.
|
||||
void push(const std::string& id, const std::string& data, bool shouldPushTest = true);
|
||||
CobraConnection::MsgId push(const std::string& id, const std::string& data, bool shouldPushTest = true);
|
||||
|
||||
/// Tells whether a metric can be pushed.
|
||||
/// A metric can be pushed if it satisfies those conditions:
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <algorithm>
|
||||
@ -13,6 +14,7 @@
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -63,6 +65,10 @@ namespace ix
|
||||
{
|
||||
ss << "Published message " << msgId << " acked";
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
||||
{
|
||||
ss << "Received websocket pong";
|
||||
}
|
||||
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
});
|
||||
@ -91,27 +97,25 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_channel = channel;
|
||||
|
||||
ix::IXCoreLogger::Log(socketTLSOptions.getDescription().c_str());
|
||||
|
||||
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(enablePerMessageDeflate);
|
||||
_cobra_connection.configure(appkey, endpoint,
|
||||
rolename, rolesecret,
|
||||
webSocketPerMessageDeflateOptions);
|
||||
webSocketPerMessageDeflateOptions, socketTLSOptions);
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind,
|
||||
const Json::Value& msg)
|
||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||
{
|
||||
// Enqueue the task
|
||||
{
|
||||
// acquire lock
|
||||
std::unique_lock<std::mutex> lock(_queue_mutex);
|
||||
|
||||
// add the task
|
||||
_queue.push(std::make_pair(messageKind, msg));
|
||||
} // release lock
|
||||
_queue.push(messageKind);
|
||||
}
|
||||
|
||||
// wake up one thread
|
||||
_condition.notify_one();
|
||||
@ -131,11 +135,6 @@ namespace ix
|
||||
{
|
||||
setThreadName("CobraMetricsPublisher");
|
||||
|
||||
Json::Value channels;
|
||||
channels.append(std::string());
|
||||
channels.append(std::string());
|
||||
const std::string messageIdKey("id");
|
||||
|
||||
_cobra_connection.connect();
|
||||
|
||||
while (true)
|
||||
@ -156,11 +155,8 @@ namespace ix
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = _queue.front();
|
||||
messageKind = _queue.front();
|
||||
_queue.pop();
|
||||
|
||||
messageKind = item.first;
|
||||
msg = item.second;
|
||||
}
|
||||
|
||||
switch (messageKind)
|
||||
@ -179,37 +175,47 @@ namespace ix
|
||||
|
||||
case MessageKind::Message:
|
||||
{
|
||||
;
|
||||
if (_cobra_connection.getPublishMode() == CobraConnection_PublishMode_Immediate)
|
||||
{
|
||||
_cobra_connection.publishNext();
|
||||
}
|
||||
}; break;
|
||||
}
|
||||
|
||||
//
|
||||
// Publish to multiple channels. This let the consumer side
|
||||
// easily subscribe to all message of a certain type, without having
|
||||
// to do manipulations on the messages on the server side.
|
||||
//
|
||||
channels[0] = _channel;
|
||||
if (msg.isMember(messageIdKey))
|
||||
{
|
||||
channels[1] = msg[messageIdKey];
|
||||
}
|
||||
_cobra_connection.publish(channels, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::push(const Json::Value& msg)
|
||||
CobraConnection::MsgId CobraMetricsThreadedPublisher::push(const Json::Value& msg)
|
||||
{
|
||||
pushMessage(MessageKind::Message, msg);
|
||||
static const std::string messageIdKey("id");
|
||||
|
||||
//
|
||||
// Publish to multiple channels. This let the consumer side
|
||||
// easily subscribe to all message of a certain type, without having
|
||||
// to do manipulations on the messages on the server side.
|
||||
//
|
||||
Json::Value channels;
|
||||
|
||||
channels.append(_channel);
|
||||
if (msg.isMember(messageIdKey))
|
||||
{
|
||||
channels.append(msg[messageIdKey]);
|
||||
}
|
||||
auto res = _cobra_connection.prePublish(channels, msg, true);
|
||||
auto msgId = res.first;
|
||||
|
||||
pushMessage(MessageKind::Message);
|
||||
|
||||
return msgId;
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::suspend()
|
||||
{
|
||||
pushMessage(MessageKind::Suspend, Json::Value());
|
||||
pushMessage(MessageKind::Suspend);
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::resume()
|
||||
{
|
||||
pushMessage(MessageKind::Resume, Json::Value());
|
||||
pushMessage(MessageKind::Resume);
|
||||
}
|
||||
|
||||
bool CobraMetricsThreadedPublisher::isConnected() const
|
@ -9,7 +9,7 @@
|
||||
#include "IXCobraConnection.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@ -18,6 +18,8 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct SocketTLSOptions;
|
||||
|
||||
class CobraMetricsThreadedPublisher
|
||||
{
|
||||
public:
|
||||
@ -30,14 +32,15 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Start the worker thread, used for background publishing
|
||||
void start();
|
||||
|
||||
/// Push a msg to our queue of messages to be published to cobra on the background
|
||||
// thread. Main user right now is the Cobra Metrics System
|
||||
void push(const Json::Value& msg);
|
||||
CobraConnection::MsgId push(const Json::Value& msg);
|
||||
|
||||
/// Set cobra connection publish mode
|
||||
void setPublishMode(CobraConnectionPublishMode publishMode);
|
||||
@ -64,7 +67,7 @@ namespace ix
|
||||
};
|
||||
|
||||
/// Push a message to be processed by the background thread
|
||||
void pushMessage(MessageKind messageKind, const Json::Value& msg);
|
||||
void pushMessage(MessageKind messageKind);
|
||||
|
||||
/// Get a wait time which is increasing exponentially based on the number of retries
|
||||
uint64_t getWaitTimeExp(int retry_count);
|
||||
@ -94,7 +97,7 @@ namespace ix
|
||||
/// that it should wake up and take care of publishing it to cobra
|
||||
/// To shutdown the worker thread one has to set the _stop boolean to true.
|
||||
/// This is done in the destructor
|
||||
std::queue<std::pair<MessageKind, Json::Value>> _queue;
|
||||
std::queue<MessageKind> _queue;
|
||||
mutable std::mutex _queue_mutex;
|
||||
std::condition_variable _condition;
|
||||
std::atomic<bool> _stop;
|
19
ixcore/CMakeLists.txt
Normal file
19
ixcore/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXCORE_SOURCES
|
||||
ixcore/utils/IXCoreLogger.cpp
|
||||
)
|
||||
|
||||
set (IXCORE_HEADERS
|
||||
ixcore/utils/IXCoreLogger.h
|
||||
)
|
||||
|
||||
add_library(ixcore STATIC
|
||||
${IXCORE_SOURCES}
|
||||
${IXCORE_HEADERS}
|
||||
)
|
||||
|
||||
target_include_directories( ixcore PUBLIC . )
|
54
ixcrypto/CMakeLists.txt
Normal file
54
ixcrypto/CMakeLists.txt
Normal file
@ -0,0 +1,54 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../CMake;${CMAKE_MODULE_PATH}")
|
||||
|
||||
set (IXCRYPTO_SOURCES
|
||||
ixcrypto/IXHMac.cpp
|
||||
ixcrypto/IXBase64.cpp
|
||||
ixcrypto/IXUuid.cpp
|
||||
ixcrypto/IXHash.cpp
|
||||
)
|
||||
|
||||
set (IXCRYPTO_HEADERS
|
||||
ixcrypto/IXHMac.h
|
||||
ixcrypto/IXBase64.h
|
||||
ixcrypto/IXUuid.h
|
||||
ixcrypto/IXHash.h
|
||||
)
|
||||
|
||||
add_library(ixcrypto STATIC
|
||||
${IXCRYPTO_SOURCES}
|
||||
${IXCRYPTO_HEADERS}
|
||||
)
|
||||
|
||||
set(IXCRYPTO_INCLUDE_DIRS
|
||||
.
|
||||
../ixcore)
|
||||
|
||||
target_include_directories( ixcrypto PUBLIC ${IXCRYPTO_INCLUDE_DIRS} )
|
||||
|
||||
# hmac computation needs a crypto library
|
||||
|
||||
if (WIN32)
|
||||
set(USE_MBED_TLS TRUE)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_TLS)
|
||||
if (USE_MBED_TLS)
|
||||
find_package(MbedTLS REQUIRED)
|
||||
target_include_directories(ixcrypto PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
||||
target_link_libraries(ixcrypto ${MBEDTLS_LIBRARIES})
|
||||
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_MBED_TLS)
|
||||
elseif (APPLE)
|
||||
elseif (WIN32)
|
||||
else()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(ixcrypto ${OPENSSL_LIBRARIES})
|
||||
target_compile_definitions(ixcrypto PUBLIC IXCRYPTO_USE_OPEN_SSL)
|
||||
endif()
|
||||
|
@ -35,15 +35,21 @@ namespace ix
|
||||
"0123456789+/";
|
||||
|
||||
std::string base64_encode(const std::string& data, size_t len)
|
||||
{
|
||||
const char* bytes_to_encode = data.c_str();
|
||||
return base64_encode(bytes_to_encode, len);
|
||||
}
|
||||
|
||||
std::string base64_encode(const char* bytes_to_encode, size_t len)
|
||||
{
|
||||
std::string ret;
|
||||
ret.reserve(((len + 2) / 3) * 4);
|
||||
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
unsigned char char_array_3[3];
|
||||
unsigned char char_array_4[4];
|
||||
|
||||
const char* bytes_to_encode = data.c_str();
|
||||
|
||||
while(len--)
|
||||
{
|
||||
char_array_3[i++] = *(bytes_to_encode++);
|
||||
@ -95,6 +101,7 @@ namespace ix
|
||||
int in_ = 0;
|
||||
unsigned char char_array_4[4], char_array_3[3];
|
||||
std::string ret;
|
||||
ret.reserve(((in_len + 3) / 4) * 3);
|
||||
|
||||
while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_]))
|
||||
{
|
@ -11,5 +11,6 @@
|
||||
namespace ix
|
||||
{
|
||||
std::string base64_encode(const std::string& data, size_t len);
|
||||
std::string base64_encode(const char* data, size_t len);
|
||||
std::string base64_decode(const std::string& encoded_string);
|
||||
} // namespace ix
|
@ -7,12 +7,14 @@
|
||||
#include "IXHMac.h"
|
||||
#include "IXBase64.h"
|
||||
|
||||
#if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
# include <mbedtls/md.h>
|
||||
#elif defined(__APPLE__)
|
||||
# include <CommonCrypto/CommonHMAC.h>
|
||||
#else
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
# include <openssl/hmac.h>
|
||||
#else
|
||||
# error "Unsupported configuration"
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
@ -22,7 +24,7 @@ namespace ix
|
||||
constexpr size_t hashSize = 16;
|
||||
unsigned char hash[hashSize];
|
||||
|
||||
#if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
#if defined(IXCRYPTO_USE_MBED_TLS)
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5),
|
||||
(unsigned char *) key.c_str(), key.size(),
|
||||
(unsigned char *) data.c_str(), data.size(),
|
||||
@ -32,11 +34,13 @@ namespace ix
|
||||
key.c_str(), key.size(),
|
||||
data.c_str(), data.size(),
|
||||
&hash);
|
||||
#else
|
||||
#elif defined(IXCRYPTO_USE_OPEN_SSL)
|
||||
HMAC(EVP_md5(),
|
||||
key.c_str(), (int) key.size(),
|
||||
(unsigned char *) data.c_str(), (int) data.size(),
|
||||
(unsigned char *) hash, nullptr);
|
||||
#else
|
||||
# error "Unsupported configuration"
|
||||
#endif
|
||||
|
||||
std::string hashString(reinterpret_cast<char*>(hash), hashSize);
|
30
ixsentry/CMakeLists.txt
Normal file
30
ixsentry/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXSENTRY_SOURCES
|
||||
ixsentry/IXSentryClient.cpp
|
||||
)
|
||||
|
||||
set (IXSENTRY_HEADERS
|
||||
ixsentry/IXSentryClient.h
|
||||
)
|
||||
|
||||
add_library(ixsentry STATIC
|
||||
${IXSENTRY_SOURCES}
|
||||
${IXSENTRY_HEADERS}
|
||||
)
|
||||
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
set(IXSENTRY_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )
|
@ -8,17 +8,20 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SentryClient::SentryClient(const std::string& dsn) :
|
||||
_dsn(dsn),
|
||||
_validDsn(false),
|
||||
_luaFrameRegex("\t([^/]+):([0-9]+): in function '([^/]+)'")
|
||||
SentryClient::SentryClient(const std::string& dsn)
|
||||
: _dsn(dsn)
|
||||
, _validDsn(false)
|
||||
, _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]")
|
||||
, _httpClient(std::make_shared<HttpClient>(true))
|
||||
{
|
||||
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
|
||||
std::smatch group;
|
||||
@ -118,26 +121,6 @@ namespace ix
|
||||
{
|
||||
Json::Value payload;
|
||||
|
||||
payload["platform"] = "python";
|
||||
payload["sdk"]["name"] = "ws";
|
||||
payload["sdk"]["version"] = "1.0.0";
|
||||
payload["timestamp"] = SentryClient::getIso8601();
|
||||
|
||||
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
|
||||
|
||||
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
|
||||
std::string stack(msg["data"][stackTraceFieldName].asString());
|
||||
|
||||
Json::Value exception;
|
||||
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
|
||||
exception["value"] = isNoisyTypes ? parseExceptionName(stack) : msg["data"]["message"];
|
||||
|
||||
payload["exception"].append(exception);
|
||||
|
||||
Json::Value extra;
|
||||
extra["cobra_event"] = msg;
|
||||
extra["cobra_event"] = msg;
|
||||
|
||||
//
|
||||
// "tags": [
|
||||
// [
|
||||
@ -146,8 +129,61 @@ namespace ix
|
||||
// ],
|
||||
// ]
|
||||
//
|
||||
Json::Value tags;
|
||||
Json::Value tags(Json::arrayValue);
|
||||
|
||||
payload["platform"] = "python";
|
||||
payload["sdk"]["name"] = "ws";
|
||||
payload["sdk"]["version"] = IX_WEBSOCKET_VERSION;
|
||||
payload["timestamp"] = SentryClient::getIso8601();
|
||||
|
||||
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
|
||||
|
||||
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
|
||||
std::string stack;
|
||||
std::string message;
|
||||
|
||||
if (isNoisyTypes)
|
||||
{
|
||||
stack = msg["data"][stackTraceFieldName].asString();
|
||||
message = parseExceptionName(stack);
|
||||
}
|
||||
else // logging
|
||||
{
|
||||
if (msg["data"].isMember("info"))
|
||||
{
|
||||
stack = msg["data"]["info"][stackTraceFieldName].asString();
|
||||
message = msg["data"]["info"]["message"].asString();
|
||||
|
||||
if (msg["data"].isMember("tags"))
|
||||
{
|
||||
auto members = msg["data"]["tags"].getMemberNames();
|
||||
|
||||
for (auto member : members)
|
||||
{
|
||||
Json::Value tag;
|
||||
tag.append(member);
|
||||
tag.append(msg["data"]["tags"][member]);
|
||||
tags.append(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack = msg["data"][stackTraceFieldName].asString();
|
||||
message = msg["data"]["message"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value exception;
|
||||
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
|
||||
exception["value"] = message;
|
||||
|
||||
payload["exception"].append(exception);
|
||||
|
||||
Json::Value extra;
|
||||
extra["cobra_event"] = msg;
|
||||
|
||||
// Builtin tags
|
||||
Json::Value gameTag;
|
||||
gameTag.append("game");
|
||||
gameTag.append(msg["device"]["game"]);
|
||||
@ -163,50 +199,75 @@ namespace ix
|
||||
environmentTag.append(msg["device"]["environment"]);
|
||||
tags.append(environmentTag);
|
||||
|
||||
Json::Value clientVersionTag;
|
||||
clientVersionTag.append("client_version");
|
||||
clientVersionTag.append(msg["device"]["app_version"]);
|
||||
tags.append(clientVersionTag);
|
||||
|
||||
payload["tags"] = tags;
|
||||
|
||||
return _jsonWriter.write(payload);
|
||||
}
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg,
|
||||
bool verbose)
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||
{
|
||||
auto args = _httpClient.createRequest();
|
||||
auto args = _httpClient->createRequest();
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg)
|
||||
{
|
||||
spdlog::info("request logger: {}", msg);
|
||||
};
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
|
||||
std::string body = computePayload(msg);
|
||||
HttpResponsePtr response = _httpClient.post(_url, body, args);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
}
|
||||
|
||||
spdlog::info("Upload size: {}", response->uploadSize);
|
||||
spdlog::info("Download size: {}", response->downloadSize);
|
||||
|
||||
spdlog::info("Status: {}", response->statusCode);
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
spdlog::info("error message: {}", response->errorMsg);
|
||||
}
|
||||
|
||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
spdlog::info("payload: {}", response->payload);
|
||||
}
|
||||
}
|
||||
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||
|
||||
return std::make_pair(response, body);
|
||||
}
|
||||
|
||||
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
||||
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "https://sentry.io/api/"
|
||||
<< project
|
||||
<< "/minidump?sentry_key="
|
||||
<< key;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
//
|
||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||
//
|
||||
void SentryClient::uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||
|
||||
auto args = _httpClient->createRequest();
|
||||
args->verb = HttpClient::kPost;
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->multipartBoundary = multipartBoundary;
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
|
||||
HttpFormDataParameters httpFormDataParameters;
|
||||
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
||||
|
||||
HttpParameters httpParameters;
|
||||
httpParameters["sentry"] = sentryMetadata;
|
||||
|
||||
args->url = computeUrl(project, key);
|
||||
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
|
||||
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
}
|
||||
} // namespace ix
|
@ -8,8 +8,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -21,13 +22,25 @@ namespace ix
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> send(const Json::Value& msg, bool verbose);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
void uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
std::string computeAuthHeader();
|
||||
std::string getIso8601();
|
||||
std::string computePayload(const Json::Value& msg);
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
std::string computeUrl(const std::string& project, const std::string& key);
|
||||
|
||||
void displayReponse(HttpResponsePtr response);
|
||||
|
||||
std::string _dsn;
|
||||
bool _validDsn;
|
||||
@ -41,7 +54,7 @@ namespace ix
|
||||
|
||||
std::regex _luaFrameRegex;
|
||||
|
||||
HttpClient _httpClient;
|
||||
std::shared_ptr<HttpClient> _httpClient;
|
||||
};
|
||||
|
||||
} // namespace ix
|
35
ixsnake/CMakeLists.txt
Normal file
35
ixsnake/CMakeLists.txt
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Author: Benjamin Sergeant
|
||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
#
|
||||
|
||||
set (IXSNAKE_SOURCES
|
||||
ixsnake/IXSnakeServer.cpp
|
||||
ixsnake/IXSnakeProtocol.cpp
|
||||
ixsnake/IXAppConfig.cpp
|
||||
ixsnake/IXRedisClient.cpp
|
||||
ixsnake/IXRedisServer.cpp
|
||||
)
|
||||
|
||||
set (IXSNAKE_HEADERS
|
||||
ixsnake/IXSnakeServer.h
|
||||
ixsnake/IXSnakeProtocol.h
|
||||
ixsnake/IXAppConfig.h
|
||||
ixsnake/IXRedisClient.h
|
||||
ixsnake/IXRedisServer.h
|
||||
)
|
||||
|
||||
add_library(ixsnake STATIC
|
||||
${IXSNAKE_SOURCES}
|
||||
${IXSNAKE_HEADERS}
|
||||
)
|
||||
|
||||
set(IXSNAKE_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
../ixcrypto
|
||||
../ixwebsocket
|
||||
../third_party)
|
||||
|
||||
target_include_directories( ixsnake PUBLIC ${IXSNAKE_INCLUDE_DIRS} )
|
@ -4,24 +4,20 @@
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXSnakeProtocol.h"
|
||||
#include "IXAppConfig.h"
|
||||
|
||||
#include "IXSnakeProtocol.h"
|
||||
#include <iostream>
|
||||
#include <ixcrypto/IXUuid.h>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
bool isAppKeyValid(
|
||||
const AppConfig& appConfig,
|
||||
std::string appkey)
|
||||
bool isAppKeyValid(const AppConfig& appConfig, std::string appkey)
|
||||
{
|
||||
return appConfig.apps.count(appkey) != 0;
|
||||
}
|
||||
|
||||
std::string getRoleSecret(
|
||||
const AppConfig& appConfig,
|
||||
std::string appkey,
|
||||
std::string role)
|
||||
std::string getRoleSecret(const AppConfig& appConfig, std::string appkey, std::string role)
|
||||
{
|
||||
if (!isAppKeyValid(appConfig, appkey))
|
||||
{
|
||||
@ -49,4 +45,4 @@ namespace snake
|
||||
std::cout << "redis password: " << appConfig.redisPassword << std::endl;
|
||||
std::cout << "redis port: " << appConfig.redisPort << std::endl;
|
||||
}
|
||||
}
|
||||
} // namespace snake
|
@ -6,9 +6,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
@ -26,6 +27,9 @@ namespace snake
|
||||
// AppKeys
|
||||
nlohmann::json apps;
|
||||
|
||||
// TLS options
|
||||
ix::SocketTLSOptions socketTLSOptions;
|
||||
|
||||
// Misc
|
||||
bool verbose;
|
||||
};
|
@ -5,14 +5,15 @@
|
||||
*/
|
||||
|
||||
#include "IXRedisClient.h"
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXSocketFactory.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -20,19 +21,29 @@ namespace ix
|
||||
{
|
||||
bool tls = false;
|
||||
std::string errorMsg;
|
||||
_socket = createSocket(tls, errorMsg);
|
||||
SocketTLSOptions tlsOptions;
|
||||
_socket = createSocket(tls, -1, errorMsg, tlsOptions);
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CancellationRequest cancellationRequest = []() -> bool
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
std::string errMsg;
|
||||
return _socket->connect(hostname, port, errMsg, nullptr);
|
||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||
}
|
||||
|
||||
bool RedisClient::auth(const std::string& password,
|
||||
std::string& response)
|
||||
void RedisClient::stop()
|
||||
{
|
||||
_stop = true;
|
||||
}
|
||||
|
||||
bool RedisClient::auth(const std::string& password, std::string& response)
|
||||
{
|
||||
response.clear();
|
||||
|
||||
@ -128,12 +139,14 @@ namespace ix
|
||||
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||
const OnRedisSubscribeCallback& callback)
|
||||
{
|
||||
_stop = false;
|
||||
|
||||
if (!_socket) return false;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "SUBSCRIBE ";
|
||||
ss << channel;
|
||||
ss << "\r\n";
|
||||
ss << "*2\r\n";
|
||||
ss << writeString("SUBSCRIBE");
|
||||
ss << writeString(channel);
|
||||
|
||||
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||
if (!sent)
|
||||
@ -159,7 +172,7 @@ namespace ix
|
||||
|
||||
if (!lineValid) return false;
|
||||
|
||||
// There are 5 items for the subscribe repply
|
||||
// There are 5 items for the subscribe reply
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
auto lineResult = _socket->readLine(nullptr);
|
||||
@ -175,13 +188,21 @@ namespace ix
|
||||
// Wait indefinitely for new messages
|
||||
while (true)
|
||||
{
|
||||
if (_stop) break;
|
||||
|
||||
// Wait until something is ready to read
|
||||
auto pollResult = _socket->isReadyToRead(-1);
|
||||
int timeoutMs = 10;
|
||||
auto pollResult = _socket->isReadyToRead(timeoutMs);
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pollResult == PollResultType::Timeout)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The first line of the response describe the return type,
|
||||
// => *3 (an array of 3 elements)
|
||||
auto lineResult = _socket->readLine(nullptr);
|
||||
@ -193,7 +214,7 @@ namespace ix
|
||||
int arraySize;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << line.substr(1, line.size()-1);
|
||||
ss << line.substr(1, line.size() - 1);
|
||||
ss >> arraySize;
|
||||
}
|
||||
|
||||
@ -210,7 +231,7 @@ namespace ix
|
||||
// => $7 (7 bytes)
|
||||
int stringSize;
|
||||
std::stringstream ss;
|
||||
ss << line.substr(1, line.size()-1);
|
||||
ss << line.substr(1, line.size() - 1);
|
||||
ss >> stringSize;
|
||||
|
||||
auto readResult = _socket->readBytes(stringSize, nullptr, nullptr);
|
||||
@ -231,4 +252,103 @@ namespace ix
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string RedisClient::prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "*5\r\n";
|
||||
ss << writeString("XADD");
|
||||
ss << writeString(stream);
|
||||
ss << writeString("*");
|
||||
ss << writeString("field");
|
||||
ss << writeString(message);
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string RedisClient::xadd(const std::string& stream,
|
||||
const std::string& message,
|
||||
std::string& errMsg)
|
||||
{
|
||||
errMsg.clear();
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
errMsg = "socket is not initialized";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string command = prepareXaddCommand(stream, message);
|
||||
|
||||
bool sent = _socket->writeBytes(command, nullptr);
|
||||
if (!sent)
|
||||
{
|
||||
errMsg = "Cannot write bytes to socket";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
return readXaddReply(errMsg);
|
||||
}
|
||||
|
||||
std::string RedisClient::readXaddReply(std::string& errMsg)
|
||||
{
|
||||
// Read result
|
||||
auto pollResult = _socket->isReadyToRead(-1);
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
errMsg = "Error while polling for result";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// First line is the string length
|
||||
auto lineResult = _socket->readLine(nullptr);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
errMsg = "Error while polling for result";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int stringSize;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << line.substr(1, line.size() - 1);
|
||||
ss >> stringSize;
|
||||
}
|
||||
|
||||
// Read the result, which is the stream id computed by the redis server
|
||||
lineResult = _socket->readLine(nullptr);
|
||||
lineValid = lineResult.first;
|
||||
line = lineResult.second;
|
||||
|
||||
std::string streamId = line.substr(0, stringSize - 1);
|
||||
return streamId;
|
||||
}
|
||||
|
||||
bool RedisClient::sendCommand(const std::string& commands, int commandsCount, std::string& errMsg)
|
||||
{
|
||||
bool sent = _socket->writeBytes(commands, nullptr);
|
||||
if (!sent)
|
||||
{
|
||||
errMsg = "Cannot write bytes to socket";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
for (int i = 0; i < commandsCount; ++i)
|
||||
{
|
||||
auto reply = readXaddReply(errMsg);
|
||||
if (reply == std::string())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
} // namespace ix
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
@ -19,22 +20,43 @@ namespace ix
|
||||
using OnRedisSubscribeResponseCallback = std::function<void(const std::string&)>;
|
||||
using OnRedisSubscribeCallback = std::function<void(const std::string&)>;
|
||||
|
||||
RedisClient() = default;
|
||||
RedisClient()
|
||||
: _stop(false)
|
||||
{
|
||||
}
|
||||
~RedisClient() = default;
|
||||
|
||||
bool connect(const std::string& hostname, int port);
|
||||
|
||||
bool auth(const std::string& password, std::string& response);
|
||||
|
||||
// Publish / Subscribe
|
||||
bool publish(const std::string& channel, const std::string& message, std::string& errMsg);
|
||||
|
||||
bool subscribe(const std::string& channel,
|
||||
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||
const OnRedisSubscribeCallback& callback);
|
||||
|
||||
// XADD
|
||||
std::string xadd(
|
||||
const std::string& channel,
|
||||
const std::string& message,
|
||||
std::string& errMsg);
|
||||
|
||||
std::string prepareXaddCommand(
|
||||
const std::string& stream,
|
||||
const std::string& message);
|
||||
|
||||
std::string readXaddReply(std::string& errMsg);
|
||||
|
||||
bool sendCommand(const std::string& commands, int commandsCount, std::string& errMsg);
|
||||
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::string writeString(const std::string& str);
|
||||
|
||||
std::shared_ptr<Socket> _socket;
|
||||
std::atomic<bool> _stop;
|
||||
};
|
||||
} // namespace ix
|
299
ixsnake/ixsnake/IXRedisServer.cpp
Normal file
299
ixsnake/ixsnake/IXRedisServer.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* IXRedisServer.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXRedisServer.h"
|
||||
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
#include <ixwebsocket/IXSocketConnect.h>
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include <ixwebsocket/IXCancellationRequest.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections)
|
||||
: SocketServer(port, host, backlog, maxConnections)
|
||||
, _connectedClientsCount(0)
|
||||
, _stopHandlingConnections(false)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
RedisServer::~RedisServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void RedisServer::stop()
|
||||
{
|
||||
stopAcceptingConnections();
|
||||
|
||||
_stopHandlingConnections = true;
|
||||
while (_connectedClientsCount != 0)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
_stopHandlingConnections = false;
|
||||
|
||||
SocketServer::stop();
|
||||
}
|
||||
|
||||
void RedisServer::handleConnection(std::shared_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
while (!_stopHandlingConnections)
|
||||
{
|
||||
std::vector<std::string> tokens;
|
||||
if (!parseRequest(socket, tokens))
|
||||
{
|
||||
if (_stopHandlingConnections)
|
||||
{
|
||||
logError("Cancellation requested");
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("Error parsing request");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// publish
|
||||
if (tokens[0] == "COMMAND")
|
||||
{
|
||||
success = handleCommand(socket, tokens);
|
||||
}
|
||||
else if (tokens[0] == "PUBLISH")
|
||||
{
|
||||
success = handlePublish(socket, tokens);
|
||||
}
|
||||
else if (tokens[0] == "SUBSCRIBE")
|
||||
{
|
||||
success = handleSubscribe(socket, tokens);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (_stopHandlingConnections)
|
||||
{
|
||||
logError("Cancellation requested");
|
||||
}
|
||||
else
|
||||
{
|
||||
logError("Error processing request for command: " + tokens[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanupSubscribers(socket);
|
||||
|
||||
logInfo("Connection closed for connection id " + connectionState->getId());
|
||||
connectionState->setTerminated();
|
||||
|
||||
_connectedClientsCount--;
|
||||
}
|
||||
|
||||
void RedisServer::cleanupSubscribers(std::shared_ptr<Socket> socket)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
for (auto&& it : _subscribers)
|
||||
{
|
||||
it.second.erase(socket);
|
||||
}
|
||||
|
||||
for (auto it : _subscribers)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscription id: " << it.first
|
||||
<< " #subscribers: " << it.second.size();
|
||||
|
||||
logInfo(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
size_t RedisServer::getConnectedClientsCount()
|
||||
{
|
||||
return _connectedClientsCount;
|
||||
}
|
||||
|
||||
bool RedisServer::startsWith(const std::string& str,
|
||||
const std::string& start)
|
||||
{
|
||||
return str.compare(0, start.length(), start) == 0;
|
||||
}
|
||||
|
||||
std::string RedisServer::writeString(const std::string& str)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "$";
|
||||
ss << str.size();
|
||||
ss << "\r\n";
|
||||
ss << str;
|
||||
ss << "\r\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool RedisServer::parseRequest(
|
||||
std::shared_ptr<Socket> socket,
|
||||
std::vector<std::string>& tokens)
|
||||
{
|
||||
// Parse first line
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
auto lineResult = socket->readLine(cb);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid) return false;
|
||||
|
||||
std::string str = line.substr(1);
|
||||
std::stringstream ss;
|
||||
ss << str;
|
||||
int count;
|
||||
ss >> count;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
auto lineResult = socket->readLine(cb);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
if (!lineValid) return false;
|
||||
|
||||
int stringSize;
|
||||
std::stringstream ss;
|
||||
ss << line.substr(1, line.size() - 1);
|
||||
ss >> stringSize;
|
||||
|
||||
auto readResult = socket->readBytes(stringSize, nullptr, nullptr);
|
||||
|
||||
if (!readResult.first) return false;
|
||||
|
||||
// read last 2 bytes (\r\n)
|
||||
char c;
|
||||
socket->readByte(&c, nullptr);
|
||||
socket->readByte(&c, nullptr);
|
||||
|
||||
tokens.push_back(readResult.second);
|
||||
}
|
||||
|
||||
for (auto&& token : tokens)
|
||||
{
|
||||
std::cerr << token << " ";
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleCommand(
|
||||
std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 1) return false;
|
||||
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
std::stringstream ss;
|
||||
|
||||
// return 2 nested arrays
|
||||
ss << "*2\r\n";
|
||||
|
||||
//
|
||||
// publish
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("publish"); // 1
|
||||
ss << ":3\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":2\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
//
|
||||
// subscribe
|
||||
//
|
||||
ss << "*6\r\n";
|
||||
ss << writeString("subscribe"); // 1
|
||||
ss << ":2\r\n"; // 2
|
||||
ss << "*0\r\n"; // 3
|
||||
ss << ":1\r\n"; // 4
|
||||
ss << ":1\r\n"; // 5
|
||||
ss << ":1\r\n"; // 6
|
||||
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handleSubscribe(
|
||||
std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 2) return false;
|
||||
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
std::string channel = tokens[1];
|
||||
|
||||
// Respond
|
||||
socket->writeBytes("*3\r\n", cb);
|
||||
socket->writeBytes(writeString("subscribe"), cb);
|
||||
socket->writeBytes(writeString(channel), cb);
|
||||
socket->writeBytes(":1\r\n", cb);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_subscribers[channel].insert(socket);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RedisServer::handlePublish(
|
||||
std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens)
|
||||
{
|
||||
if (tokens.size() != 3) return false;
|
||||
|
||||
auto cb = makeCancellationRequestWithTimeout(30, _stopHandlingConnections);
|
||||
std::string channel = tokens[1];
|
||||
std::string data = tokens[2];
|
||||
|
||||
// now dispatch the message to subscribers (write custom method)
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
auto it = _subscribers.find(channel);
|
||||
if (it == _subscribers.end())
|
||||
{
|
||||
// return the number of clients that received the message, 0 in that case
|
||||
socket->writeBytes(":0\r\n", cb);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto subscribers = it->second;
|
||||
for (auto jt : subscribers)
|
||||
{
|
||||
jt->writeBytes("*3\r\n", cb);
|
||||
jt->writeBytes(writeString("message"), cb);
|
||||
jt->writeBytes(writeString(channel), cb);
|
||||
jt->writeBytes(writeString(data), cb);
|
||||
}
|
||||
|
||||
// return the number of clients that received the message.
|
||||
std::stringstream ss;
|
||||
ss << ":"
|
||||
<< std::to_string(subscribers.size())
|
||||
<< "\r\n";
|
||||
socket->writeBytes(ss.str(), cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ix
|
67
ixsnake/ixsnake/IXRedisServer.h
Normal file
67
ixsnake/ixsnake/IXRedisServer.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* IXRedisServer.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocketServer.h"
|
||||
#include "IXSocket.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility> // pair
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class RedisServer final : public SocketServer
|
||||
{
|
||||
public:
|
||||
RedisServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||
virtual ~RedisServer();
|
||||
virtual void stop() final;
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
std::atomic<int> _connectedClientsCount;
|
||||
|
||||
// Subscribers
|
||||
// We could store connection states in there, to add better debugging
|
||||
// since a connection state has a readable ID
|
||||
std::map<std::string, std::set<std::shared_ptr<Socket>>> _subscribers;
|
||||
std::mutex _mutex;
|
||||
|
||||
std::atomic<bool> _stopHandlingConnections;
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::shared_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
bool startsWith(const std::string& str, const std::string& start);
|
||||
std::string writeString(const std::string& str);
|
||||
|
||||
bool parseRequest(
|
||||
std::shared_ptr<Socket> socket,
|
||||
std::vector<std::string>& tokens);
|
||||
|
||||
bool handlePublish(std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleSubscribe(std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
|
||||
bool handleCommand(std::shared_ptr<Socket> socket,
|
||||
const std::vector<std::string>& tokens);
|
||||
|
||||
void cleanupSubscribers(std::shared_ptr<Socket> socket);
|
||||
};
|
||||
} // namespace ix
|
61
ixsnake/ixsnake/IXSnakeConnectionState.h
Normal file
61
ixsnake/ixsnake/IXSnakeConnectionState.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* IXSnakeConnectionState.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXRedisClient.h"
|
||||
#include <future>
|
||||
#include <ixwebsocket/IXConnectionState.h>
|
||||
#include <string>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
class SnakeConnectionState : public ix::ConnectionState
|
||||
{
|
||||
public:
|
||||
std::string getNonce()
|
||||
{
|
||||
return _nonce;
|
||||
}
|
||||
|
||||
void setNonce(const std::string& nonce)
|
||||
{
|
||||
_nonce = nonce;
|
||||
}
|
||||
|
||||
std::string appkey()
|
||||
{
|
||||
return _appkey;
|
||||
}
|
||||
void setAppkey(const std::string& appkey)
|
||||
{
|
||||
_appkey = appkey;
|
||||
}
|
||||
|
||||
std::string role()
|
||||
{
|
||||
return _role;
|
||||
}
|
||||
void setRole(const std::string& role)
|
||||
{
|
||||
_role = role;
|
||||
}
|
||||
|
||||
ix::RedisClient& redisClient()
|
||||
{
|
||||
return _redisClient;
|
||||
}
|
||||
|
||||
std::future<void> fut;
|
||||
|
||||
private:
|
||||
std::string _nonce;
|
||||
std::string _role;
|
||||
std::string _appkey;
|
||||
|
||||
ix::RedisClient _redisClient;
|
||||
};
|
||||
} // namespace snake
|
@ -6,41 +6,33 @@
|
||||
|
||||
#include "IXSnakeProtocol.h"
|
||||
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "IXAppConfig.h"
|
||||
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
void handleError(
|
||||
const std::string& action,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
nlohmann::json pdu,
|
||||
const std::string& errMsg)
|
||||
void handleError(const std::string& action,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
nlohmann::json pdu,
|
||||
const std::string& errMsg)
|
||||
{
|
||||
std::string actionError(action);
|
||||
actionError += "/error";
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", actionError},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {
|
||||
{"reason", errMsg}
|
||||
}}
|
||||
};
|
||||
{"action", actionError}, {"id", pdu.value("id", 1)}, {"body", {{"reason", errMsg}}}};
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
void handleHandshake(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
void handleHandshake(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::string role = pdu["body"]["data"]["role"];
|
||||
|
||||
@ -50,39 +42,29 @@ namespace snake
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/handshake/ok"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {
|
||||
{"data", {
|
||||
{"nonce", state->getNonce()},
|
||||
{"connection_id", state->getId()}
|
||||
}},
|
||||
}}
|
||||
};
|
||||
{"body",
|
||||
{
|
||||
{"data", {{"nonce", state->getNonce()}, {"connection_id", state->getId()}}},
|
||||
}}};
|
||||
|
||||
auto serializedResponse = response.dump();
|
||||
std::cout << "response = " << serializedResponse << std::endl;
|
||||
|
||||
ws->sendText(serializedResponse);
|
||||
}
|
||||
|
||||
void handleAuth(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
void handleAuth(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
auto secret = getRoleSecret(appConfig, state->appkey(), state->role());
|
||||
std::cout << "secret = " << secret << std::endl;
|
||||
|
||||
if (secret.empty())
|
||||
{
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/authenticate/error"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {
|
||||
{"error", "authentication_failed"},
|
||||
{"reason", "invalid secret"}
|
||||
}}
|
||||
};
|
||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid secret"}}}};
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
@ -91,39 +73,25 @@ namespace snake
|
||||
auto serverHash = ix::hmac(nonce, secret);
|
||||
std::string clientHash = pdu["body"]["credentials"]["hash"];
|
||||
|
||||
if (appConfig.verbose)
|
||||
{
|
||||
std::cout << serverHash << std::endl;
|
||||
std::cout << clientHash << std::endl;
|
||||
}
|
||||
|
||||
if (serverHash != clientHash)
|
||||
{
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/authenticate/error"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {
|
||||
{"error", "authentication_failed"},
|
||||
{"reason", "invalid hash"}
|
||||
}}
|
||||
};
|
||||
{"body", {{"error", "authentication_failed"}, {"reason", "invalid hash"}}}};
|
||||
ws->sendText(response.dump());
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", "auth/authenticate/ok"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {}}
|
||||
};
|
||||
{"action", "auth/authenticate/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
void handlePublish(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
void handlePublish(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::vector<std::string> channels;
|
||||
|
||||
@ -150,9 +118,7 @@ namespace snake
|
||||
for (auto&& channel : channels)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << state->appkey()
|
||||
<< "::"
|
||||
<< channel;
|
||||
ss << state->appkey() << "::" << channel;
|
||||
|
||||
std::string errMsg;
|
||||
if (!state->redisClient().publish(ss.str(), pdu.dump(), errMsg))
|
||||
@ -163,24 +129,26 @@ namespace snake
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/publish/ok"}, {"id", pdu.value("id", 1)}, {"body", {}}};
|
||||
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
//
|
||||
// FIXME: this is not cancellable. We should be able to cancel the redis subscription
|
||||
//
|
||||
void handleRedisSubscription(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
void handleRedisSubscription(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
std::string channel = pdu["body"]["channel"];
|
||||
std::string subscriptionId = channel;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << state->appkey()
|
||||
<< "::"
|
||||
<< channel;
|
||||
ss << state->appkey() << "::" << channel;
|
||||
|
||||
std::string appChannel(ss.str());
|
||||
|
||||
@ -199,8 +167,6 @@ namespace snake
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Connected to redis host " << hostname << ":" << port << std::endl;
|
||||
|
||||
// Now authenticate, if needed
|
||||
if (!appConfig.redisPassword.empty())
|
||||
{
|
||||
@ -212,42 +178,40 @@ namespace snake
|
||||
handleError("rtm/subscribe", ws, pdu, ss.str());
|
||||
return;
|
||||
}
|
||||
std::cout << "Auth response: " << authResponse << ":" << port << std::endl;
|
||||
}
|
||||
|
||||
int id = 0;
|
||||
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr)
|
||||
{
|
||||
auto callback = [ws, &id, &subscriptionId](const std::string& messageStr) {
|
||||
auto msg = nlohmann::json::parse(messageStr);
|
||||
|
||||
msg = msg["body"]["message"];
|
||||
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/subscription/data"},
|
||||
{"id", id++},
|
||||
{"body", {
|
||||
{"subscription_id", subscriptionId},
|
||||
{"messages", {{msg}}}
|
||||
}}
|
||||
};
|
||||
{"body", {{"subscription_id", subscriptionId}, {"messages", {msg}}}}};
|
||||
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
|
||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse)
|
||||
{
|
||||
std::cout << "Redis subscribe response: " << redisResponse << std::endl;
|
||||
auto responseCallback = [ws, pdu, &subscriptionId](const std::string& redisResponse) {
|
||||
std::stringstream ss;
|
||||
ss << "Redis Response: " << redisResponse << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
|
||||
// Success
|
||||
nlohmann::json response = {
|
||||
{"action", "rtm/subscribe/ok"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {
|
||||
{"subscription_id", subscriptionId}
|
||||
}}
|
||||
};
|
||||
nlohmann::json response = {{"action", "rtm/subscribe/ok"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"subscription_id", subscriptionId}}}};
|
||||
ws->sendText(response.dump());
|
||||
};
|
||||
|
||||
std::cerr << "Subscribing to " << appChannel << "..." << std::endl;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Subscribing to " << appChannel << "...";
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
}
|
||||
|
||||
if (!redisClient.subscribe(appChannel, responseCallback, callback))
|
||||
{
|
||||
std::stringstream ss;
|
||||
@ -257,31 +221,38 @@ namespace snake
|
||||
}
|
||||
}
|
||||
|
||||
void handleSubscribe(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
void handleSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
state->fut = std::async(std::launch::async,
|
||||
handleRedisSubscription,
|
||||
state,
|
||||
ws,
|
||||
appConfig,
|
||||
pdu);
|
||||
state->fut =
|
||||
std::async(std::launch::async, handleRedisSubscription, state, ws, appConfig, pdu);
|
||||
}
|
||||
|
||||
void processCobraMessage(
|
||||
std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const std::string& str)
|
||||
void handleUnSubscribe(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const nlohmann::json& pdu)
|
||||
{
|
||||
// extract subscription_id
|
||||
auto body = pdu["body"];
|
||||
auto subscriptionId = body["subscription_id"];
|
||||
|
||||
state->redisClient().stop();
|
||||
|
||||
nlohmann::json response = {{"action", "rtm/unsubscribe/ok"},
|
||||
{"id", pdu.value("id", 1)},
|
||||
{"body", {{"subscription_id", subscriptionId}}}};
|
||||
ws->sendText(response.dump());
|
||||
}
|
||||
|
||||
void processCobraMessage(std::shared_ptr<SnakeConnectionState> state,
|
||||
std::shared_ptr<ix::WebSocket> ws,
|
||||
const AppConfig& appConfig,
|
||||
const std::string& str)
|
||||
{
|
||||
auto pdu = nlohmann::json::parse(str);
|
||||
std::cout << "Got " << str << std::endl;
|
||||
|
||||
auto action = pdu["action"];
|
||||
std::cout << "action = " << action << std::endl;
|
||||
|
||||
if (action == "auth/handshake")
|
||||
{
|
||||
@ -299,9 +270,13 @@ namespace snake
|
||||
{
|
||||
handleSubscribe(state, ws, appConfig, pdu);
|
||||
}
|
||||
else if (action == "rtm/unsubscribe")
|
||||
{
|
||||
handleUnSubscribe(state, ws, pdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unhandled action: " << action << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace snake
|
@ -4,21 +4,23 @@
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <IXSnakeServer.h>
|
||||
#include <IXSnakeProtocol.h>
|
||||
#include <IXSnakeConnectionState.h>
|
||||
#include <IXAppConfig.h>
|
||||
#include "IXSnakeServer.h"
|
||||
|
||||
#include "IXAppConfig.h"
|
||||
#include "IXSnakeConnectionState.h"
|
||||
#include "IXSnakeProtocol.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
|
||||
namespace snake
|
||||
{
|
||||
SnakeServer::SnakeServer(const AppConfig& appConfig) :
|
||||
_appConfig(appConfig),
|
||||
_server(appConfig.port, appConfig.hostname)
|
||||
SnakeServer::SnakeServer(const AppConfig& appConfig)
|
||||
: _appConfig(appConfig)
|
||||
, _server(appConfig.port, appConfig.hostname)
|
||||
{
|
||||
;
|
||||
_server.setTLSOptions(appConfig.socketTLSOptions);
|
||||
}
|
||||
|
||||
//
|
||||
@ -32,7 +34,7 @@ namespace snake
|
||||
idx = path.rfind('=');
|
||||
if (idx != std::string::npos)
|
||||
{
|
||||
std::string appkey = path.substr(idx+1);
|
||||
std::string appkey = path.substr(idx + 1);
|
||||
return appkey;
|
||||
}
|
||||
else
|
||||
@ -43,32 +45,28 @@ namespace snake
|
||||
|
||||
bool SnakeServer::run()
|
||||
{
|
||||
std::cout << "Listening on " << _appConfig.hostname << ":" << _appConfig.port << std::endl;
|
||||
|
||||
auto factory = []() -> std::shared_ptr<ix::ConnectionState>
|
||||
{
|
||||
auto factory = []() -> std::shared_ptr<ix::ConnectionState> {
|
||||
return std::make_shared<SnakeConnectionState>();
|
||||
};
|
||||
_server.setConnectionStateFactory(factory);
|
||||
|
||||
_server.setOnConnectionCallback(
|
||||
[this](std::shared_ptr<ix::WebSocket> webSocket,
|
||||
std::shared_ptr<ix::ConnectionState> connectionState)
|
||||
{
|
||||
std::shared_ptr<ix::ConnectionState> connectionState) {
|
||||
auto state = std::dynamic_pointer_cast<SnakeConnectionState>(connectionState);
|
||||
|
||||
webSocket->setOnMessageCallback(
|
||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
[this, webSocket, state](const ix::WebSocketMessagePtr& msg) {
|
||||
std::stringstream ss;
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
{
|
||||
std::cerr << "New connection" << std::endl;
|
||||
std::cerr << "id: " << state->getId() << std::endl;
|
||||
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
|
||||
std::cerr << "Headers:" << std::endl;
|
||||
ss << "New connection" << std::endl;
|
||||
ss << "id: " << state->getId() << std::endl;
|
||||
ss << "Uri: " << msg->openInfo.uri << std::endl;
|
||||
ss << "Headers:" << std::endl;
|
||||
for (auto it : msg->openInfo.headers)
|
||||
{
|
||||
std::cerr << it.first << ": " << it.second << std::endl;
|
||||
ss << it.first << ": " << it.second << std::endl;
|
||||
}
|
||||
|
||||
std::string appkey = parseAppKey(msg->openInfo.uri);
|
||||
@ -78,37 +76,36 @@ namespace snake
|
||||
if (!state->redisClient().connect(_appConfig.redisHosts[0],
|
||||
_appConfig.redisPort))
|
||||
{
|
||||
std::cerr << "Cannot connect to redis host" << std::endl;
|
||||
ss << "Cannot connect to redis host" << std::endl;
|
||||
}
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||
{
|
||||
std::cerr << "Closed connection"
|
||||
<< " code " << msg->closeInfo.code
|
||||
<< " reason " << msg->closeInfo.reason << std::endl;
|
||||
ss << "Closed connection"
|
||||
<< " code " << msg->closeInfo.code << " reason "
|
||||
<< msg->closeInfo.reason << std::endl;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
std::cerr << ss.str();
|
||||
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||
ss << "#retries: " << msg->errorInfo.retries << std::endl;
|
||||
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Fragment)
|
||||
{
|
||||
std::cerr << "Received message fragment" << std::endl;
|
||||
ss << "Received message fragment" << std::endl;
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
std::cerr << "Received " << msg->wireSize << " bytes" << std::endl;
|
||||
ss << "Received " << msg->wireSize << " bytes" << std::endl;
|
||||
processCobraMessage(state, webSocket, _appConfig, msg->str);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
});
|
||||
});
|
||||
|
||||
auto res = _server.listen();
|
||||
if (!res.first)
|
||||
@ -118,8 +115,19 @@ namespace snake
|
||||
}
|
||||
|
||||
_server.start();
|
||||
_server.wait();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeServer::runForever()
|
||||
{
|
||||
if (run())
|
||||
{
|
||||
_server.wait();
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeServer::stop()
|
||||
{
|
||||
_server.stop();
|
||||
}
|
||||
} // namespace snake
|
@ -19,6 +19,8 @@ namespace snake
|
||||
~SnakeServer() = default;
|
||||
|
||||
bool run();
|
||||
void runForever();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::string parseAppKey(const std::string& path);
|
@ -10,14 +10,13 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
CancellationRequest makeCancellationRequestWithTimeout(int secs,
|
||||
std::atomic<bool>& requestInitCancellation)
|
||||
CancellationRequest makeCancellationRequestWithTimeout(
|
||||
int secs, std::atomic<bool>& requestInitCancellation)
|
||||
{
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto timeout = std::chrono::seconds(secs);
|
||||
|
||||
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool
|
||||
{
|
||||
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool {
|
||||
// Was an explicit cancellation requested ?
|
||||
if (requestInitCancellation) return true;
|
||||
|
||||
@ -30,4 +29,4 @@ namespace ix
|
||||
|
||||
return isCancellationRequested;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -10,7 +10,8 @@ namespace ix
|
||||
{
|
||||
std::atomic<uint64_t> ConnectionState::_globalId(0);
|
||||
|
||||
ConnectionState::ConnectionState() : _terminated(false)
|
||||
ConnectionState::ConnectionState()
|
||||
: _terminated(false)
|
||||
{
|
||||
computeId();
|
||||
}
|
||||
@ -39,5 +40,4 @@ namespace ix
|
||||
{
|
||||
_terminated = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -5,22 +5,22 @@
|
||||
*/
|
||||
|
||||
#include "IXDNSLookup.h"
|
||||
#include "IXNetSystem.h"
|
||||
|
||||
#include <string.h>
|
||||
#include "IXNetSystem.h"
|
||||
#include <chrono>
|
||||
#include <string.h>
|
||||
#include <thread>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const int64_t DNSLookup::kDefaultWait = 1; // ms
|
||||
|
||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
||||
_hostname(hostname),
|
||||
_port(port),
|
||||
_wait(wait),
|
||||
_res(nullptr),
|
||||
_done(false)
|
||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait)
|
||||
: _hostname(hostname)
|
||||
, _port(port)
|
||||
, _wait(wait)
|
||||
, _res(nullptr)
|
||||
, _done(false)
|
||||
{
|
||||
;
|
||||
}
|
||||
@ -38,8 +38,7 @@ namespace ix
|
||||
std::string sport = std::to_string(port);
|
||||
|
||||
struct addrinfo* res;
|
||||
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(),
|
||||
&hints, &res);
|
||||
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res);
|
||||
if (getaddrinfo_result)
|
||||
{
|
||||
errMsg = gai_strerror(getaddrinfo_result);
|
||||
@ -56,13 +55,13 @@ namespace ix
|
||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||
}
|
||||
|
||||
struct addrinfo* DNSLookup::resolveUnCancellable(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
struct addrinfo* DNSLookup::resolveUnCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
// Maybe a cancellation request got in before the background thread terminated ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
@ -71,8 +70,8 @@ namespace ix
|
||||
return getAddrInfo(_hostname, _port, errMsg);
|
||||
}
|
||||
|
||||
struct addrinfo* DNSLookup::resolveCancellable(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
struct addrinfo* DNSLookup::resolveCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
@ -94,7 +93,7 @@ namespace ix
|
||||
int port = _port;
|
||||
std::string hostname(_hostname);
|
||||
|
||||
// We make the background thread doing the work a shared pointer
|
||||
// We make the background thread doing the work a shared pointer
|
||||
// instead of a member variable, because it can keep running when
|
||||
// this object goes out of scope, in case of cancellation
|
||||
auto t = std::make_shared<std::thread>(&DNSLookup::run, this, self, hostname, port);
|
||||
@ -108,7 +107,7 @@ namespace ix
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
|
||||
|
||||
// Were we cancelled ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
@ -116,7 +115,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Maybe a cancellation request got in before the bg terminated ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
@ -126,7 +125,9 @@ namespace ix
|
||||
return getRes();
|
||||
}
|
||||
|
||||
void DNSLookup::run(std::weak_ptr<DNSLookup> self, std::string hostname, int port) // thread runner
|
||||
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
|
||||
std::string hostname,
|
||||
int port) // thread runner
|
||||
{
|
||||
// We don't want to read or write into members variables of an object that could be
|
||||
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
||||
@ -134,7 +135,7 @@ namespace ix
|
||||
std::string errMsg;
|
||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||
|
||||
if (self.lock())
|
||||
if (auto lock = self.lock())
|
||||
{
|
||||
// Copy result into the member variables
|
||||
setRes(res);
|
||||
@ -167,4 +168,4 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_resMutex);
|
||||
return _res;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -10,11 +10,10 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
uint32_t calculateRetryWaitMilliseconds(
|
||||
uint32_t retry_count,
|
||||
uint32_t maxWaitBetweenReconnectionRetries)
|
||||
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
|
||||
uint32_t maxWaitBetweenReconnectionRetries)
|
||||
{
|
||||
uint32_t wait_time = std::pow(2, retry_count) * 100;
|
||||
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0;
|
||||
|
||||
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
|
||||
{
|
||||
@ -23,4 +22,4 @@ namespace ix
|
||||
|
||||
return wait_time;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
uint32_t calculateRetryWaitMilliseconds(
|
||||
uint32_t retry_count,
|
||||
uint32_t maxWaitBetweenReconnectionRetries);
|
||||
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
|
||||
uint32_t maxWaitBetweenReconnectionRetries);
|
||||
} // namespace ix
|
||||
|
@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
#include "IXHttp.h"
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXSocket.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@ -27,7 +27,38 @@ namespace ix
|
||||
return out;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(const std::string& line)
|
||||
std::pair<std::string, int> Http::parseStatusLine(const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
std::stringstream tokenStream(line);
|
||||
std::vector<std::string> tokens;
|
||||
|
||||
// Split by ' '
|
||||
while (std::getline(tokenStream, token, ' '))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
}
|
||||
|
||||
std::string httpVersion;
|
||||
if (tokens.size() >= 1)
|
||||
{
|
||||
httpVersion = trim(tokens[0]);
|
||||
}
|
||||
|
||||
int statusCode = -1;
|
||||
if (tokens.size() >= 2)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << trim(tokens[1]);
|
||||
ss >> statusCode;
|
||||
}
|
||||
|
||||
return std::make_pair(httpVersion, statusCode);
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(
|
||||
const std::string& line)
|
||||
{
|
||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||
std::string token;
|
||||
@ -84,8 +115,8 @@ namespace ix
|
||||
|
||||
// Parse request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
@ -131,8 +162,6 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
return response->payload.empty()
|
||||
? true
|
||||
: socket->writeBytes(response->payload, nullptr);
|
||||
return response->payload.empty() ? true : socket->writeBytes(response->payload, nullptr);
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -65,7 +66,8 @@ namespace ix
|
||||
};
|
||||
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
using HttpParameters = std::map<std::string, std::string>;
|
||||
using HttpParameters = std::unordered_map<std::string, std::string>;
|
||||
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
|
||||
using Logger = std::function<void(const std::string&)>;
|
||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||
|
||||
@ -75,6 +77,7 @@ namespace ix
|
||||
std::string verb;
|
||||
WebSocketHttpHeaders extraHeaders;
|
||||
std::string body;
|
||||
std::string multipartBoundary;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
@ -115,6 +118,7 @@ namespace ix
|
||||
std::shared_ptr<Socket> socket);
|
||||
static bool sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket);
|
||||
|
||||
static std::pair<std::string, int> parseStatusLine(const std::string& line);
|
||||
static std::tuple<std::string, std::string, std::string> parseRequestLine(
|
||||
const std::string& line);
|
||||
static std::string trim(const std::string& str);
|
||||
|
@ -5,17 +5,17 @@
|
||||
*/
|
||||
|
||||
#include "IXHttpClient.h"
|
||||
|
||||
#include "IXSocketFactory.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXSocketFactory.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace ix
|
||||
@ -26,7 +26,9 @@ namespace ix
|
||||
const std::string HttpClient::kDel = "DEL";
|
||||
const std::string HttpClient::kPut = "PUT";
|
||||
|
||||
HttpClient::HttpClient(bool async) : _async(async), _stop(false)
|
||||
HttpClient::HttpClient(bool async)
|
||||
: _async(async)
|
||||
, _stop(false)
|
||||
{
|
||||
if (!_async) return;
|
||||
|
||||
@ -42,8 +44,12 @@ namespace ix
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url,
|
||||
const std::string& verb)
|
||||
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
|
||||
{
|
||||
_tlsOptions = tlsOptions;
|
||||
}
|
||||
|
||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
|
||||
{
|
||||
auto request = std::make_shared<HttpRequestArgs>();
|
||||
request->url = url;
|
||||
@ -106,12 +112,11 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::request(
|
||||
const std::string& url,
|
||||
const std::string& verb,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args,
|
||||
int redirects)
|
||||
HttpResponsePtr HttpClient::request(const std::string& url,
|
||||
const std::string& verb,
|
||||
const std::string& body,
|
||||
HttpRequestArgsPtr args,
|
||||
int redirects)
|
||||
{
|
||||
// We only have one socket connection, so we cannot
|
||||
// make multiple requests concurrently.
|
||||
@ -131,20 +136,30 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot parse url: " << url;
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::UrlMalformed,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::UrlMalformed,
|
||||
headers,
|
||||
payload,
|
||||
ss.str(),
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
bool tls = protocol == "https";
|
||||
std::string errorMsg;
|
||||
_socket = createSocket(tls, errorMsg);
|
||||
_socket = createSocket(tls, -1, errorMsg, _tlsOptions);
|
||||
|
||||
if (!_socket)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotCreateSocket,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::CannotCreateSocket,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
// Build request string
|
||||
@ -154,7 +169,8 @@ namespace ix
|
||||
|
||||
if (args->compress)
|
||||
{
|
||||
ss << "Accept-Encoding: gzip" << "\r\n";
|
||||
ss << "Accept-Encoding: gzip"
|
||||
<< "\r\n";
|
||||
}
|
||||
|
||||
// Append extra headers
|
||||
@ -166,7 +182,8 @@ namespace ix
|
||||
// Set a default Accept header if none is present
|
||||
if (headers.find("Accept") == headers.end())
|
||||
{
|
||||
ss << "Accept: */*" << "\r\n";
|
||||
ss << "Accept: */*"
|
||||
<< "\r\n";
|
||||
}
|
||||
|
||||
// Set a default User agent if none is present
|
||||
@ -182,7 +199,16 @@ namespace ix
|
||||
// Set default Content-Type if unspecified
|
||||
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
|
||||
{
|
||||
ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
|
||||
if (args->multipartBoundary.empty())
|
||||
{
|
||||
ss << "Content-Type: application/x-www-form-urlencoded"
|
||||
<< "\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
|
||||
<< "\r\n";
|
||||
}
|
||||
}
|
||||
ss << "\r\n";
|
||||
ss << body;
|
||||
@ -205,9 +231,14 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotConnect,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::CannotConnect,
|
||||
headers,
|
||||
payload,
|
||||
ss.str(),
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
// Make a new cancellation object dealing with transfer timeout
|
||||
@ -221,8 +252,7 @@ namespace ix
|
||||
<< "to " << host << ":" << port << std::endl
|
||||
<< "request size: " << req.size() << " bytes" << std::endl
|
||||
<< "=============" << std::endl
|
||||
<< req
|
||||
<< "=============" << std::endl
|
||||
<< req << "=============" << std::endl
|
||||
<< std::endl;
|
||||
|
||||
log(ss.str(), args);
|
||||
@ -231,9 +261,14 @@ namespace ix
|
||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||
{
|
||||
std::string errorMsg("Cannot send request");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::SendError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::SendError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
uploadSize = req.size();
|
||||
@ -245,9 +280,14 @@ namespace ix
|
||||
if (!lineValid)
|
||||
{
|
||||
std::string errorMsg("Cannot retrieve status line");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadStatusLine,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::CannotReadStatusLine,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
if (args->verbose)
|
||||
@ -260,9 +300,14 @@ namespace ix
|
||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||
{
|
||||
std::string errorMsg("Cannot parse response code from status line");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingStatus,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::MissingStatus,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
@ -272,9 +317,14 @@ namespace ix
|
||||
if (!headersValid)
|
||||
{
|
||||
std::string errorMsg("Cannot parse http headers");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::HeaderParsingError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::HeaderParsingError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
// Redirect ?
|
||||
@ -283,30 +333,45 @@ namespace ix
|
||||
if (headers.find("Location") == headers.end())
|
||||
{
|
||||
std::string errorMsg("Missing location header for redirect");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingLocation,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::MissingLocation,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
if (redirects >= args->maxRedirects)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Too many redirects: " << redirects;
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::TooManyRedirects,
|
||||
headers, payload, ss.str(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::TooManyRedirects,
|
||||
headers,
|
||||
payload,
|
||||
ss.str(),
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
// Recurse
|
||||
std::string location = headers["Location"];
|
||||
return request(location, verb, body, args, redirects+1);
|
||||
return request(location, verb, body, args, redirects + 1);
|
||||
}
|
||||
|
||||
if (verb == "HEAD")
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
payload,
|
||||
std::string(),
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
// Parse response:
|
||||
@ -319,15 +384,19 @@ namespace ix
|
||||
|
||||
payload.reserve(contentLength);
|
||||
|
||||
auto chunkResult = _socket->readBytes(contentLength,
|
||||
args->onProgressCallback,
|
||||
isCancellationRequested);
|
||||
auto chunkResult = _socket->readBytes(
|
||||
contentLength, args->onProgressCallback, isCancellationRequested);
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::ChunkReadError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
payload += chunkResult.second;
|
||||
}
|
||||
@ -343,9 +412,14 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::ChunkReadError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
uint64_t chunkSize;
|
||||
@ -356,23 +430,26 @@ namespace ix
|
||||
if (args->verbose)
|
||||
{
|
||||
std::stringstream oss;
|
||||
oss << "Reading " << chunkSize << " bytes"
|
||||
<< std::endl;
|
||||
oss << "Reading " << chunkSize << " bytes" << std::endl;
|
||||
log(oss.str(), args);
|
||||
}
|
||||
|
||||
payload.reserve(payload.size() + (size_t) chunkSize);
|
||||
|
||||
// Read a chunk
|
||||
auto chunkResult = _socket->readBytes((size_t) chunkSize,
|
||||
args->onProgressCallback,
|
||||
isCancellationRequested);
|
||||
auto chunkResult = _socket->readBytes(
|
||||
(size_t) chunkSize, args->onProgressCallback, isCancellationRequested);
|
||||
if (!chunkResult.first)
|
||||
{
|
||||
errorMsg = "Cannot read chunk";
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::ChunkReadError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
payload += chunkResult.second;
|
||||
|
||||
@ -381,9 +458,14 @@ namespace ix
|
||||
|
||||
if (!lineResult.first)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::ChunkReadError,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
if (chunkSize == 0) break;
|
||||
@ -396,9 +478,14 @@ namespace ix
|
||||
else
|
||||
{
|
||||
std::string errorMsg("Cannot read http body");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadBody,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::CannotReadBody,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
downloadSize = payload.size();
|
||||
@ -410,32 +497,39 @@ namespace ix
|
||||
if (!gzipInflate(payload, decompressedPayload))
|
||||
{
|
||||
std::string errorMsg("Error decompressing payload");
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Gzip,
|
||||
headers, payload, errorMsg,
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::Gzip,
|
||||
headers,
|
||||
payload,
|
||||
errorMsg,
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
payload = decompressedPayload;
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||
headers, payload, std::string(),
|
||||
uploadSize, downloadSize);
|
||||
return std::make_shared<HttpResponse>(code,
|
||||
description,
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
payload,
|
||||
std::string(),
|
||||
uploadSize,
|
||||
downloadSize);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::get(const std::string& url,
|
||||
HttpRequestArgsPtr args)
|
||||
HttpResponsePtr HttpClient::get(const std::string& url, HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kGet, std::string(), args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::head(const std::string& url,
|
||||
HttpRequestArgsPtr args)
|
||||
HttpResponsePtr HttpClient::head(const std::string& url, HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kHead, std::string(), args);
|
||||
}
|
||||
|
||||
HttpResponsePtr HttpClient::del(const std::string& url,
|
||||
HttpRequestArgsPtr args)
|
||||
HttpResponsePtr HttpClient::del(const std::string& url, HttpRequestArgsPtr args)
|
||||
{
|
||||
return request(url, kDel, std::string(), args);
|
||||
}
|
||||
@ -474,8 +568,7 @@ namespace ix
|
||||
escaped.fill('0');
|
||||
escaped << std::hex;
|
||||
|
||||
for (std::string::const_iterator i = value.begin(), n = value.end();
|
||||
i != n; ++i)
|
||||
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
|
||||
{
|
||||
std::string::value_type c = (*i);
|
||||
|
||||
@ -503,21 +596,64 @@ namespace ix
|
||||
|
||||
for (auto&& it : httpParameters)
|
||||
{
|
||||
ss << urlEncode(it.first)
|
||||
<< "="
|
||||
<< urlEncode(it.second);
|
||||
ss << urlEncode(it.first) << "=" << urlEncode(it.second);
|
||||
|
||||
if (i++ < (count-1))
|
||||
if (i++ < (count - 1))
|
||||
{
|
||||
ss << "&";
|
||||
ss << "&";
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool HttpClient::gzipInflate(
|
||||
const std::string& in,
|
||||
std::string& out)
|
||||
std::string HttpClient::serializeHttpFormDataParameters(
|
||||
const std::string& multipartBoundary,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
const HttpParameters& httpParameters)
|
||||
{
|
||||
//
|
||||
// --AaB03x
|
||||
// Content-Disposition: form-data; name="submit-name"
|
||||
|
||||
// Larry
|
||||
// --AaB03x
|
||||
// Content-Disposition: form-data; name="foo.txt"; filename="file1.txt"
|
||||
// Content-Type: text/plain
|
||||
|
||||
// ... contents of file1.txt ...
|
||||
// --AaB03x--
|
||||
//
|
||||
std::stringstream ss;
|
||||
|
||||
for (auto&& it : httpFormDataParameters)
|
||||
{
|
||||
ss << "--" << multipartBoundary << "\r\n"
|
||||
<< "Content-Disposition:"
|
||||
<< " form-data; name=\"" << it.first << "\";"
|
||||
<< " filename=\"" << it.first << "\""
|
||||
<< "\r\n"
|
||||
<< "Content-Type: application/octet-stream"
|
||||
<< "\r\n"
|
||||
<< "\r\n"
|
||||
<< it.second << "\r\n";
|
||||
}
|
||||
|
||||
for (auto&& it : httpParameters)
|
||||
{
|
||||
ss << "--" << multipartBoundary << "\r\n"
|
||||
<< "Content-Disposition:"
|
||||
<< " form-data; name=\"" << it.first << "\";"
|
||||
<< "\r\n"
|
||||
<< "\r\n"
|
||||
<< it.second << "\r\n";
|
||||
}
|
||||
|
||||
ss << "--" << multipartBoundary << "\r\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool HttpClient::gzipInflate(const std::string& in, std::string& out)
|
||||
{
|
||||
z_stream inflateState;
|
||||
std::memset(&inflateState, 0, sizeof(inflateState));
|
||||
@ -528,13 +664,13 @@ namespace ix
|
||||
inflateState.avail_in = 0;
|
||||
inflateState.next_in = Z_NULL;
|
||||
|
||||
if (inflateInit2(&inflateState, 16+MAX_WBITS) != Z_OK)
|
||||
if (inflateInit2(&inflateState, 16 + MAX_WBITS) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
inflateState.avail_in = (uInt) in.size();
|
||||
inflateState.next_in = (unsigned char *)(const_cast<char *>(in.data()));
|
||||
inflateState.next_in = (unsigned char*) (const_cast<char*>(in.data()));
|
||||
|
||||
const int kBufferSize = 1 << 14;
|
||||
|
||||
@ -554,22 +690,31 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
out.append(
|
||||
reinterpret_cast<char *>(compressBuffer.get()),
|
||||
kBufferSize - inflateState.avail_out
|
||||
);
|
||||
out.append(reinterpret_cast<char*>(compressBuffer.get()),
|
||||
kBufferSize - inflateState.avail_out);
|
||||
} while (inflateState.avail_out == 0);
|
||||
|
||||
inflateEnd(&inflateState);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpClient::log(const std::string& msg,
|
||||
HttpRequestArgsPtr args)
|
||||
void HttpClient::log(const std::string& msg, HttpRequestArgsPtr args)
|
||||
{
|
||||
if (args->logger)
|
||||
{
|
||||
args->logger(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string HttpClient::generateMultipartBoundary()
|
||||
{
|
||||
std::string str("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
static std::random_device rd;
|
||||
static std::mt19937 generator(rd());
|
||||
|
||||
std::shuffle(str.begin(), str.end(), generator);
|
||||
|
||||
return str;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
@ -58,8 +59,18 @@ namespace ix
|
||||
bool performRequest(HttpRequestArgsPtr request,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
// TLS
|
||||
void setTLSOptions(const SocketTLSOptions& tlsOptions);
|
||||
|
||||
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
||||
|
||||
std::string serializeHttpFormDataParameters(
|
||||
const std::string& multipartBoundary,
|
||||
const HttpFormDataParameters& httpFormDataParameters,
|
||||
const HttpParameters& httpParameters = HttpParameters());
|
||||
|
||||
std::string generateMultipartBoundary();
|
||||
|
||||
std::string urlEncode(const std::string& value);
|
||||
|
||||
const static std::string kPost;
|
||||
@ -86,5 +97,7 @@ namespace ix
|
||||
|
||||
std::shared_ptr<Socket> _socket;
|
||||
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
||||
|
||||
SocketTLSOptions _tlsOptions;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -5,13 +5,12 @@
|
||||
*/
|
||||
|
||||
#include "IXHttpServer.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXSocketFactory.h"
|
||||
#include "IXNetSystem.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
@ -28,7 +27,7 @@ namespace
|
||||
file.seekg(0, file.beg);
|
||||
|
||||
memblock.resize((size_t) size);
|
||||
file.read((char*)&memblock.front(), static_cast<std::streamsize>(size));
|
||||
file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
|
||||
|
||||
return std::make_pair(true, memblock);
|
||||
}
|
||||
@ -39,15 +38,13 @@ namespace
|
||||
auto vec = res.second;
|
||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
HttpServer::HttpServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections) : SocketServer(port, host, backlog, maxConnections),
|
||||
_connectedClientsCount(0)
|
||||
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections)
|
||||
: SocketServer(port, host, backlog, maxConnections)
|
||||
, _connectedClientsCount(0)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
@ -71,18 +68,11 @@ namespace ix
|
||||
_onConnectionCallback = callback;
|
||||
}
|
||||
|
||||
void HttpServer::handleConnection(
|
||||
int fd,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
void HttpServer::handleConnection(std::shared_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
std::string errorMsg;
|
||||
auto socket = createSocket(fd, errorMsg);
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
auto ret = Http::parseRequest(socket);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
@ -108,39 +98,33 @@ namespace ix
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||
{
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
uri = "/index.html";
|
||||
}
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
std::string path("." + uri);
|
||||
auto res = readAsString(path);
|
||||
bool found = res.first;
|
||||
if (!found)
|
||||
{
|
||||
return std::make_shared<HttpResponse>(404, "Not Found",
|
||||
HttpErrorCode::Ok,
|
||||
WebSocketHttpHeaders(),
|
||||
std::string());
|
||||
return std::make_shared<HttpResponse>(
|
||||
404, "Not Found", HttpErrorCode::Ok, WebSocketHttpHeaders(), std::string());
|
||||
}
|
||||
|
||||
std::string content = res.second;
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << request->method
|
||||
<< " "
|
||||
<< request->headers["User-Agent"]
|
||||
<< " "
|
||||
<< request->uri
|
||||
<< " "
|
||||
<< content.size();
|
||||
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri << " " << content.size();
|
||||
logInfo(ss.str());
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
// FIXME: check extensions to set the content type
|
||||
// headers["Content-Type"] = "application/octet-stream";
|
||||
headers["Accept-Ranges"] = "none";
|
||||
@ -150,11 +134,39 @@ namespace ix
|
||||
headers[it.first] = it.second;
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(200, "OK",
|
||||
HttpErrorCode::Ok,
|
||||
headers,
|
||||
content);
|
||||
}
|
||||
);
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, headers, content);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::makeRedirectServer(const std::string& redirectUrl)
|
||||
{
|
||||
//
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this,
|
||||
redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr {
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
// Log request
|
||||
std::stringstream ss;
|
||||
ss << request->method << " " << request->headers["User-Agent"] << " "
|
||||
<< request->uri;
|
||||
logInfo(ss.str());
|
||||
|
||||
if (request->method == "POST")
|
||||
{
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, headers, std::string());
|
||||
}
|
||||
|
||||
headers["Location"] = redirectUrl;
|
||||
|
||||
return std::make_shared<HttpResponse>(
|
||||
301, "OK", HttpErrorCode::Ok, headers, std::string());
|
||||
});
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -34,13 +34,15 @@ namespace ix
|
||||
|
||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||
|
||||
void makeRedirectServer(const std::string& redirectUrl);
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
std::atomic<int> _connectedClientsCount;
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(int fd,
|
||||
virtual void handleConnection(std::shared_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
|
@ -34,10 +34,7 @@ namespace ix
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// This function should be in the global namespace
|
||||
#ifdef _WIN32
|
||||
//
|
||||
// That function could 'return WSAPoll(pfd, nfds, timeout);'
|
||||
// but WSAPoll is said to have weird behaviors on the internet
|
||||
@ -45,8 +42,9 @@ namespace ix
|
||||
//
|
||||
// So we make it a select wrapper
|
||||
//
|
||||
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
int poll(struct pollfd* fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int maxfd = 0;
|
||||
fd_set readfds, writefds, errorfds;
|
||||
FD_ZERO(&readfds);
|
||||
@ -55,7 +53,7 @@ namespace ix
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i)
|
||||
{
|
||||
struct pollfd *fd = &fds[i];
|
||||
struct pollfd* fd = &fds[i];
|
||||
|
||||
if (fd->fd > maxfd)
|
||||
{
|
||||
@ -79,17 +77,16 @@ namespace ix
|
||||
tv.tv_sec = timeout / 1000;
|
||||
tv.tv_usec = (timeout % 1000) * 1000;
|
||||
|
||||
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds,
|
||||
timeout != -1 ? &tv : NULL);
|
||||
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
|
||||
|
||||
if (ret < 0)
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (nfds_t i = 0; i < nfds; ++i)
|
||||
{
|
||||
struct pollfd *fd = &fds[i];
|
||||
struct pollfd* fd = &fds[i];
|
||||
fd->revents = 0;
|
||||
|
||||
if (FD_ISSET(fd->fd, &readfds))
|
||||
@ -107,5 +104,9 @@ namespace ix
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
return ::poll(fds, nfds, timeout);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -13,15 +13,15 @@
|
||||
#include <io.h>
|
||||
#include <ws2def.h>
|
||||
|
||||
// Define our own poll on Windows
|
||||
// Define our own poll on Windows, as a wrapper on top of select
|
||||
typedef unsigned long int nfds_t;
|
||||
|
||||
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
|
||||
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <sys/select.h>
|
||||
@ -35,4 +35,6 @@ namespace ix
|
||||
{
|
||||
bool initNetSystem();
|
||||
bool uninitNetSystem();
|
||||
|
||||
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
|
||||
} // namespace ix
|
||||
|
@ -42,5 +42,4 @@ namespace ix
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -26,14 +26,13 @@
|
||||
|
||||
#include "IXSelectInterruptEventFd.h"
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <unistd.h> // for write
|
||||
#include <string.h> // for strerror
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <string.h> // for strerror
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h> // for write
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -113,4 +112,4 @@ namespace ix
|
||||
{
|
||||
return _eventfd;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,9 +7,9 @@
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
# include <ixwebsocket/IXSelectInterruptPipe.h>
|
||||
#include <ixwebsocket/IXSelectInterruptPipe.h>
|
||||
#else
|
||||
# include <ixwebsocket/IXSelectInterrupt.h>
|
||||
#include <ixwebsocket/IXSelectInterrupt.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
@ -22,4 +22,4 @@ namespace ix
|
||||
return std::make_shared<SelectInterrupt>();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -10,12 +10,12 @@
|
||||
|
||||
#include "IXSelectInterruptPipe.h"
|
||||
|
||||
#include <unistd.h> // for write
|
||||
#include <string.h> // for strerror
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sstream>
|
||||
#include <string.h> // for strerror
|
||||
#include <unistd.h> // for write
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -143,4 +143,4 @@ namespace ix
|
||||
|
||||
return _fildes[kPipeReadIndex];
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,21 +5,20 @@
|
||||
*/
|
||||
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
@ -32,9 +31,9 @@ namespace ix
|
||||
const uint64_t Socket::kCloseRequest = 2;
|
||||
constexpr size_t Socket::kChunkSize;
|
||||
|
||||
Socket::Socket(int fd) :
|
||||
_sockfd(fd),
|
||||
_selectInterrupt(createSelectInterrupt())
|
||||
Socket::Socket(int fd)
|
||||
: _sockfd(fd)
|
||||
, _selectInterrupt(createSelectInterrupt())
|
||||
{
|
||||
;
|
||||
}
|
||||
@ -50,13 +49,13 @@ namespace ix
|
||||
std::shared_ptr<SelectInterrupt> selectInterrupt)
|
||||
{
|
||||
//
|
||||
// We used to use ::select to poll but on Android 9 we get large fds out of ::connect
|
||||
// which crash in FD_SET as they are larger than FD_SETSIZE.
|
||||
// Switching to ::poll does fix that.
|
||||
// We used to use ::select to poll but on Android 9 we get large fds out of
|
||||
// ::connect which crash in FD_SET as they are larger than FD_SETSIZE. Switching
|
||||
// to ::poll does fix that.
|
||||
//
|
||||
// However poll isn't as portable as select and has bugs on Windows, so we should write a
|
||||
// shim to fallback to select on those platforms.
|
||||
// See https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
|
||||
// However poll isn't as portable as select and has bugs on Windows, so we
|
||||
// should write a shim to fallback to select on those platforms. See
|
||||
// https://github.com/mpv-player/mpv/pull/5203/files for such a select wrapper.
|
||||
//
|
||||
nfds_t nfds = 1;
|
||||
struct pollfd fds[2];
|
||||
@ -79,7 +78,7 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
int ret = ::poll(fds, nfds, timeoutMs);
|
||||
int ret = ix::poll(fds, nfds, timeoutMs);
|
||||
|
||||
PollResultType pollResult = PollResultType::ReadyForRead;
|
||||
if (ret < 0)
|
||||
@ -123,8 +122,7 @@ namespace ix
|
||||
|
||||
// getsockopt() puts the errno value for connect into optval so 0
|
||||
// means no-error.
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
|
||||
optval != 0)
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 || optval != 0)
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
|
||||
@ -166,6 +164,16 @@ namespace ix
|
||||
return _selectInterrupt->notify(wakeUpCode);
|
||||
}
|
||||
|
||||
bool Socket::accept(std::string& errMsg)
|
||||
{
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
errMsg = "Socket is uninitialized";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Socket::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -201,7 +209,7 @@ namespace ix
|
||||
|
||||
ssize_t Socket::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*)&buffer[0], buffer.size());
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t Socket::recv(void* buffer, size_t length)
|
||||
@ -263,7 +271,7 @@ namespace ix
|
||||
{
|
||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||
|
||||
ssize_t ret = send((char*)&str[offset], len);
|
||||
ssize_t ret = send((char*) &str[offset], len);
|
||||
|
||||
// We wrote some bytes, as needed, all good.
|
||||
if (ret > 0)
|
||||
@ -292,8 +300,7 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
bool Socket::readByte(void* buffer,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@ -332,7 +339,7 @@ namespace ix
|
||||
std::string line;
|
||||
line.reserve(64);
|
||||
|
||||
for (int i = 0; i < 2 || (line[i-2] != '\r' && line[i-1] != '\n'); ++i)
|
||||
for (int i = 0; i < 2 || (line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
|
||||
{
|
||||
if (!readByte(&c, isCancellationRequested))
|
||||
{
|
||||
@ -365,18 +372,15 @@ namespace ix
|
||||
}
|
||||
|
||||
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 && !Socket::isWaitNeeded())
|
||||
if (ret > 0)
|
||||
{
|
||||
// Error
|
||||
return std::make_pair(false, std::string());
|
||||
output.insert(output.end(), _readBuffer.begin(), _readBuffer.begin() + ret);
|
||||
}
|
||||
else
|
||||
else if (ret <= 0 && !Socket::isWaitNeeded())
|
||||
{
|
||||
output.insert(output.end(),
|
||||
_readBuffer.begin(),
|
||||
_readBuffer.begin() + ret);
|
||||
return std::make_pair(false, std::string());
|
||||
}
|
||||
|
||||
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
||||
@ -389,7 +393,6 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(true, std::string(output.begin(),
|
||||
output.end()));
|
||||
return std::make_pair(true, std::string(output.begin(), output.end()));
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -64,6 +64,8 @@ namespace ix
|
||||
PollResultType isReadyToRead(int timeoutMs);
|
||||
|
||||
// Virtual methods
|
||||
virtual bool accept(std::string& errMsg);
|
||||
|
||||
virtual bool connect(const std::string& url,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
|
@ -6,11 +6,13 @@
|
||||
* Adapted from Satori SDK Apple SSL code.
|
||||
*/
|
||||
#include "IXSocketAppleSSL.h"
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -18,131 +20,16 @@
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <errno.h>
|
||||
#define socketerrno errno
|
||||
|
||||
#include <Security/SecureTransport.h>
|
||||
|
||||
namespace {
|
||||
|
||||
OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0)
|
||||
return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t requested_sz = *len;
|
||||
|
||||
ssize_t status = read(fd, data, requested_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (requested_sz > *len)
|
||||
return errSSLWouldBlock;
|
||||
else
|
||||
return noErr;
|
||||
}
|
||||
else if (0 == status)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN:
|
||||
return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET:
|
||||
return errSSLClosedAbort;
|
||||
|
||||
default:
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t *len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0)
|
||||
return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t to_write_sz = *len;
|
||||
ssize_t status = write(fd, data, to_write_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (to_write_sz > *len)
|
||||
return errSSLWouldBlock;
|
||||
else
|
||||
return noErr;
|
||||
}
|
||||
else if (0 == status)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
if (EAGAIN == errno)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSSLErrorDescription(OSStatus status)
|
||||
{
|
||||
std::string errMsg("Unknown SSL error.");
|
||||
|
||||
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
|
||||
if (error)
|
||||
{
|
||||
CFStringRef message = CFErrorCopyDescription(error);
|
||||
if (message)
|
||||
{
|
||||
char localBuffer[128];
|
||||
Boolean success;
|
||||
success = CFStringGetCString(message, localBuffer, 128,
|
||||
CFStringGetSystemEncoding());
|
||||
if (success)
|
||||
{
|
||||
errMsg = localBuffer;
|
||||
}
|
||||
CFRelease(message);
|
||||
}
|
||||
CFRelease(error);
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
|
||||
_sslContext(nullptr)
|
||||
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _sslContext(nullptr)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
;
|
||||
}
|
||||
@ -152,6 +39,119 @@ namespace ix
|
||||
SocketAppleSSL::close();
|
||||
}
|
||||
|
||||
std::string SocketAppleSSL::getSSLErrorDescription(OSStatus status)
|
||||
{
|
||||
std::string errMsg("Unknown SSL error.");
|
||||
|
||||
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
|
||||
if (error)
|
||||
{
|
||||
CFStringRef message = CFErrorCopyDescription(error);
|
||||
if (message)
|
||||
{
|
||||
char localBuffer[128];
|
||||
Boolean success;
|
||||
success = CFStringGetCString(message, localBuffer, 128, kCFStringEncodingUTF8);
|
||||
if (success)
|
||||
{
|
||||
errMsg = localBuffer;
|
||||
}
|
||||
CFRelease(message);
|
||||
}
|
||||
CFRelease(error);
|
||||
}
|
||||
|
||||
return errMsg;
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::readFromSocket(SSLConnectionRef connection, void* data, size_t* len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0) return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t requested_sz = *len;
|
||||
|
||||
ssize_t status = read(fd, data, requested_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (requested_sz > *len)
|
||||
return errSSLWouldBlock;
|
||||
else
|
||||
return noErr;
|
||||
}
|
||||
else if (0 == status)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
switch (errno)
|
||||
{
|
||||
case ENOENT: return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN: return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET: return errSSLClosedAbort;
|
||||
|
||||
default: return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
|
||||
const void* data,
|
||||
size_t* len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0) return errSSLInternal;
|
||||
|
||||
assert(data != nullptr);
|
||||
assert(len != nullptr);
|
||||
|
||||
size_t to_write_sz = *len;
|
||||
ssize_t status = write(fd, data, to_write_sz);
|
||||
|
||||
if (status > 0)
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (to_write_sz > *len)
|
||||
return errSSLWouldBlock;
|
||||
else
|
||||
return noErr;
|
||||
}
|
||||
else if (0 == status)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
}
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
if (EAGAIN == errno)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SocketAppleSSL::accept(std::string& errMsg)
|
||||
{
|
||||
errMsg = "TLS not supported yet in server mode with apple ssl backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
bool SocketAppleSSL::connect(const std::string& host,
|
||||
int port,
|
||||
@ -167,15 +167,38 @@ namespace ix
|
||||
|
||||
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
|
||||
|
||||
SSLSetIOFuncs(_sslContext, read_from_socket, write_to_socket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef) (long) _sockfd);
|
||||
SSLSetIOFuncs(
|
||||
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
do {
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (errSSLWouldBlock == status ||
|
||||
errSSLServerAuthCompleted == status);
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
Boolean option(1);
|
||||
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option);
|
||||
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (errSSLWouldBlock == status || errSSLServerAuthCompleted == status);
|
||||
|
||||
if (status == errSSLServerAuthCompleted)
|
||||
{
|
||||
// proceed with the handshake
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (errSSLWouldBlock == status || errSSLServerAuthCompleted == status);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (errSSLWouldBlock == status || errSSLServerAuthCompleted == status);
|
||||
}
|
||||
}
|
||||
|
||||
if (noErr != status)
|
||||
@ -205,7 +228,8 @@ namespace ix
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
OSStatus status;
|
||||
do {
|
||||
do
|
||||
{
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLWrite(_sslContext, buf, nbyte, &processed);
|
||||
@ -214,14 +238,13 @@ namespace ix
|
||||
nbyte -= processed;
|
||||
} while (nbyte > 0 && errSSLWouldBlock == status);
|
||||
|
||||
if (ret == 0 && errSSLClosedAbort != status)
|
||||
ret = -1;
|
||||
if (ret == 0 && errSSLClosedAbort != status) ret = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t SocketAppleSSL::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*)&buffer[0], buffer.size());
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
// No wait support
|
||||
@ -234,13 +257,11 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLRead(_sslContext, buf, nbyte, &processed);
|
||||
|
||||
if (processed > 0)
|
||||
return (ssize_t) processed;
|
||||
if (processed > 0) return (ssize_t) processed;
|
||||
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful ||
|
||||
status == errSSLClosedNoNotify ||
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort)
|
||||
{
|
||||
errno = ECONNRESET;
|
||||
@ -256,4 +277,4 @@ namespace ix
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <Security/SecureTransport.h>
|
||||
#include <Security/Security.h>
|
||||
#include <mutex>
|
||||
@ -17,9 +18,11 @@ namespace ix
|
||||
class SocketAppleSSL final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketAppleSSL(int fd = -1);
|
||||
SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
|
||||
~SocketAppleSSL();
|
||||
|
||||
virtual bool accept(std::string& errMsg) final;
|
||||
|
||||
virtual bool connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -31,8 +34,14 @@ namespace ix
|
||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||
|
||||
private:
|
||||
static std::string getSSLErrorDescription(OSStatus status);
|
||||
static OSStatus writeToSocket(SSLConnectionRef connection, const void* data, size_t* len);
|
||||
static OSStatus readFromSocket(SSLConnectionRef connection, void* data, size_t* len);
|
||||
|
||||
SSLContextRef _sslContext;
|
||||
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
|
||||
|
||||
SocketTLSOptions _tlsOptions;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -5,36 +5,35 @@
|
||||
*/
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include "IXDNSLookup.h"
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocket.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
|
||||
#ifdef ANDROID
|
||||
# include <linux/in.h>
|
||||
# include <linux/tcp.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
//
|
||||
// This function can be cancelled every 50 ms
|
||||
// This is important so that we don't block the main UI thread when shutting down a connection which is
|
||||
// already trying to reconnect, and can be blocked waiting for ::connect to respond.
|
||||
// This is important so that we don't block the main UI thread when shutting down a
|
||||
// connection which is already trying to reconnect, and can be blocked waiting for
|
||||
// ::connect to respond.
|
||||
//
|
||||
int SocketConnect::connectToAddress(const struct addrinfo *address,
|
||||
int SocketConnect::connectToAddress(const struct addrinfo* address,
|
||||
std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
|
||||
int fd = socket(address->ai_family,
|
||||
address->ai_socktype,
|
||||
address->ai_protocol);
|
||||
int fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
|
||||
if (fd < 0)
|
||||
{
|
||||
errMsg = "Cannot create a socket";
|
||||
@ -74,8 +73,7 @@ namespace ix
|
||||
else if (pollResult == PollResultType::Error)
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = std::string("Connect error: ") +
|
||||
strerror(Socket::getErrno());
|
||||
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
else if (pollResult == PollResultType::ReadyForWrite)
|
||||
@ -85,8 +83,7 @@ namespace ix
|
||||
else
|
||||
{
|
||||
Socket::closeSocket(fd);
|
||||
errMsg = std::string("Connect error: ") +
|
||||
strerror(Socket::getErrno());
|
||||
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -105,7 +102,7 @@ namespace ix
|
||||
// First do DNS resolution
|
||||
//
|
||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||
struct addrinfo *res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return -1;
|
||||
@ -114,7 +111,7 @@ namespace ix
|
||||
int sockfd = -1;
|
||||
|
||||
// iterate through the records to find a working peer
|
||||
struct addrinfo *address;
|
||||
struct addrinfo* address;
|
||||
for (address = res; address != nullptr; address = address->ai_next)
|
||||
{
|
||||
//
|
||||
@ -149,8 +146,7 @@ namespace ix
|
||||
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
|
||||
#ifdef SO_NOSIGPIPE
|
||||
int value = 1;
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
|
||||
(void *)&value, sizeof(value));
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -8,15 +8,15 @@
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
|
||||
# ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
# include <ixwebsocket/IXSocketMbedTLS.h>
|
||||
# elif __APPLE__
|
||||
# include <ixwebsocket/IXSocketAppleSSL.h>
|
||||
# elif defined(_WIN32)
|
||||
# include <ixwebsocket/IXSocketSChannel.h>
|
||||
# elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
# include <ixwebsocket/IXSocketOpenSSL.h>
|
||||
# endif
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
#include <ixwebsocket/IXSocketMbedTLS.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <ixwebsocket/IXSocketSChannel.h>
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
#include <ixwebsocket/IXSocketOpenSSL.h>
|
||||
#elif __APPLE__
|
||||
#include <ixwebsocket/IXSocketAppleSSL.h>
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
@ -27,27 +27,30 @@
|
||||
namespace ix
|
||||
{
|
||||
std::shared_ptr<Socket> createSocket(bool tls,
|
||||
std::string& errorMsg)
|
||||
int fd,
|
||||
std::string& errorMsg,
|
||||
const SocketTLSOptions& tlsOptions)
|
||||
{
|
||||
(void) tlsOptions;
|
||||
errorMsg.clear();
|
||||
std::shared_ptr<Socket> socket;
|
||||
|
||||
if (!tls)
|
||||
{
|
||||
socket = std::make_shared<Socket>();
|
||||
socket = std::make_shared<Socket>(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
# if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
socket = std::make_shared<SocketMbedTLS>();
|
||||
# elif defined(__APPLE__)
|
||||
socket = std::make_shared<SocketAppleSSL>();
|
||||
# elif defined(_WIN32)
|
||||
socket = std::make_shared<SocketSChannel>();
|
||||
# else
|
||||
socket = std::make_shared<SocketOpenSSL>();
|
||||
# endif
|
||||
#if defined(IXWEBSOCKET_USE_MBED_TLS)
|
||||
socket = std::make_shared<SocketMbedTLS>(tlsOptions, fd);
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
socket = std::make_shared<SocketOpenSSL>(tlsOptions, fd);
|
||||
#elif defined(_WIN32)
|
||||
socket = std::make_shared<SocketSChannel>(tlsOptions, fd);
|
||||
#elif defined(__APPLE__)
|
||||
socket = std::make_shared<SocketAppleSSL>(tlsOptions, fd);
|
||||
#endif
|
||||
#else
|
||||
errorMsg = "TLS support is not enabled on this platform.";
|
||||
return nullptr;
|
||||
@ -61,18 +64,4 @@ namespace ix
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
std::shared_ptr<Socket> createSocket(int fd,
|
||||
std::string& errorMsg)
|
||||
{
|
||||
errorMsg.clear();
|
||||
|
||||
std::shared_ptr<Socket> socket = std::make_shared<Socket>(fd);
|
||||
if (!socket->init(errorMsg))
|
||||
{
|
||||
socket.reset();
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,13 +7,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class Socket;
|
||||
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg);
|
||||
|
||||
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
|
||||
std::shared_ptr<Socket> createSocket(bool tls,
|
||||
int fd,
|
||||
std::string& errorMsg,
|
||||
const SocketTLSOptions& tlsOptions);
|
||||
} // namespace ix
|
||||
|
@ -9,34 +9,50 @@
|
||||
*/
|
||||
|
||||
#include "IXSocketMbedTLS.h"
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocket.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
#include <string.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SocketMbedTLS::~SocketMbedTLS()
|
||||
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
close();
|
||||
initMBedTLS();
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
|
||||
SocketMbedTLS::~SocketMbedTLS()
|
||||
{
|
||||
SocketMbedTLS::close();
|
||||
}
|
||||
|
||||
void SocketMbedTLS::initMBedTLS()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
mbedtls_ssl_init(&_ssl);
|
||||
mbedtls_ssl_config_init(&_conf);
|
||||
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
||||
|
||||
const char *pers = "IXSocketMbedTLS";
|
||||
|
||||
mbedtls_entropy_init(&_entropy);
|
||||
mbedtls_x509_crt_init(&_cacert);
|
||||
mbedtls_x509_crt_init(&_cert);
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
const char* pers = "IXSocketMbedTLS";
|
||||
|
||||
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
|
||||
mbedtls_entropy_func,
|
||||
&_entropy,
|
||||
(const unsigned char *) pers,
|
||||
(const unsigned char*) pers,
|
||||
strlen(pers)) != 0)
|
||||
{
|
||||
errMsg = "Setting entropy seed failed";
|
||||
@ -44,9 +60,9 @@ namespace ix
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_config_defaults(&_conf,
|
||||
MBEDTLS_SSL_IS_CLIENT,
|
||||
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT ) != 0)
|
||||
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
|
||||
{
|
||||
errMsg = "Setting config default failed";
|
||||
return false;
|
||||
@ -54,8 +70,47 @@ namespace ix
|
||||
|
||||
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 (_tlsOptions.hasCertAndKey())
|
||||
{
|
||||
if (mbedtls_x509_crt_parse_file(&_cert, _tlsOptions.certFile.c_str()) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse cert file '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
if (mbedtls_pk_parse_keyfile(&_pkey, _tlsOptions.keyFile.c_str(), "") < 0)
|
||||
{
|
||||
errMsg = "Cannot parse key file '" + _tlsOptions.keyFile + "'";
|
||||
return false;
|
||||
}
|
||||
if (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
|
||||
{
|
||||
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
; // FIXME
|
||||
}
|
||||
else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
|
||||
{
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
|
||||
{
|
||||
@ -63,7 +118,7 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
{
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
@ -72,6 +127,50 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::accept(std::string& errMsg)
|
||||
{
|
||||
bool isClient = false;
|
||||
bool initialized = init(std::string(), isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||
|
||||
int res;
|
||||
do
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
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;
|
||||
|
||||
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
{
|
||||
char verifyBuf[512];
|
||||
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
|
||||
|
||||
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
|
||||
errMsg += " : ";
|
||||
errMsg += verifyBuf;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -83,7 +182,9 @@ namespace ix
|
||||
if (_sockfd == -1) return false;
|
||||
}
|
||||
|
||||
if (!init(host, errMsg))
|
||||
bool isClient = true;
|
||||
bool initialized = init(host, isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
@ -96,8 +197,7 @@ namespace ix
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
}
|
||||
while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
@ -122,6 +222,8 @@ namespace ix
|
||||
mbedtls_ssl_config_free(&_conf);
|
||||
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
||||
mbedtls_entropy_free(&_entropy);
|
||||
mbedtls_x509_crt_free(&_cacert);
|
||||
mbedtls_x509_crt_free(&_cert);
|
||||
|
||||
Socket::close();
|
||||
}
|
||||
@ -136,13 +238,18 @@ namespace ix
|
||||
|
||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
||||
|
||||
if (res > 0) {
|
||||
if (res > 0)
|
||||
{
|
||||
nbyte -= res;
|
||||
sent += res;
|
||||
} else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
||||
}
|
||||
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -151,7 +258,7 @@ namespace ix
|
||||
|
||||
ssize_t SocketMbedTLS::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*)&buffer[0], buffer.size());
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
|
||||
@ -175,4 +282,4 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,12 +7,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketTLSOptions.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 <mbedtls/x509.h>
|
||||
#include <mbedtls/x509_crt.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace ix
|
||||
@ -20,9 +23,11 @@ namespace ix
|
||||
class SocketMbedTLS final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketMbedTLS() = default;
|
||||
SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1);
|
||||
~SocketMbedTLS();
|
||||
|
||||
virtual bool accept(std::string& errMsg) final;
|
||||
|
||||
virtual bool connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -38,10 +43,15 @@ namespace ix
|
||||
mbedtls_ssl_config _conf;
|
||||
mbedtls_entropy_context _entropy;
|
||||
mbedtls_ctr_drbg_context _ctr_drbg;
|
||||
mbedtls_x509_crt _cacert;
|
||||
mbedtls_x509_crt _cert;
|
||||
mbedtls_pk_context _pkey;
|
||||
|
||||
std::mutex _mutex;
|
||||
SocketTLSOptions _tlsOptions;
|
||||
|
||||
bool init(const std::string& host, std::string& errMsg);
|
||||
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
||||
void initMBedTLS();
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -1,30 +1,38 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Matt DeBoer
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* Adapted from Satori SDK OpenSSL code.
|
||||
*/
|
||||
|
||||
#include "IXSocketOpenSSL.h"
|
||||
|
||||
#include "IXSocketConnect.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include <fnmatch.h>
|
||||
#include <errno.h>
|
||||
#include <fnmatch.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#define socketerrno errno
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const std::string kDefaultCiphers =
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA "
|
||||
"ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 "
|
||||
"ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA "
|
||||
"ECDHE-RSA-AES256-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 "
|
||||
"DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA "
|
||||
"DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 AES128-SHA";
|
||||
|
||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||
|
||||
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
||||
_ssl_connection(nullptr),
|
||||
_ssl_context(nullptr)
|
||||
SocketOpenSSL::SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _ssl_connection(nullptr)
|
||||
, _ssl_context(nullptr)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
|
||||
}
|
||||
@ -114,15 +122,11 @@ namespace ix
|
||||
SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
|
||||
if (ctx)
|
||||
{
|
||||
// To skip verification, pass in SSL_VERIFY_NONE
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
|
||||
[](int preverify, X509_STORE_CTX*) -> int
|
||||
{
|
||||
return preverify;
|
||||
});
|
||||
SSL_CTX_set_mode(ctx,
|
||||
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
|
||||
SSL_CTX_set_verify_depth(ctx, 4);
|
||||
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_options(
|
||||
ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
@ -130,16 +134,16 @@ namespace ix
|
||||
/**
|
||||
* Check whether a hostname matches a pattern
|
||||
*/
|
||||
bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern)
|
||||
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
||||
{
|
||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl,
|
||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
|
||||
const std::string& hostname,
|
||||
std::string& errMsg)
|
||||
{
|
||||
X509 *server_cert = SSL_get_peer_certificate(ssl);
|
||||
X509* server_cert = SSL_get_peer_certificate(ssl);
|
||||
if (server_cert == nullptr)
|
||||
{
|
||||
errMsg = "OpenSSL failed - peer didn't present a X509 certificate.";
|
||||
@ -149,18 +153,17 @@ namespace ix
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
// Check server name
|
||||
bool hostname_verifies_ok = false;
|
||||
STACK_OF(GENERAL_NAME) *san_names =
|
||||
(STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert,
|
||||
NID_subject_alt_name, NULL, NULL);
|
||||
STACK_OF(GENERAL_NAME)* san_names = (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i(
|
||||
(X509*) server_cert, NID_subject_alt_name, NULL, NULL);
|
||||
if (san_names)
|
||||
{
|
||||
for (int i=0; i<sk_GENERAL_NAME_num(san_names); i++)
|
||||
for (int i = 0; i < sk_GENERAL_NAME_num(san_names); i++)
|
||||
{
|
||||
const GENERAL_NAME *sk_name = sk_GENERAL_NAME_value(san_names, i);
|
||||
const GENERAL_NAME* sk_name = sk_GENERAL_NAME_value(san_names, i);
|
||||
if (sk_name->type == GEN_DNS)
|
||||
{
|
||||
char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName);
|
||||
if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
|
||||
char* name = (char*) ASN1_STRING_data(sk_name->d.dNSName);
|
||||
if ((size_t) ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
|
||||
checkHost(hostname, name))
|
||||
{
|
||||
hostname_verifies_ok = true;
|
||||
@ -173,20 +176,20 @@ namespace ix
|
||||
|
||||
if (!hostname_verifies_ok)
|
||||
{
|
||||
int cn_pos = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert),
|
||||
NID_commonName, -1);
|
||||
int cn_pos = X509_NAME_get_index_by_NID(
|
||||
X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
|
||||
if (cn_pos)
|
||||
{
|
||||
X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry(
|
||||
X509_get_subject_name((X509 *)server_cert), cn_pos);
|
||||
X509_NAME_ENTRY* cn_entry =
|
||||
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
|
||||
|
||||
if (cn_entry)
|
||||
{
|
||||
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
|
||||
char *cn = (char *)ASN1_STRING_data(cn_asn1);
|
||||
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
|
||||
char* cn = (char*) ASN1_STRING_data(cn_asn1);
|
||||
|
||||
if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) &&
|
||||
checkHost(hostname, cn))
|
||||
if ((size_t) ASN1_STRING_length(cn_asn1) == strlen(cn) &&
|
||||
checkHost(hostname, cn))
|
||||
{
|
||||
hostname_verifies_ok = true;
|
||||
}
|
||||
@ -205,7 +208,7 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
|
||||
bool SocketOpenSSL::openSSLClientHandshake(const std::string& host, std::string& errMsg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@ -240,6 +243,274 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLServerHandshake(std::string& errMsg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
int accept_result = SSL_accept(_ssl_connection);
|
||||
if (accept_result == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int reason = SSL_get_error(_ssl_connection, accept_result);
|
||||
|
||||
bool rc = false;
|
||||
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
rc = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
errMsg = getSSLError(accept_result);
|
||||
rc = false;
|
||||
}
|
||||
|
||||
if (!rc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::handleTLSOptions(std::string& errMsg)
|
||||
{
|
||||
ERR_clear_error();
|
||||
if (_tlsOptions.hasCertAndKey())
|
||||
{
|
||||
if (SSL_CTX_use_certificate_chain_file(_ssl_context, _tlsOptions.certFile.c_str()) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
|
||||
_tlsOptions.certFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
else if (SSL_CTX_use_PrivateKey_file(
|
||||
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" + _tlsOptions.keyFile +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
else if (!SSL_CTX_check_private_key(_ssl_context))
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - cert/key mismatch(\"" + _tlsOptions.certFile + ", " +
|
||||
_tlsOptions.keyFile + "\")";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if (!_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (SSL_CTX_load_verify_locations(
|
||||
_ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(_ssl_context,
|
||||
SSL_VERIFY_PEER,
|
||||
[](int preverify, X509_STORE_CTX*) -> int { return preverify; });
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
|
||||
}
|
||||
|
||||
if (_tlsOptions.isUsingDefaultCiphers())
|
||||
{
|
||||
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + kDefaultCiphers +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_set_cipher_list(\"" + _tlsOptions.ciphers +
|
||||
"\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::accept(std::string& errMsg)
|
||||
{
|
||||
bool handshakeSuccessful = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
if (!_openSSLInitializationSuccessful)
|
||||
{
|
||||
errMsg = "OPENSSL_init_ssl failure";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_sockfd == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
const SSL_METHOD* method = SSLv23_server_method();
|
||||
if (method == nullptr)
|
||||
{
|
||||
errMsg = "SSLv23_server_method failure";
|
||||
_ssl_context = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ssl_method = method;
|
||||
|
||||
_ssl_context = SSL_CTX_new(_ssl_method);
|
||||
if (_ssl_context)
|
||||
{
|
||||
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
SSL_CTX_set_mode(_ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_CTX_set_options(_ssl_context,
|
||||
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_ssl_context == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
if (_tlsOptions.hasCertAndKey())
|
||||
{
|
||||
if (SSL_CTX_use_certificate_chain_file(_ssl_context,
|
||||
_tlsOptions.certFile.c_str()) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_use_certificate_chain_file(\"" +
|
||||
_tlsOptions.certFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
else if (SSL_CTX_use_PrivateKey_file(
|
||||
_ssl_context, _tlsOptions.keyFile.c_str(), SSL_FILETYPE_PEM) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_use_PrivateKey_file(\"" +
|
||||
_tlsOptions.keyFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ERR_clear_error();
|
||||
if (!_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
if (_tlsOptions.isUsingSystemDefaults())
|
||||
{
|
||||
if (SSL_CTX_set_default_verify_paths(_ssl_context) == 0)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* root_ca_file = _tlsOptions.caFile.c_str();
|
||||
STACK_OF(X509_NAME) * rootCAs;
|
||||
rootCAs = SSL_load_client_CA_file(root_ca_file);
|
||||
if (rootCAs == NULL)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
|
||||
"') failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
|
||||
if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
|
||||
{
|
||||
auto sslErr = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
|
||||
_tlsOptions.caFile + "\") failed: ";
|
||||
errMsg += ERR_error_string(sslErr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SSL_CTX_set_verify(
|
||||
_ssl_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
|
||||
SSL_CTX_set_verify_depth(_ssl_context, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr);
|
||||
}
|
||||
if (_tlsOptions.isUsingDefaultCiphers())
|
||||
{
|
||||
if (SSL_CTX_set_cipher_list(_ssl_context, kDefaultCiphers.c_str()) != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (SSL_CTX_set_cipher_list(_ssl_context, _tlsOptions.ciphers.c_str()) != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_ssl_connection = SSL_new(_ssl_context);
|
||||
if (_ssl_connection == nullptr)
|
||||
{
|
||||
errMsg = "OpenSSL failed to connect";
|
||||
SSL_CTX_free(_ssl_context);
|
||||
_ssl_context = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_set_ecdh_auto(_ssl_connection, 1);
|
||||
|
||||
SSL_set_fd(_ssl_connection, _sockfd);
|
||||
|
||||
handshakeSuccessful = openSSLServerHandshake(errMsg);
|
||||
}
|
||||
|
||||
if (!handshakeSuccessful)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -264,13 +535,9 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context);
|
||||
if (cert_load_result == 0)
|
||||
if (!handleTLSOptions(errMsg))
|
||||
{
|
||||
unsigned long ssl_err = ERR_get_error();
|
||||
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
|
||||
errMsg += ERR_error_string(ssl_err, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
_ssl_connection = SSL_new(_ssl_context);
|
||||
@ -289,13 +556,12 @@ namespace ix
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
// Support for server name verification
|
||||
// (The docs say that this should work from 1.0.2, and is the default from
|
||||
// 1.1.0, but it does not. To be on the safe side, the manual test below is
|
||||
// enabled for all versions prior to 1.1.0.)
|
||||
X509_VERIFY_PARAM *param = SSL_get0_param(_ssl_connection);
|
||||
// 1.1.0, but it does not. To be on the safe side, the manual test
|
||||
// below is enabled for all versions prior to 1.1.0.)
|
||||
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
|
||||
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
|
||||
#endif
|
||||
|
||||
handshakeSuccessful = openSSLHandshake(host, errMsg);
|
||||
handshakeSuccessful = openSSLClientHandshake(host, errMsg);
|
||||
}
|
||||
|
||||
if (!handshakeSuccessful)
|
||||
@ -342,13 +608,18 @@ namespace ix
|
||||
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
|
||||
int reason = SSL_get_error(_ssl_connection, (int) write_result);
|
||||
|
||||
if (reason == SSL_ERROR_NONE) {
|
||||
if (reason == SSL_ERROR_NONE)
|
||||
{
|
||||
nbyte -= write_result;
|
||||
sent += write_result;
|
||||
} else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
|
||||
}
|
||||
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -357,7 +628,7 @@ namespace ix
|
||||
|
||||
ssize_t SocketOpenSSL::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*)&buffer[0], buffer.size());
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
|
||||
@ -389,4 +660,4 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -1,13 +1,14 @@
|
||||
/*
|
||||
* IXSocketOpenSSL.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
* Author: Benjamin Sergeant, Matt DeBoer
|
||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <mutex>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/conf.h>
|
||||
@ -20,9 +21,11 @@ namespace ix
|
||||
class SocketOpenSSL final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketOpenSSL(int fd = -1);
|
||||
SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
|
||||
~SocketOpenSSL();
|
||||
|
||||
virtual bool accept(std::string& errMsg) final;
|
||||
|
||||
virtual bool connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -37,13 +40,17 @@ namespace ix
|
||||
void openSSLInitialize();
|
||||
std::string getSSLError(int ret);
|
||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
||||
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
|
||||
bool openSSLClientHandshake(const std::string& hostname, std::string& errMsg);
|
||||
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
|
||||
bool checkHost(const std::string& host, const char* pattern);
|
||||
bool handleTLSOptions(std::string& errMsg);
|
||||
bool openSSLServerHandshake(std::string& errMsg);
|
||||
|
||||
SSL* _ssl_connection;
|
||||
SSL_CTX* _ssl_context;
|
||||
const SSL_METHOD* _ssl_method;
|
||||
SocketTLSOptions _tlsOptions;
|
||||
|
||||
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
||||
|
||||
static std::once_flag _openSSLInitFlag;
|
||||
|
@ -9,16 +9,19 @@
|
||||
*
|
||||
* This is the right example to look at:
|
||||
* https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
|
||||
*
|
||||
* Similar code is available from this git repo
|
||||
* https://github.com/david-maw/StreamSSL
|
||||
*/
|
||||
#include "IXSocketSChannel.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <basetsd.h>
|
||||
# include <WinSock2.h>
|
||||
# include <ws2def.h>
|
||||
# include <WS2tcpip.h>
|
||||
# include <schannel.h>
|
||||
# include <io.h>
|
||||
#include <WS2tcpip.h>
|
||||
#include <WinSock2.h>
|
||||
#include <basetsd.h>
|
||||
#include <io.h>
|
||||
#include <schannel.h>
|
||||
#include <ws2def.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
@ -26,14 +29,15 @@
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <mstcpip.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <rpc.h>
|
||||
#include <ntdsapi.h>
|
||||
#include <rpc.h>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define RECV_DATA_BUF_SIZE 256
|
||||
|
||||
@ -50,12 +54,8 @@
|
||||
// has already been initialized
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#else
|
||||
# error("This file should only be built on Windows")
|
||||
#error("This file should only be built on Windows")
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
@ -67,12 +67,9 @@ namespace ix
|
||||
|
||||
SocketSChannel::~SocketSChannel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool SocketSChannel::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg)
|
||||
bool SocketSChannel::connect(const std::string& host, int port, std::string& errMsg)
|
||||
{
|
||||
return Socket::connect(host, port, errMsg, nullptr);
|
||||
}
|
||||
@ -103,4 +100,4 @@ namespace ix
|
||||
return Socket::recv(buf, nbyte);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,14 +5,15 @@
|
||||
*/
|
||||
|
||||
#include "IXSocketServer.h"
|
||||
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXNetSystem.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#include "IXSocketFactory.h"
|
||||
#include <assert.h>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -24,17 +25,16 @@ namespace ix
|
||||
SocketServer::SocketServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections) :
|
||||
_port(port),
|
||||
_host(host),
|
||||
_backlog(backlog),
|
||||
_maxConnections(maxConnections),
|
||||
_serverFd(-1),
|
||||
_stop(false),
|
||||
_stopGc(false),
|
||||
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||
size_t maxConnections)
|
||||
: _port(port)
|
||||
, _host(host)
|
||||
, _backlog(backlog)
|
||||
, _maxConnections(maxConnections)
|
||||
, _serverFd(-1)
|
||||
, _stop(false)
|
||||
, _stopGc(false)
|
||||
, _connectionStateFactory(&ConnectionState::createConnectionState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SocketServer::~SocketServer()
|
||||
@ -45,13 +45,13 @@ namespace ix
|
||||
void SocketServer::logError(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
std::cerr << str << std::endl;
|
||||
fprintf(stderr, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
void SocketServer::logInfo(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
std::cout << str << std::endl;
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> SocketServer::listen()
|
||||
@ -62,21 +62,18 @@ namespace ix
|
||||
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error creating socket): "
|
||||
<< strerror(Socket::getErrno());
|
||||
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
|
||||
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Make that socket reusable. (allow restarting this server at will)
|
||||
int enable = 1;
|
||||
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR,
|
||||
(char*) &enable, sizeof(enable)) < 0)
|
||||
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, (char*) &enable, sizeof(enable)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
@ -84,7 +81,7 @@ namespace ix
|
||||
|
||||
// Bind the socket to the server address.
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(_port);
|
||||
server.sin_port = htons(_port);
|
||||
|
||||
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected
|
||||
// by the osx firewall. We need to codesign the binary with a self-signed cert
|
||||
@ -95,12 +92,11 @@ namespace ix
|
||||
//
|
||||
server.sin_addr.s_addr = inet_addr(_host.c_str());
|
||||
|
||||
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
|
||||
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
@ -113,8 +109,7 @@ namespace ix
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling listen "
|
||||
<< "at address " << _host << ":" << _port
|
||||
<< " : " << strerror(Socket::getErrno());
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
@ -125,6 +120,8 @@ namespace ix
|
||||
|
||||
void SocketServer::start()
|
||||
{
|
||||
_stop = false;
|
||||
|
||||
if (!_thread.joinable())
|
||||
{
|
||||
_thread = std::thread(&SocketServer::run, this);
|
||||
@ -186,7 +183,7 @@ namespace ix
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
auto it = _connectionsThreads.begin();
|
||||
auto itEnd = _connectionsThreads.end();
|
||||
auto itEnd = _connectionsThreads.end();
|
||||
|
||||
while (it != itEnd)
|
||||
{
|
||||
@ -221,8 +218,7 @@ namespace ix
|
||||
if (pollResult == PollResultType::Error)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error in select: "
|
||||
<< strerror(Socket::getErrno());
|
||||
ss << "SocketServer::run() error in select: " << strerror(Socket::getErrno());
|
||||
logError(ss.str());
|
||||
continue;
|
||||
}
|
||||
@ -238,15 +234,15 @@ namespace ix
|
||||
socklen_t addressLen = 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::isWaitNeeded())
|
||||
{
|
||||
// FIXME: that error should be propagated
|
||||
int err = Socket::getErrno();
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() error accepting connection: "
|
||||
<< err << ", " << strerror(err);
|
||||
ss << "SocketServer::run() error accepting connection: " << err << ", "
|
||||
<< strerror(err);
|
||||
logError(ss.str());
|
||||
}
|
||||
continue;
|
||||
@ -255,8 +251,7 @@ namespace ix
|
||||
if (getConnectedClientsCount() >= _maxConnections)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::run() reached max connections = "
|
||||
<< _maxConnections << ". "
|
||||
ss << "SocketServer::run() reached max connections = " << _maxConnections << ". "
|
||||
<< "Not accepting connection";
|
||||
logError(ss.str());
|
||||
|
||||
@ -273,14 +268,33 @@ namespace ix
|
||||
|
||||
if (_stop) return;
|
||||
|
||||
// create socket
|
||||
std::string errorMsg;
|
||||
bool tls = _socketTLSOptions.tls;
|
||||
auto socket = createSocket(tls, clientFd, errorMsg, _socketTLSOptions);
|
||||
|
||||
if (socket == nullptr)
|
||||
{
|
||||
logError("SocketServer::run() cannot create socket: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(clientFd);
|
||||
|
||||
if (!socket->accept(errorMsg))
|
||||
{
|
||||
logError("SocketServer::run() tls accept failed: " + errorMsg);
|
||||
Socket::closeSocket(clientFd);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Launch the handleConnection work asynchronously in its own thread.
|
||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
||||
_connectionsThreads.push_back(std::make_pair(
|
||||
connectionState,
|
||||
std::thread(&SocketServer::handleConnection,
|
||||
this,
|
||||
clientFd,
|
||||
connectionState)));
|
||||
connectionState,
|
||||
std::thread(&SocketServer::handleConnection, this, socket, connectionState)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,5 +322,9 @@ namespace ix
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_socketTLSOptions = socketTLSOptions;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXConnectionState.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
@ -20,6 +21,8 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class Socket;
|
||||
|
||||
class SocketServer
|
||||
{
|
||||
public:
|
||||
@ -51,6 +54,8 @@ namespace ix
|
||||
std::pair<bool, std::string> listen();
|
||||
void wait();
|
||||
|
||||
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
protected:
|
||||
// Logging
|
||||
void logError(const std::string& str);
|
||||
@ -68,10 +73,11 @@ namespace ix
|
||||
// socket for accepting connections
|
||||
int _serverFd;
|
||||
|
||||
std::atomic<bool> _stop;
|
||||
|
||||
std::mutex _logMutex;
|
||||
|
||||
// background thread to wait for incoming connections
|
||||
std::atomic<bool> _stop;
|
||||
std::thread _thread;
|
||||
void run();
|
||||
|
||||
@ -92,11 +98,14 @@ namespace ix
|
||||
// the factory to create ConnectionState objects
|
||||
ConnectionStateFactory _connectionStateFactory;
|
||||
|
||||
virtual void handleConnection(int fd, std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||
virtual void handleConnection(std::shared_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||
virtual size_t getConnectedClientsCount() = 0;
|
||||
|
||||
// Returns true if all connection threads are joined
|
||||
void closeTerminatedThreads();
|
||||
size_t getConnectionsThreadsCount();
|
||||
|
||||
SocketTLSOptions _socketTLSOptions;
|
||||
};
|
||||
} // namespace ix
|
||||
|
87
ixwebsocket/IXSocketTLSOptions.cpp
Normal file
87
ixwebsocket/IXSocketTLSOptions.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* IXSocketTLSOptions.h
|
||||
* Author: Matt DeBoer
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include "IXSocketTLSOptions.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const char* kTLSCAFileUseSystemDefaults = "SYSTEM";
|
||||
const char* kTLSCAFileDisableVerify = "NONE";
|
||||
const char* kTLSCiphersUseDefault = "DEFAULT";
|
||||
|
||||
bool SocketTLSOptions::isValid() const
|
||||
{
|
||||
if (!_validated)
|
||||
{
|
||||
if (!certFile.empty() && !std::ifstream(certFile))
|
||||
{
|
||||
_errMsg = "certFile not found: " + certFile;
|
||||
return false;
|
||||
}
|
||||
if (!keyFile.empty() && !std::ifstream(keyFile))
|
||||
{
|
||||
_errMsg = "keyFile not found: " + keyFile;
|
||||
return false;
|
||||
}
|
||||
if (!caFile.empty() && caFile != kTLSCAFileDisableVerify &&
|
||||
caFile != kTLSCAFileUseSystemDefaults && !std::ifstream(caFile))
|
||||
{
|
||||
_errMsg = "caFile not found: " + caFile;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (certFile.empty() != keyFile.empty())
|
||||
{
|
||||
_errMsg = "certFile and keyFile must be both present, or both absent";
|
||||
return false;
|
||||
}
|
||||
|
||||
_validated = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::hasCertAndKey() const
|
||||
{
|
||||
return !certFile.empty() && !keyFile.empty();
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingSystemDefaults() const
|
||||
{
|
||||
return caFile == kTLSCAFileUseSystemDefaults;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isPeerVerifyDisabled() const
|
||||
{
|
||||
return caFile == kTLSCAFileDisableVerify;
|
||||
}
|
||||
|
||||
bool SocketTLSOptions::isUsingDefaultCiphers() const
|
||||
{
|
||||
return ciphers.empty() || ciphers == kTLSCiphersUseDefault;
|
||||
}
|
||||
|
||||
const std::string& SocketTLSOptions::getErrorMsg() const
|
||||
{
|
||||
return _errMsg;
|
||||
}
|
||||
|
||||
std::string SocketTLSOptions::getDescription() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "TLS Options:" << std::endl;
|
||||
ss << " certFile = " << certFile << std::endl;
|
||||
ss << " keyFile = " << keyFile << std::endl;
|
||||
ss << " caFile = " << caFile << std::endl;
|
||||
ss << " ciphers = " << ciphers << std::endl;
|
||||
ss << " ciphers = " << ciphers << std::endl;
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace ix
|
52
ixwebsocket/IXSocketTLSOptions.h
Normal file
52
ixwebsocket/IXSocketTLSOptions.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* IXSocketTLSOptions.h
|
||||
* Author: Matt DeBoer
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct SocketTLSOptions
|
||||
{
|
||||
public:
|
||||
// check validity of the object
|
||||
bool isValid() const;
|
||||
|
||||
// the certificate presented to peers
|
||||
std::string certFile;
|
||||
|
||||
// the key used for signing/encryption
|
||||
std::string keyFile;
|
||||
|
||||
// the ca certificate (or certificate bundle) file containing
|
||||
// certificates to be trusted by peers; use 'SYSTEM' to
|
||||
// leverage the system defaults, use 'NONE' to disable peer verification
|
||||
std::string caFile = "SYSTEM";
|
||||
|
||||
// list of ciphers (rsa, etc...)
|
||||
std::string ciphers = "DEFAULT";
|
||||
|
||||
// whether tls is enabled, used for server code
|
||||
bool tls = false;
|
||||
|
||||
bool hasCertAndKey() const;
|
||||
|
||||
bool isUsingSystemDefaults() const;
|
||||
|
||||
bool isPeerVerifyDisabled() const;
|
||||
|
||||
bool isUsingDefaultCiphers() const;
|
||||
|
||||
const std::string& getErrorMsg() const;
|
||||
|
||||
std::string getDescription() const;
|
||||
|
||||
private:
|
||||
mutable std::string _errMsg;
|
||||
mutable bool _validated = false;
|
||||
};
|
||||
} // namespace ix
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "IXUrlParser.h"
|
||||
|
||||
#include "LUrlParser.h"
|
||||
|
||||
namespace ix
|
||||
@ -24,9 +25,9 @@ namespace ix
|
||||
}
|
||||
|
||||
protocol = res.m_Scheme;
|
||||
host = res.m_Host;
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
host = res.m_Host;
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
|
||||
if (!res.GetPort(&port))
|
||||
{
|
||||
@ -64,4 +65,4 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -5,48 +5,50 @@
|
||||
*/
|
||||
|
||||
#include "IXUserAgent.h"
|
||||
#include "IXWebSocketVersion.h"
|
||||
|
||||
#include "IXWebSocketVersion.h"
|
||||
#include <sstream>
|
||||
#include <zlib.h>
|
||||
|
||||
// Platform name
|
||||
#if defined(_WIN32)
|
||||
#define PLATFORM_NAME "windows" // Windows
|
||||
#define PLATFORM_NAME "windows" // Windows
|
||||
#elif defined(_WIN64)
|
||||
#define PLATFORM_NAME "windows" // Windows
|
||||
#define PLATFORM_NAME "windows" // Windows
|
||||
#elif defined(__CYGWIN__) && !defined(_WIN32)
|
||||
#define PLATFORM_NAME "windows" // Windows (Cygwin POSIX under Microsoft Window)
|
||||
#define PLATFORM_NAME "windows" // Windows (Cygwin POSIX under Microsoft Window)
|
||||
#elif defined(__ANDROID__)
|
||||
#define PLATFORM_NAME "android" // Android (implies Linux, so it must come first)
|
||||
#define PLATFORM_NAME "android" // Android (implies Linux, so it must come first)
|
||||
#elif defined(__linux__)
|
||||
#define PLATFORM_NAME "linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other
|
||||
#define PLATFORM_NAME "linux" // Debian, Ubuntu, Gentoo, Fedora, openSUSE, RedHat, Centos and other
|
||||
#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
|
||||
#include <sys/param.h>
|
||||
#if defined(BSD)
|
||||
#define PLATFORM_NAME "bsd" // FreeBSD, NetBSD, OpenBSD, DragonFly BSD
|
||||
#endif
|
||||
#include <sys/param.h>
|
||||
#if defined(BSD)
|
||||
#define PLATFORM_NAME "bsd" // FreeBSD, NetBSD, OpenBSD, DragonFly BSD
|
||||
#endif
|
||||
#elif defined(__hpux)
|
||||
#define PLATFORM_NAME "hp-ux" // HP-UX
|
||||
#define PLATFORM_NAME "hp-ux" // HP-UX
|
||||
#elif defined(_AIX)
|
||||
#define PLATFORM_NAME "aix" // IBM AIX
|
||||
#define PLATFORM_NAME "aix" // IBM AIX
|
||||
#elif defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin)
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_IPHONE_SIMULATOR == 1
|
||||
#define PLATFORM_NAME "ios" // Apple iOS
|
||||
#elif TARGET_OS_IPHONE == 1
|
||||
#define PLATFORM_NAME "ios" // Apple iOS
|
||||
#elif TARGET_OS_MAC == 1
|
||||
#define PLATFORM_NAME "macos" // Apple OSX
|
||||
#endif
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_IPHONE_SIMULATOR == 1
|
||||
#define PLATFORM_NAME "ios" // Apple iOS
|
||||
#elif TARGET_OS_IPHONE == 1
|
||||
#define PLATFORM_NAME "ios" // Apple iOS
|
||||
#elif TARGET_OS_MAC == 1
|
||||
#define PLATFORM_NAME "macos" // Apple OSX
|
||||
#endif
|
||||
#elif defined(__sun) && defined(__SVR4)
|
||||
#define PLATFORM_NAME "solaris" // Oracle Solaris, Open Indiana
|
||||
#define PLATFORM_NAME "solaris" // Oracle Solaris, Open Indiana
|
||||
#else
|
||||
#define PLATFORM_NAME "unknown platform"
|
||||
#define PLATFORM_NAME "unknown platform"
|
||||
#endif
|
||||
|
||||
// SSL
|
||||
#if defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
#include <mbedtls/version.h>
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
#include <openssl/opensslv.h>
|
||||
#endif
|
||||
|
||||
@ -65,11 +67,11 @@ namespace ix
|
||||
// TLS
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
ss << " ssl/mbedtls";
|
||||
#elif __APPLE__
|
||||
ss << " ssl/DarwinSSL";
|
||||
ss << " ssl/mbedtls " << MBEDTLS_VERSION_STRING;
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
ss << " ssl/OpenSSL " << OPENSSL_VERSION_TEXT;
|
||||
#elif __APPLE__
|
||||
ss << " ssl/DarwinSSL";
|
||||
#endif
|
||||
#else
|
||||
ss << " nossl";
|
||||
@ -80,4 +82,4 @@ namespace ix
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
178
ixwebsocket/IXUtf8Validator.h
Normal file
178
ixwebsocket/IXUtf8Validator.h
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* The following code is adapted from code originally written by Bjoern
|
||||
* Hoehrmann <bjoern@hoehrmann.de>. See
|
||||
* http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
*
|
||||
* The original license:
|
||||
*
|
||||
* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* IXUtf8Validator.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* From websocketpp. Tiny modifications made for code style, function names etc...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
/// State that represents a valid utf8 input sequence
|
||||
static unsigned int const utf8_accept = 0;
|
||||
/// State that represents an invalid utf8 input sequence
|
||||
static unsigned int const utf8_reject = 1;
|
||||
|
||||
/// Lookup table for the UTF8 decode state machine
|
||||
static uint8_t const utf8d[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
|
||||
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef
|
||||
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1,
|
||||
1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
|
||||
};
|
||||
|
||||
/// Decode the next byte of a UTF8 sequence
|
||||
/**
|
||||
* @param [out] state The decoder state to advance
|
||||
* @param [out] codep The codepoint to fill in
|
||||
* @param [in] byte The byte to input
|
||||
* @return The ending state of the decode operation
|
||||
*/
|
||||
inline uint32_t decodeNextByte(uint32_t* state, uint32_t* codep, uint8_t byte)
|
||||
{
|
||||
uint32_t type = utf8d[byte];
|
||||
|
||||
*codep = (*state != utf8_accept) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte);
|
||||
|
||||
*state = utf8d[256 + *state * 16 + type];
|
||||
return *state;
|
||||
}
|
||||
|
||||
/// Provides streaming UTF8 validation functionality
|
||||
class Utf8Validator
|
||||
{
|
||||
public:
|
||||
/// Construct and initialize the validator
|
||||
Utf8Validator()
|
||||
: m_state(utf8_accept)
|
||||
, m_codepoint(0)
|
||||
{
|
||||
}
|
||||
|
||||
/// Advance the state of the validator with the next input byte
|
||||
/**
|
||||
* @param byte The byte to advance the validation state with
|
||||
* @return Whether or not the byte resulted in a validation error.
|
||||
*/
|
||||
bool consume(uint8_t byte)
|
||||
{
|
||||
if (decodeNextByte(&m_state, &m_codepoint, byte) == utf8_reject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Advance Validator state with input from an iterator pair
|
||||
/**
|
||||
* @param begin Input iterator to the start of the input range
|
||||
* @param end Input iterator to the end of the input range
|
||||
* @return Whether or not decoding the bytes resulted in a validation error.
|
||||
*/
|
||||
template<typename iterator_type>
|
||||
bool decode(iterator_type begin, iterator_type end)
|
||||
{
|
||||
for (iterator_type it = begin; it != end; ++it)
|
||||
{
|
||||
unsigned int result =
|
||||
decodeNextByte(&m_state, &m_codepoint, static_cast<uint8_t>(*it));
|
||||
|
||||
if (result == utf8_reject)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Return whether the input sequence ended on a valid utf8 codepoint
|
||||
/**
|
||||
* @return Whether or not the input sequence ended on a valid codepoint.
|
||||
*/
|
||||
bool complete()
|
||||
{
|
||||
return m_state == utf8_accept;
|
||||
}
|
||||
|
||||
/// Reset the Validator to decode another message
|
||||
void reset()
|
||||
{
|
||||
m_state = utf8_accept;
|
||||
m_codepoint = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_state;
|
||||
uint32_t m_codepoint;
|
||||
};
|
||||
|
||||
/// Validate a UTF8 string
|
||||
/**
|
||||
* convenience function that creates a Validator, validates a complete string
|
||||
* and returns the result.
|
||||
*/
|
||||
inline bool validateUtf8(std::string const& s)
|
||||
{
|
||||
Utf8Validator v;
|
||||
if (!v.decode(s.begin(), s.end()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return v.complete();
|
||||
}
|
||||
|
||||
} // namespace ix
|
@ -5,69 +5,14 @@
|
||||
*/
|
||||
|
||||
#include "IXWebSocket.h"
|
||||
#include "IXSetThreadName.h"
|
||||
#include "IXWebSocketHandshake.h"
|
||||
|
||||
#include "IXExponentialBackoff.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "IXSetThreadName.h"
|
||||
#include "IXUtf8Validator.h"
|
||||
#include "IXWebSocketHandshake.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
//
|
||||
// Stolen from here http://www.zedwood.com/article/cpp-is-valid-utf8-string-function
|
||||
// There doesn't seem to be anything in the C++ library so far to do that.
|
||||
// The closest thing is code for converting from utf-8 to utf-16 or utf-32 but
|
||||
// that isn't working well for some broken input strings.
|
||||
//
|
||||
bool isValidUtf8(const std::string& str)
|
||||
{
|
||||
size_t i = 0;
|
||||
size_t ix = str.length();
|
||||
int c, n, j;
|
||||
|
||||
for (; i < ix; i++)
|
||||
{
|
||||
c = (unsigned char) str[i];
|
||||
//if (c==0x09 || c==0x0a || c==0x0d || (0x20 <= c && c <= 0x7e) ) n = 0; // is_printable_ascii
|
||||
if (0x00 <= c && c <= 0x7f)
|
||||
{
|
||||
n = 0; // 0bbbbbbb
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0)
|
||||
{
|
||||
n = 1; // 110bbbbb
|
||||
}
|
||||
else if ( c==0xed && i<(ix-1) && ((unsigned char)str[i+1] & 0xa0)==0xa0)
|
||||
{
|
||||
return false; //U+d800 to U+dfff
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0)
|
||||
{
|
||||
n = 2; // 1110bbbb
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0)
|
||||
{
|
||||
n = 3; // 11110bbb
|
||||
}
|
||||
//else if (($c & 0xFC) == 0xF8) n=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8
|
||||
//else if (($c & 0xFE) == 0xFC) n=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (j=0; j<n && i<ix; j++)
|
||||
{ // n bytes matching 10bbbbbb follow ?
|
||||
if ((++i == ix) || (( (unsigned char)str[i] & 0xC0) != 0x80))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -78,26 +23,26 @@ namespace ix
|
||||
const bool WebSocket::kDefaultEnablePong(true);
|
||||
const uint32_t WebSocket::kDefaultMaxWaitBetweenReconnectionRetries(10 * 1000); // 10s
|
||||
|
||||
WebSocket::WebSocket() :
|
||||
_onMessageCallback(OnMessageCallback()),
|
||||
_stop(false),
|
||||
_automaticReconnection(true),
|
||||
_maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries),
|
||||
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
|
||||
_enablePong(kDefaultEnablePong),
|
||||
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
||||
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
|
||||
WebSocket::WebSocket()
|
||||
: _onMessageCallback(OnMessageCallback())
|
||||
, _stop(false)
|
||||
, _automaticReconnection(true)
|
||||
, _maxWaitBetweenReconnectionRetries(kDefaultMaxWaitBetweenReconnectionRetries)
|
||||
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||
, _pingTimeoutSecs(kDefaultPingTimeoutSecs)
|
||||
{
|
||||
_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(
|
||||
std::make_shared<WebSocketMessage>(
|
||||
WebSocketMessageType::Close, "", wireSize,
|
||||
WebSocketErrorInfo(), WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(code, reason, remote)));
|
||||
}
|
||||
);
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
"",
|
||||
wireSize,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(code, reason, remote)));
|
||||
});
|
||||
}
|
||||
|
||||
WebSocket::~WebSocket()
|
||||
@ -122,12 +67,19 @@ namespace ix
|
||||
return _url;
|
||||
}
|
||||
|
||||
void WebSocket::setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
|
||||
void WebSocket::setPerMessageDeflateOptions(
|
||||
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_perMessageDeflateOptions = perMessageDeflateOptions;
|
||||
}
|
||||
|
||||
void WebSocket::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_socketTLSOptions = socketTLSOptions;
|
||||
}
|
||||
|
||||
const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
@ -208,8 +160,7 @@ namespace ix
|
||||
_thread = std::thread(&WebSocket::run, this);
|
||||
}
|
||||
|
||||
void WebSocket::stop(uint16_t code,
|
||||
const std::string& reason)
|
||||
void WebSocket::stop(uint16_t code, const std::string& reason)
|
||||
{
|
||||
close(code, reason);
|
||||
|
||||
@ -228,48 +179,65 @@ namespace ix
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_ws.configure(_perMessageDeflateOptions,
|
||||
_socketTLSOptions,
|
||||
_enablePong,
|
||||
_pingIntervalSecs,
|
||||
_pingTimeoutSecs);
|
||||
}
|
||||
|
||||
WebSocketInitResult status = _ws.connectToUrl(_url, _extraHeaders, timeoutSecs);
|
||||
WebSocketHttpHeaders headers(_extraHeaders);
|
||||
std::string subProtocolsHeader;
|
||||
auto subProtocols = getSubProtocols();
|
||||
if (!subProtocols.empty())
|
||||
{
|
||||
for (auto subProtocol : subProtocols)
|
||||
{
|
||||
subProtocolsHeader += ",";
|
||||
subProtocolsHeader += subProtocol;
|
||||
}
|
||||
headers["Sec-WebSocket-Protocol"] = subProtocolsHeader;
|
||||
}
|
||||
|
||||
WebSocketInitResult status = _ws.connectToUrl(_url, headers, timeoutSecs);
|
||||
if (!status.success)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(
|
||||
WebSocketMessageType::Open, "", 0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo()));
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(
|
||||
WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers, status.protocol),
|
||||
WebSocketCloseInfo()));
|
||||
return status;
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocket::connectToSocket(int fd, int timeoutSecs)
|
||||
WebSocketInitResult WebSocket::connectToSocket(std::shared_ptr<Socket> socket, int timeoutSecs)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_ws.configure(_perMessageDeflateOptions,
|
||||
_socketTLSOptions,
|
||||
_enablePong,
|
||||
_pingIntervalSecs,
|
||||
_pingTimeoutSecs);
|
||||
}
|
||||
|
||||
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
|
||||
WebSocketInitResult status = _ws.connectToSocket(socket, timeoutSecs);
|
||||
if (!status.success)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(
|
||||
WebSocketMessageType::Open, "", 0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo()));
|
||||
std::make_shared<WebSocketMessage>(WebSocketMessageType::Open,
|
||||
"",
|
||||
0,
|
||||
WebSocketErrorInfo(),
|
||||
WebSocketOpenInfo(status.uri, status.headers),
|
||||
WebSocketCloseInfo()));
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -283,8 +251,7 @@ namespace ix
|
||||
return getReadyState() == ReadyState::Closing;
|
||||
}
|
||||
|
||||
void WebSocket::close(uint16_t code,
|
||||
const std::string& reason)
|
||||
void WebSocket::close(uint16_t code, const std::string& reason)
|
||||
{
|
||||
_ws.close(code, reason);
|
||||
}
|
||||
@ -328,20 +295,22 @@ namespace ix
|
||||
|
||||
if (_automaticReconnection)
|
||||
{
|
||||
duration = millis(calculateRetryWaitMilliseconds(retries++, _maxWaitBetweenReconnectionRetries));
|
||||
duration = millis(calculateRetryWaitMilliseconds(
|
||||
retries++, _maxWaitBetweenReconnectionRetries));
|
||||
|
||||
connectErr.wait_time = duration.count();
|
||||
connectErr.retries = retries;
|
||||
}
|
||||
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.reason = status.errorStr;
|
||||
connectErr.http_status = status.http_status;
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(
|
||||
WebSocketMessageType::Error, "", 0,
|
||||
connectErr, WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo()));
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(WebSocketMessageType::Error,
|
||||
"",
|
||||
0,
|
||||
connectErr,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -377,8 +346,7 @@ namespace ix
|
||||
[this](const std::string& msg,
|
||||
size_t wireSize,
|
||||
bool decompressionError,
|
||||
WebSocketTransport::MessageKind messageKind)
|
||||
{
|
||||
WebSocketTransport::MessageKind messageKind) {
|
||||
WebSocketMessageType webSocketMessageType;
|
||||
switch (messageKind)
|
||||
{
|
||||
@ -386,22 +354,26 @@ namespace ix
|
||||
case WebSocketTransport::MessageKind::MSG_BINARY:
|
||||
{
|
||||
webSocketMessageType = WebSocketMessageType::Message;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WebSocketTransport::MessageKind::PING:
|
||||
{
|
||||
webSocketMessageType = WebSocketMessageType::Ping;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WebSocketTransport::MessageKind::PONG:
|
||||
{
|
||||
webSocketMessageType = WebSocketMessageType::Pong;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case WebSocketTransport::MessageKind::FRAGMENT:
|
||||
{
|
||||
webSocketMessageType = WebSocketMessageType::Fragment;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
WebSocketErrorInfo webSocketErrorInfo;
|
||||
@ -409,11 +381,13 @@ namespace ix
|
||||
|
||||
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||
|
||||
_onMessageCallback(
|
||||
std::make_shared<WebSocketMessage>(
|
||||
webSocketMessageType, msg, wireSize,
|
||||
webSocketErrorInfo, WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(), binary));
|
||||
_onMessageCallback(std::make_shared<WebSocketMessage>(webSocketMessageType,
|
||||
msg,
|
||||
wireSize,
|
||||
webSocketErrorInfo,
|
||||
WebSocketOpenInfo(),
|
||||
WebSocketCloseInfo(),
|
||||
binary));
|
||||
|
||||
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
||||
});
|
||||
@ -459,7 +433,7 @@ namespace ix
|
||||
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
||||
const OnProgressCallback& onProgressCallback)
|
||||
{
|
||||
if (!isValidUtf8(text))
|
||||
if (!validateUtf8(text))
|
||||
{
|
||||
close(WebSocketCloseConstants::kInvalidFramePayloadData,
|
||||
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
|
||||
@ -500,17 +474,20 @@ namespace ix
|
||||
case SendMessageKind::Text:
|
||||
{
|
||||
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SendMessageKind::Binary:
|
||||
{
|
||||
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SendMessageKind::Ping:
|
||||
{
|
||||
webSocketSendInfo = _ws.sendPing(text);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||
@ -522,10 +499,10 @@ namespace ix
|
||||
{
|
||||
switch (_ws.getReadyState())
|
||||
{
|
||||
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
|
||||
case ix::WebSocketTransport::ReadyState::OPEN: return ReadyState::Open;
|
||||
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSING: return ReadyState::Closing;
|
||||
case ix::WebSocketTransport::ReadyState::CLOSED: return ReadyState::Closed;
|
||||
default: return ReadyState::Closed;
|
||||
}
|
||||
}
|
||||
@ -534,10 +511,10 @@ namespace ix
|
||||
{
|
||||
switch (readyState)
|
||||
{
|
||||
case ReadyState::Open : return "OPEN";
|
||||
case ReadyState::Open: return "OPEN";
|
||||
case ReadyState::Connecting: return "CONNECTING";
|
||||
case ReadyState::Closing : return "CLOSING";
|
||||
case ReadyState::Closed : return "CLOSED";
|
||||
case ReadyState::Closing: return "CLOSING";
|
||||
case ReadyState::Closed: return "CLOSED";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
@ -561,4 +538,16 @@ namespace ix
|
||||
{
|
||||
return _ws.bufferedAmount();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::addSubProtocol(const std::string& subProtocol)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
_subProtocols.push_back(subProtocol);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& WebSocket::getSubProtocols()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
return _subProtocols;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXSocketTLSOptions.h"
|
||||
#include "IXWebSocketCloseConstants.h"
|
||||
#include "IXWebSocketErrorInfo.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
@ -49,12 +50,14 @@ namespace ix
|
||||
void setExtraHeaders(const WebSocketHttpHeaders& headers);
|
||||
void setPerMessageDeflateOptions(
|
||||
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
|
||||
void setHeartBeatPeriod(int heartBeatPeriodSecs);
|
||||
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
|
||||
void setPingTimeout(int pingTimeoutSecs);
|
||||
void enablePong();
|
||||
void disablePong();
|
||||
void disablePerMessageDeflate();
|
||||
void addSubProtocol(const std::string& subProtocol);
|
||||
|
||||
// Run asynchronously, by calling start and stop.
|
||||
void start();
|
||||
@ -99,6 +102,7 @@ namespace ix
|
||||
bool isAutomaticReconnectionEnabled() const;
|
||||
void setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries);
|
||||
uint32_t getMaxWaitBetweenReconnectionRetries() const;
|
||||
const std::vector<std::string>& getSubProtocols();
|
||||
|
||||
private:
|
||||
WebSocketSendInfo sendMessage(const std::string& text,
|
||||
@ -111,7 +115,7 @@ namespace ix
|
||||
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
||||
|
||||
// Server
|
||||
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
|
||||
WebSocketInitResult connectToSocket(std::shared_ptr<Socket>, int timeoutSecs);
|
||||
|
||||
WebSocketTransport _ws;
|
||||
|
||||
@ -119,6 +123,9 @@ namespace ix
|
||||
WebSocketHttpHeaders _extraHeaders;
|
||||
|
||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
||||
|
||||
SocketTLSOptions _socketTLSOptions;
|
||||
|
||||
mutable std::mutex _configMutex; // protect all config variables access
|
||||
|
||||
OnMessageCallback _onMessageCallback;
|
||||
@ -146,6 +153,9 @@ namespace ix
|
||||
static const int kDefaultPingIntervalSecs;
|
||||
static const int kDefaultPingTimeoutSecs;
|
||||
|
||||
// Subprotocols
|
||||
std::vector<std::string> _subProtocols;
|
||||
|
||||
friend class WebSocketServer;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -22,9 +22,15 @@ namespace ix
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
|
||||
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorReservedBitUsed("Reserved bit used");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized("Ping reason control frame with payload length > 125 octets");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented("Control message fragmented");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence("Fragmentation: data message out of sequence");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence("Fragmentation: continuation opcode out of sequence");
|
||||
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage("Invalid frame payload data");
|
||||
}
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorPingPayloadOversized(
|
||||
"Ping reason control frame with payload length > 125 octets");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented(
|
||||
"Control message fragmented");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence(
|
||||
"Fragmentation: data message out of sequence");
|
||||
const std::string WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence(
|
||||
"Fragmentation: continuation opcode out of sequence");
|
||||
const std::string WebSocketCloseConstants::kInvalidFramePayloadDataMessage(
|
||||
"Invalid frame payload data");
|
||||
const std::string WebSocketCloseConstants::kInvalidCloseCodeMessage("Invalid close code");
|
||||
} // namespace ix
|
||||
|
@ -32,5 +32,6 @@ namespace ix
|
||||
static const std::string kProtocolErrorCodeDataOpcodeOutOfSequence;
|
||||
static const std::string kProtocolErrorCodeContinuationOpCodeOutOfSequence;
|
||||
static const std::string kInvalidFramePayloadDataMessage;
|
||||
static const std::string kInvalidCloseCodeMessage;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -5,50 +5,45 @@
|
||||
*/
|
||||
|
||||
#include "IXWebSocketHandshake.h"
|
||||
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUrlParser.h"
|
||||
#include "IXHttp.h"
|
||||
#include "IXUserAgent.h"
|
||||
|
||||
#include "libwshandshake.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
WebSocketHandshake::WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
|
||||
std::shared_ptr<Socket> socket,
|
||||
WebSocketPerMessageDeflate& perMessageDeflate,
|
||||
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||
std::atomic<bool>& enablePerMessageDeflate) :
|
||||
_requestInitCancellation(requestInitCancellation),
|
||||
_socket(socket),
|
||||
_perMessageDeflate(perMessageDeflate),
|
||||
_perMessageDeflateOptions(perMessageDeflateOptions),
|
||||
_enablePerMessageDeflate(enablePerMessageDeflate)
|
||||
WebSocketHandshake::WebSocketHandshake(
|
||||
std::atomic<bool>& requestInitCancellation,
|
||||
std::shared_ptr<Socket> socket,
|
||||
WebSocketPerMessageDeflate& perMessageDeflate,
|
||||
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||
std::atomic<bool>& enablePerMessageDeflate)
|
||||
: _requestInitCancellation(requestInitCancellation)
|
||||
, _socket(socket)
|
||||
, _perMessageDeflate(perMessageDeflate)
|
||||
, _perMessageDeflateOptions(perMessageDeflateOptions)
|
||||
, _enablePerMessageDeflate(enablePerMessageDeflate)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||
{
|
||||
return std::equal(a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
[](char a, char b)
|
||||
{
|
||||
return tolower(a) == tolower(b);
|
||||
});
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) {
|
||||
return tolower(a) == tolower(b);
|
||||
});
|
||||
}
|
||||
|
||||
std::string WebSocketHandshake::genRandomString(const int len)
|
||||
{
|
||||
std::string alphanum =
|
||||
"0123456789"
|
||||
"ABCDEFGH"
|
||||
"abcdefgh";
|
||||
std::string alphanum = "0123456789"
|
||||
"ABCDEFGH"
|
||||
"abcdefgh";
|
||||
|
||||
std::random_device r;
|
||||
std::default_random_engine e1(r());
|
||||
@ -74,6 +69,7 @@ namespace ix
|
||||
ss << " ";
|
||||
ss << reason;
|
||||
ss << "\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
|
||||
// Socket write can only be cancelled through a timeout here, not manually.
|
||||
static std::atomic<bool> requestInitCancellation(false);
|
||||
@ -88,12 +84,13 @@ namespace ix
|
||||
return WebSocketInitResult(false, code, reason);
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::clientHandshake(const std::string& url,
|
||||
const WebSocketHttpHeaders& extraHeaders,
|
||||
const std::string& host,
|
||||
const std::string& path,
|
||||
int port,
|
||||
int timeoutSecs)
|
||||
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
||||
const std::string& url,
|
||||
const WebSocketHttpHeaders& extraHeaders,
|
||||
const std::string& host,
|
||||
const std::string& path,
|
||||
int port,
|
||||
int timeoutSecs)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
|
||||
@ -105,9 +102,7 @@ namespace ix
|
||||
if (!success)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unable to connect to " << host
|
||||
<< " on port " << port
|
||||
<< ", error: " << errMsg;
|
||||
ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg;
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
@ -123,7 +118,7 @@ namespace ix
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
||||
ss << "Host: "<< host << ":" << port << "\r\n";
|
||||
ss << "Host: " << host << ":" << port << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Sec-WebSocket-Version: 13\r\n";
|
||||
@ -149,7 +144,8 @@ namespace ix
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
||||
{
|
||||
return WebSocketInitResult(false, 0, std::string("Failed sending GET request to ") + url);
|
||||
return WebSocketInitResult(
|
||||
false, 0, std::string("Failed sending GET request to ") + url);
|
||||
}
|
||||
|
||||
// Read HTTP status line
|
||||
@ -159,30 +155,31 @@ namespace ix
|
||||
|
||||
if (!lineValid)
|
||||
{
|
||||
return WebSocketInitResult(false, 0,
|
||||
std::string("Failed reading HTTP status line from ") + url);
|
||||
return WebSocketInitResult(
|
||||
false, 0, std::string("Failed reading HTTP status line from ") + url);
|
||||
}
|
||||
|
||||
// Validate status
|
||||
int status;
|
||||
auto statusLine = Http::parseStatusLine(line);
|
||||
std::string httpVersion = statusLine.first;
|
||||
int status = statusLine.second;
|
||||
|
||||
// HTTP/1.0 is too old.
|
||||
if (sscanf(line.c_str(), "HTTP/1.0 %d", &status) == 1)
|
||||
if (httpVersion != "HTTP/1.1")
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Server version is HTTP/1.0. Rejecting connection to " << host
|
||||
<< ", status: " << status
|
||||
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
|
||||
<< "Rejecting connection to " << host << ":" << port << ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
// We want an 101 HTTP status
|
||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &status) != 1 || status != 101)
|
||||
if (status != 101)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Got bad status connecting to " << host
|
||||
<< ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << host << ":" << port << ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
@ -242,18 +239,13 @@ namespace ix
|
||||
return WebSocketInitResult(true, status, "", headers, path);
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int fd, int timeoutSecs)
|
||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
|
||||
// Set the socket to non blocking mode + other tweaks
|
||||
SocketConnect::configure(fd);
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
||||
|
||||
std::string remote = std::string("remote fd ") + std::to_string(fd);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
@ -266,8 +258,8 @@ namespace ix
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
|
||||
if (method != "GET")
|
||||
@ -277,7 +269,8 @@ namespace ix
|
||||
|
||||
if (httpVersion != "HTTP/1.1")
|
||||
{
|
||||
return sendErrorResponse(400, "Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||
return sendErrorResponse(400,
|
||||
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||
}
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
@ -295,9 +288,17 @@ namespace ix
|
||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
|
||||
}
|
||||
|
||||
if (headers["upgrade"] != "websocket")
|
||||
if (headers.find("upgrade") == headers.end())
|
||||
{
|
||||
return sendErrorResponse(400, "Invalid or missing Upgrade header");
|
||||
return sendErrorResponse(400, "Missing Upgrade header");
|
||||
}
|
||||
|
||||
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket"))
|
||||
{
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Upgrade header, "
|
||||
"need WebSocket, got " +
|
||||
headers["upgrade"]);
|
||||
}
|
||||
|
||||
if (headers.find("sec-websocket-version") == headers.end())
|
||||
@ -313,8 +314,10 @@ namespace ix
|
||||
|
||||
if (version != 13)
|
||||
{
|
||||
return sendErrorResponse(400, "Invalid Sec-WebSocket-Version, "
|
||||
"need 13, got" + ss.str());
|
||||
return sendErrorResponse(400,
|
||||
"Invalid Sec-WebSocket-Version, "
|
||||
"need 13, got " +
|
||||
ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,6 +329,7 @@ namespace ix
|
||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
||||
ss << "Upgrade: websocket\r\n";
|
||||
ss << "Connection: Upgrade\r\n";
|
||||
ss << "Server: " << userAgent() << "\r\n";
|
||||
|
||||
// Parse the client headers. Does it support deflate ?
|
||||
std::string header = headers["sec-websocket-extensions"];
|
||||
@ -339,7 +343,7 @@ namespace ix
|
||||
if (!_perMessageDeflate.init(webSocketPerMessageDeflateOptions))
|
||||
{
|
||||
return WebSocketInitResult(
|
||||
false, 0,"Failed to initialize per message deflate engine");
|
||||
false, 0, "Failed to initialize per message deflate engine");
|
||||
}
|
||||
ss << webSocketPerMessageDeflateOptions.generateHeader();
|
||||
}
|
||||
@ -348,9 +352,10 @@ namespace ix
|
||||
|
||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
||||
{
|
||||
return WebSocketInitResult(false, 0, std::string("Failed sending response to ") + remote);
|
||||
return WebSocketInitResult(
|
||||
false, 0, std::string("Failed sending response to remote end"));
|
||||
}
|
||||
|
||||
return WebSocketInitResult(true, 200, "", headers, uri);
|
||||
}
|
||||
}
|
||||
} // namespace ix
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user