Compare commits

..

9 Commits

362 changed files with 25304 additions and 50555 deletions

View File

@ -11,7 +11,7 @@ AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: false
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakTemplateDeclarations: true
@ -42,6 +42,5 @@ NamespaceIndentation: All
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
SpaceAfterTemplateKeyword: false
SpaceAfterCStyleCast: true
Standard: Cpp11
UseTab: Never

View File

@ -1,50 +0,0 @@
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

5
.gitignore vendored
View File

@ -1,7 +1,2 @@
build
*.pyc
venv
ixsnake/ixsnake/.certs/
site/
ws/.certs/
ws/.srl

View File

@ -1,7 +0,0 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

View File

@ -6,26 +6,18 @@ language: bash
matrix:
include:
# macOS
# - 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
- os: osx
compiler: clang
script:
- 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
dist: xenial
script:
- python test/run.py
# - make ws
- python test/run.py
- make ws
env:
- CC=gcc
- CXX=g++
@ -43,17 +35,7 @@ matrix:
# env:
# - CMAKE_PATH="/c/Program Files/CMake/bin"
# script:
# - cd third_party/zlib
# - cmake .
# - cmake --build . --target install
# - cd ../..
# # - cd third_party/mbedtls
# # - cmake .
# # - cmake --build . --target install
# # - cd ../..
# - export PATH=$CMAKE_PATH:$PATH
# - cd test
# - cmake .
# - cmake --build --parallel .
# - ixwebsocket_unittest.exe
# # - python test/run.py
# # - cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .
# # - cmake --build --parallel .
# - python test/run.py

34
CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file.
## [5.0.0] - 2019-06-23
### Changed
- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files.
- IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved
- cobra_to_sentry / backtraces are reversed and line number is not extracted correctly
- mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found
- travis CI uses g++ on Linux
## [4.0.0] - 2019-06-09
### Changed
- WebSocket::send() sends message in TEXT mode by default
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
- WebSocket::send takes a third arg, binary which default to true (can be text too)
- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg
- Add explicit WebSocket::sendBinary method
- New headers + WebSocketMessage class to hold message data, still not used across the board
- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility.
- ws echo_server has a -g option to print a greeting message on connect
- IXSocketMbedTLS: better error handling in close and connect
## [3.1.2] - 2019-06-06
### Added
- ws connect has a -x option to disable per message deflate
- Add WebSocket::disablePerMessageDeflate() option.
## [3.0.0] - 2019-06-xx
### Changed
- TLS, aka SSL works on Windows (websocket and http clients)
- ws command line tool build on Windows
- Async API for HttpClient
- HttpClient API changed to use shared_ptr for response and request

View File

@ -1,19 +0,0 @@
# 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})

View File

@ -12,7 +12,8 @@ set (CMAKE_CXX_STANDARD 14)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
if (UNIX)
# -Wshorten-64-to-32 does not work with clang
if (NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
endif()
@ -24,7 +25,6 @@ set( IXWEBSOCKET_SOURCES
ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXExponentialBackoff.cpp
ixwebsocket/IXHttp.cpp
ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXHttpServer.cpp
@ -35,9 +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
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketHandshake.cpp
@ -55,7 +53,6 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXExponentialBackoff.h
ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h
@ -68,17 +65,13 @@ 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
ixwebsocket/IXWebSocketCloseInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketInitResult.h
ixwebsocket/IXWebSocketMessage.h
ixwebsocket/IXWebSocketMessageQueue.h
ixwebsocket/IXWebSocketMessageType.h
@ -89,7 +82,6 @@ set( IXWEBSOCKET_HEADERS
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/IXWebSocketVersion.h
ixwebsocket/LUrlParser.h
ixwebsocket/libwshandshake.hpp
)
@ -105,8 +97,6 @@ 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)
@ -117,17 +107,22 @@ 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 AND NOT USE_OPEN_SSL)
elseif (APPLE)
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,41 +134,16 @@ 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()
if (WIN32)
target_link_libraries(ixwebsocket wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
if (UNIX)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
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()
if (USE_OPEN_SSL)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
@ -181,8 +151,7 @@ if (USE_TLS AND USE_OPEN_SSL)
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
endif()
if (USE_TLS AND USE_MBED_TLS)
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
if (USE_MBED_TLS)
if (USE_VENDORED_THIRD_PARTY)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)
@ -191,19 +160,20 @@ if (USE_TLS AND USE_MBED_TLS)
target_link_libraries(ixwebsocket mbedtls)
else()
find_package(MbedTLS REQUIRED)
target_include_directories(ixwebsocket PUBLIC ${MBEDTLS_INCLUDE_DIRS})
include_directories(${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
endif()
endif()
find_package(ZLIB)
find_package(ZLIB REQUIRED)
if (ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIRS})
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
else()
add_subdirectory(third_party/zlib)
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
target_link_libraries(ixwebsocket zlibstatic)
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
@ -224,19 +194,6 @@ install(TARGETS ixwebsocket
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
)
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()
if (USE_WS)
add_subdirectory(ws)
endif()

1
DOCKER_VERSION Normal file
View File

@ -0,0 +1 @@
4.0.4

View File

@ -1,35 +0,0 @@
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"]

1
Dockerfile Symbolic link
View File

@ -0,0 +1 @@
docker/Dockerfile.alpine

450
README.md
View File

@ -1,41 +1,459 @@
## Hello world
# General
![Build status badge](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master)
![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master)
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.
## Introduction
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.
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
```cpp
// Required on Windows
ix::initNetSystem();
* macOS
* iOS
* Linux
* Android
* Windows
// Our websocket object
## Examples
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
Here is what the client API looks like.
```
ix::WebSocket webSocket;
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)
// Optional heart beat, sent every 45 seconds when there is not any traffic
// to make sure that load balancers do not kill an idle connection.
webSocket.setHeartBeatPeriod(45);
// Per message deflate connection is enabled by default. You can tweak its parameters or disable it
webSocket.disablePerMessageDeflate();
// Setup a callback to be fired 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");
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
webSocket.sendBinary("some serialized binary data");
// ... finally ...
// Stop the connection
webSocket.stop()
```
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.
Here is what the server API looks like. Note that server support is very recent and subject to changes.
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).
```
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
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.
server.setOnConnectionCallback(
[&server](std::shared_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cerr << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cerr << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
webSocket->send(msg->str, msg->binary);
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
Here is what the HTTP client API looks like.
```
//
// Preparation
//
HttpClient httpClient;
HttpRequestArgsPtr args = httpClient.createRequest();
// Custom headers can be set
WebSocketHttpHeaders headers;
headers["Foo"] = "bar";
args->extraHeaders = headers;
// Timeout options
args->connectTimeout = connectTimeout;
args->transferTimeout = transferTimeout;
// Redirect options
args->followRedirects = followRedirects;
args->maxRedirects = maxRedirects;
// Misc
args->compress = compress; // Enable gzip compression
args->verbose = verbose;
args->logger = [](const std::string& msg)
{
std::cout << msg;
};
//
// Synchronous Request
//
HttpResponsePtr out;
std::string url = "https://www.google.com";
// HEAD request
out = httpClient.head(url, args);
// GET request
out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
out = httpClient.post(url, httpParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
//
// Result
//
auto errorCode = response->errorCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
auto errorCode = response->errorCode; // 200, 404, etc...
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
auto payload = response->payload; // All the bytes from the response as an std::string
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
auto uploadSize = response->uploadSize; // Byte count of uploaded data
auto downloadSize = response->downloadSize; // Byte count of downloaded data
//
// Asynchronous Request
//
bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results
}
);
// ok will be false if your httpClient is not async
```
Here is what the HTTP server API looks like. Note that HTTP server support is very, very recent and subject to changes.
```
ix::HttpServer server(port, hostname);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
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.
```
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
{
// Build a string for the response
std::stringstream ss;
ss << request->method
<< " "
<< request->uri;
std::string content = ss.str();
return std::make_shared<HttpResponse>(200, "OK",
HttpErrorCode::Ok,
WebSocketHttpHeaders(),
content);
}
```
## Build
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
There is a unittest which can be executed by typing `make test`.
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
```
brew tap bsergean/IXWebSocket
brew install IXWebSocket
```
## Implementation details
### Per Message Deflate compression.
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL is used on Android and Linux, mbedTLS is used on Windows.
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled.
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
## Limitations
* On Windows TLS is not setup yet to validate certificates.
* There is no convenient way to embed a ca cert.
* No utf-8 validation is made when sending TEXT message with sendText()
* 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.
## C++ code organization
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+ --- Public
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
| | Each connection is handled in a new OS thread.
| |
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocketHandshake | Establish the connection between client and server.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
| |
| IXSocketConnect | Connect to the remote host (client).
| |
+-----------------------+
| |
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
| |
+-----------------------+
```
## API
### Sending messages
`websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. ReadyState::Connecting - The connection is not yet open.
2. ReadyState::Open - The connection is open and ready to communicate.
3. ReadyState::Closing - The connection is in the process of closing.
4. ReadyState::Closed - The connection is closed or could not be opened.
### 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`.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : msg->headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
std::cout << "disconnected" << std::endl;
// The server can send an explicit code and reason for closing.
// This data can be accessed through the closeInfo object.
std::cout << msg->closeInfo.code << std::endl;
std::cout << msg->closeInfo.reason << std::endl;
}
}
);
```
### Error notification
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->eventInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
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.
```
std::string url("wss://example.com");
websocket.configure(url);
```
### Ping/Pong support
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.
```
webSocket.setOnMessageCallback(
[](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << msg->str << std::endl;
}
}
);
```
A ping message can be sent to the server, with an optional data string.
```
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
```
### Heartbeat.
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.
```
webSocket.setHeartBeatPeriod(45);
```

View File

@ -1,11 +0,0 @@
# 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.

View File

@ -2,21 +2,13 @@ image:
- Visual Studio 2017
install:
- cd C:\Tools\vcpkg
- git pull
- .\bootstrap-vcpkg.bat
- cd %APPVEYOR_BUILD_FOLDER%
- ls -al
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- vcpkg install zlib:x64-windows
- vcpkg install mbedtls:x64-windows
- cd test
- mkdir build
- cd build
- cmake -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DUSE_WS=1 -DUSE_TEST=1 -DUSE_TLS=1 -G"NMake Makefiles" ..
- cmake -G"NMake Makefiles" ..
- nmake
- cd ..
- cd test
- ..\build\test\ixwebsocket_unittest.exe
cache: c:\tools\vcpkg\installed\
- ixwebsocket_unittest.exe
build: off

View File

@ -1,67 +1,43 @@
version: "3"
services:
# 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
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
# 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
ws:
security_opt:
- seccomp:unconfined
cap_add:
- SYS_PTRACE
stdin_open: true
tty: true
volumes:
- /Users/bsergeant/src/foss:/home/bsergean/src/foss
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
networks:
ws-net:

View File

@ -14,7 +14,7 @@ COPY --chown=app:app . /opt
WORKDIR /opt
USER app
RUN [ "make", "ws_install" ]
RUN [ "make" ]
FROM alpine as runtime
@ -30,5 +30,4 @@ USER app
WORKDIR /home/app
ENTRYPOINT ["ws"]
EXPOSE 8008
CMD ["--help"]

View File

@ -1,24 +0,0 @@
# Build time
FROM ubuntu:disco as build
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update
RUN apt-get -y install wget
RUN mkdir -p /tmp/cmake
WORKDIR /tmp/cmake
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
RUN apt-get -y install g++
RUN apt-get -y install libssl-dev
RUN apt-get -y install libz-dev
RUN apt-get -y install make
RUN apt-get -y install python
COPY . .
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
# RUN ["make", "test"]
CMD ["sh"]

View File

@ -1,396 +0,0 @@
# Changelog
All changes to this project will be documented in this file.
## [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
Framentation: data and continuation blocks received out of order (fix autobahn test: 5.9 through 5.20 Fragmentation)
## [5.1.4] - 2019-09-03
Sending invalid UTF-8 TEXT message should fail and close the connection (fix **tons** of autobahn test: 6.X UTF-8 Handling)
## [5.1.3] - 2019-09-03
Message type (TEXT or BINARY) is invalid for received fragmented messages (fix autobahn test: 5.3 through 5.8 Fragmentation)
## [5.1.2] - 2019-09-02
Ping and Pong messages cannot be fragmented (fix autobahn test: 5.1 and 5.2 Fragmentation)
## [5.1.1] - 2019-09-01
Close connections when reserved bits are used (fix autobahn test: 3.X Reserved Bits)
## [5.1.0] - 2019-08-31
- ws autobahn / Add code to test websocket client compliance with the autobahn test-suite
- 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 / 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
- User-Agent header is set when not specified.
- New option to cap the max wait between reconnection attempts. Still default to 10s. (setMaxWaitBetweenReconnectionRetries).
```
ws connect --max_wait 5000 ws://example.com # will only wait 5 seconds max between reconnection attempts
```
## [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
If you run against `ws echo_server` you will see the headers being received printed in the terminal.
```
ws connect -H "foo: bar" -H "baz: buz" ws://127.0.0.1:8008
```
- CobraConnection: sets a unique id field for all messages sent to [cobra](https://github.com/machinezone/cobra).
- CobraConnection: sets a counter as a field for each event published.
## [5.0.6] - 2019-08-22
- Windows: silly compile error (poll should be in the global namespace)
## [5.0.5] - 2019-08-22
- Windows: use select instead of WSAPoll, through a poll wrapper
## [5.0.4] - 2019-08-20
- Windows build fixes (there was a problem with the use of ::poll that has a different name on Windows (WSAPoll))
## [5.0.3] - 2019-08-14
- CobraMetricThreadedPublisher _enable flag is an atomic, and CobraMetricsPublisher is enabled by default
## [5.0.2] - 2019-08-01
- ws cobra_subscribe has a new -q (quiet) option
- ws cobra_subscribe knows to and display msg stats (count and # of messages received per second)
- ws cobra_subscribe, cobra_to_statsd and cobra_to_sentry commands have a new option, --filter to restrict the events they want to receive
## [5.0.1] - 2019-07-25
- ws connect command has a new option to send in binary mode (still default to text)
- ws connect command has readline history thanks to libnoise-cpp. Now ws connect one can use using arrows to lookup previous sent messages and edit them
## [5.0.0] - 2019-06-23
### Changed
- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files.
- IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved
- cobra_to_sentry / backtraces are reversed and line number is not extracted correctly
- mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found
- travis CI uses g++ on Linux
## [4.0.0] - 2019-06-09
### Changed
- WebSocket::send() sends message in TEXT mode by default
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
- WebSocket::send takes a third arg, binary which default to true (can be text too)
- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg
- Add explicit WebSocket::sendBinary method
- New headers + WebSocketMessage class to hold message data, still not used across the board
- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility.
- ws echo_server has a -g option to print a greeting message on connect
- IXSocketMbedTLS: better error handling in close and connect
## [3.1.2] - 2019-06-06
### Added
- ws connect has a -x option to disable per message deflate
- Add WebSocket::disablePerMessageDeflate() option.
## [3.0.0] - 2019-06-xx
### Changed
- TLS, aka SSL works on Windows (websocket and http clients)
- ws command line tool build on Windows
- Async API for HttpClient
- HttpClient API changed to use shared_ptr for response and request

View File

@ -1,61 +0,0 @@
## Build
### CMake
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
```
mkdir build # make a build dir so that you can build out of tree.
cd build
cmake -DUSE_TLS=1 ..
make -j
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
```
Headers and a static library will be installed to the target dir.
There is a unittest which can be executed by typing `make test`.
Options for building:
* `-DUSE_TLS=1` will enable TLS support
* `-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).
```
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`, 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
```
### Docker
There is a Dockerfile for running the unittest on Linux, and to run the `ws` tool. It is also available on the docker registry.
```
docker run bsergean/ws
```
To use docker-compose you must make a docker container first.
```
$ make docker
...
$ docker compose up &
...
$ docker exec -it ixwebsocket_ws_1 bash
app@ca2340eb9106:~$ ws --help
ws is a websocket tool
...
```

View File

@ -1,81 +0,0 @@
## 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
```

View File

@ -1,77 +0,0 @@
## Implementation details
### Per Message Deflate compression.
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
### TLS/SSL
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL is used on Android and Linux, mbedTLS is used on Windows.
### Polling and background thread work
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
### Automatic reconnection
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled.
### Large messages
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
### Testing
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 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 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.
* 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.
## C++ code organization
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
```
+-----------------------+ --- Public
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
| |
+-----------------------+
| |
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
| | Each connection is handled in a new OS thread.
| |
+-----------------------+ --- Private
| |
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
| |
+-----------------------+
| |
| IXWebSocketHandshake | Establish the connection between client and server.
| |
+-----------------------+
| |
| IXWebSocket | ws:// Unencrypted Socket handler
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
| | Can be used on macOS too.
+-----------------------+
| |
| IXSocketConnect | Connect to the remote host (client).
| |
+-----------------------+
| |
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
| |
+-----------------------+
```

View File

@ -1,51 +0,0 @@
![Alt text](https://travis-ci.org/machinezone/IXWebSocket.svg?branch=master)
## Introduction
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client and server HTTP communication. *TLS* aka *SSL* is supported. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
* macOS
* iOS
* Linux
* Android
* Windows
* FreeBSD
## Example code
```cpp
// Required on Windows
ix::initNetSystem();
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Setup a callback to be fired 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");
```
## 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.

View File

@ -1,466 +0,0 @@
# Examples
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
## Windows note
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()
{
ix::initNetSystem();
...
ix::uninitNetSystem();
return 0;
}
```
## WebSocket client API
```cpp
#include <ixwebsocket/IXWebSocket.h>
...
// Our websocket object
ix::WebSocket webSocket;
std::string url("ws://localhost:8080/");
webSocket.setUrl(url);
// Optional heart beat, sent every 45 seconds when there is not any traffic
// to make sure that load balancers do not kill an idle connection.
webSocket.setHeartBeatPeriod(45);
// Per message deflate connection is enabled by default. You can tweak its parameters or disable it
webSocket.disablePerMessageDeflate();
// Setup a callback to be fired 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");
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
webSocket.sendBinary("some serialized binary data");
// ... finally ...
// Stop the connection
webSocket.stop()
```
### Sending messages
`websocket.send("foo")` will send a message.
If the connection was closed and sending failed, the return value will be set to false.
### ReadyState
`getReadyState()` returns the state of the connection. There are 4 possible states.
1. ReadyState::Connecting - The connection is not yet open.
2. ReadyState::Open - The connection is open and ready to communicate.
3. ReadyState::Closing - The connection is in the process of closing.
4. ReadyState::Closed - The connection is closed or could not be opened.
### 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`.
```cpp
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : msg->headers)
{
std::cout << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
std::cout << "disconnected" << std::endl;
// The server can send an explicit code and reason for closing.
// This data can be accessed through the closeInfo object.
std::cout << msg->closeInfo.code << std::endl;
std::cout << msg->closeInfo.reason << std::endl;
}
}
);
```
### Error notification
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)
{
std::stringstream ss;
ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->eventInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl;
std::cout << ss.str() << std::endl;
}
}
);
```
### start, stop
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
### Configuring the remote url
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);
```
### Ping/Pong support
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 ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << msg->str << std::endl;
}
}
);
```
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");
```
### Heartbeat.
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);
```
### Supply extra HTTP headers.
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
```
The technique to calculate wait time is called [exponential
backoff](https://docs.aws.amazon.com/general/latest/gr/api-retries.html). Here
are the default waiting times between attempts (from connecting with `ws connect ws://foo.com`)
```
> Connection error: Got bad status connecting to foo.com, status: 301, HTTP Status line: HTTP/1.1 301 Moved Permanently
#retries: 1
Wait time(ms): 100
#retries: 2
Wait time(ms): 200
#retries: 3
Wait time(ms): 400
#retries: 4
Wait time(ms): 800
#retries: 5
Wait time(ms): 1600
#retries: 6
Wait time(ms): 3200
#retries: 7
Wait time(ms): 6400
#retries: 8
Wait time(ms): 10000
```
The waiting time is capped by default at 10s between 2 attempts, but that value can be changed and queried.
```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>
...
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
server.setOnConnectionCallback(
[&server](std::shared_ptr<WebSocket> webSocket,
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cerr << "New connection" << std::endl;
// A connection state object is available, and has a default id
// You can subclass ConnectionState and pass an alternate factory
// to override it. It is useful if you want to store custom
// attributes per connection (authenticated bool flag, attributes, etc...)
std::cerr << "id: " << connectionState->getId() << std::endl;
// The uri the client did connect to.
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
std::cerr << "Headers:" << std::endl;
for (auto it : msg->openInfo.headers)
{
std::cerr << it.first << ": " << it.second << std::endl;
}
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
// For an echo server, we just send back to the client whatever was received by the server
// All connected clients are available in an std::set. See the broadcast cpp example.
// Second parameter tells whether we are sending the message in binary or text mode.
// Here we send it in the same mode as it was received.
webSocket->send(msg->str, msg->binary);
}
}
);
}
);
auto res = server.listen();
if (!res.first)
{
// Error handling
return 1;
}
// Run the server in the background. Server can be stoped by calling server.stop()
server.start();
// Block until server.stop() is called.
server.wait();
```
## HTTP client API
```cpp
#include <ixwebsocket/IXHttpClient.h>
...
//
// Preparation
//
HttpClient httpClient;
HttpRequestArgsPtr args = httpClient.createRequest();
// Custom headers can be set
WebSocketHttpHeaders headers;
headers["Foo"] = "bar";
args->extraHeaders = headers;
// Timeout options
args->connectTimeout = connectTimeout;
args->transferTimeout = transferTimeout;
// Redirect options
args->followRedirects = followRedirects;
args->maxRedirects = maxRedirects;
// Misc
args->compress = compress; // Enable gzip compression
args->verbose = verbose;
args->logger = [](const std::string& msg)
{
std::cout << msg;
};
//
// Synchronous Request
//
HttpResponsePtr out;
std::string url = "https://www.google.com";
// HEAD request
out = httpClient.head(url, args);
// GET request
out = httpClient.get(url, args);
// POST request with parameters
HttpParameters httpParameters;
httpParameters["foo"] = "bar";
out = httpClient.post(url, httpParameters, args);
// POST request with a body
out = httpClient.post(url, std::string("foo=bar"), args);
//
// Result
//
auto statusCode = response->statusCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
auto errorCode = response->errorCode; // 200, 404, etc...
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
auto payload = response->payload; // All the bytes from the response as an std::string
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
auto uploadSize = response->uploadSize; // Byte count of uploaded data
auto downloadSize = response->downloadSize; // Byte count of downloaded data
//
// Asynchronous Request
//
bool async = true;
HttpClient httpClient(async);
auto args = httpClient.createRequest(url, HttpClient::kGet);
// Push the request to a queue,
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
{
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
auto statusCode = response->statusCode; // acess results
}
);
// ok will be false if your httpClient is not async
```
## HTTP server API
```cpp
#include <ixwebsocket/IXHttpServer.h>
ix::HttpServer server(port, hostname);
auto res = server.listen();
if (!res.first)
{
std::cerr << res.second << std::endl;
return 1;
}
server.start();
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
{
// Build a string for the response
std::stringstream ss;
ss << request->method
<< " "
<< request->uri;
std::string content = ss.str();
return std::make_shared<HttpResponse>(200, "OK",
HttpErrorCode::Ok,
WebSocketHttpHeaders(),
content);
}
```

View File

@ -1,369 +0,0 @@
## General
ws is a command line tool that should exercise most of the IXWebSocket code, and provide example code.
```
ws is a websocket tool
Usage: ws [OPTIONS] SUBCOMMAND
Options:
-h,--help Print this help message and exit
Subcommands:
send Send a file
receive Receive a file
transfer Broadcasting server
connect Connect to a remote server
chat Group chat
echo_server Echo server
broadcast_server Broadcasting server
ping Ping pong
curl HTTP Client
redis_publish Redis publisher
redis_subscribe Redis subscriber
cobra_subscribe Cobra subscriber
cobra_publish Cobra publisher
cobra_to_statsd Cobra to statsd
cobra_to_sentry Cobra to sentry
snake Snake server
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
```
# Start transfer server, which is just a broadcast server at this point
ws transfer # running on port 8080.
# Start receiver first
ws receive ws://localhost:8080
# Then send a file. File will be received and written to disk by the receiver process
ws send ws://localhost:8080 /file/to/path
```
## HTTP Client
```
$ ws curl --help
HTTP Client
Usage: ws curl [OPTIONS] url
Positionals:
url TEXT REQUIRED Connection url
Options:
-h,--help Print this help message and exit
-d TEXT Form data
-F TEXT Form data
-H TEXT Header
--output TEXT Output file
-I Send a HEAD request
-L Follow redirects
--max-redirects INT Max Redirects
-v Verbose
-O Save output to disk
--compress Enable gzip compression
--connect-timeout INT Connection timeout
--transfer-timeout INT Transfer timeout
```
## Cobra client and server
[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.

View File

@ -1,35 +0,0 @@
#
# 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} )

View File

@ -1,19 +0,0 @@
#
# 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 . )

View File

@ -1,54 +0,0 @@
#
# 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()

View File

@ -1,30 +0,0 @@
#
# 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} )

View File

@ -1,35 +0,0 @@
#
# 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} )

View File

@ -1,299 +0,0 @@
/*
* 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

View File

@ -1,67 +0,0 @@
/*
* 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

View File

@ -1,61 +0,0 @@
/*
* 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

View File

@ -10,13 +10,14 @@
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;
@ -29,4 +30,4 @@ namespace ix
return isCancellationRequested;
}
} // namespace ix
}

View File

@ -10,8 +10,7 @@ namespace ix
{
std::atomic<uint64_t> ConnectionState::_globalId(0);
ConnectionState::ConnectionState()
: _terminated(false)
ConnectionState::ConnectionState() : _terminated(false)
{
computeId();
}
@ -40,4 +39,5 @@ namespace ix
{
_terminated = true;
}
} // namespace ix
}

View File

@ -5,24 +5,22 @@
*/
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <chrono>
#include <string.h>
#include <thread>
#include <chrono>
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms
const int64_t DNSLookup::kDefaultWait = 10; // 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) :
_port(port),
_wait(wait),
_res(nullptr),
_done(false)
{
;
setHostname(hostname);
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
@ -38,7 +36,8 @@ 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);
@ -49,29 +48,29 @@ namespace ix
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable)
bool blocking)
{
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
return blocking ? resolveBlocking(errMsg, isCancellationRequested)
: resolveAsync(errMsg, isCancellationRequested);
}
struct addrinfo* DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
struct addrinfo* DNSLookup::resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
// Maybe a cancellation request got in before the background thread terminated ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
return getAddrInfo(getHostname(), _port, errMsg);
}
struct addrinfo* DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -90,24 +89,23 @@ namespace ix
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
int port = _port;
std::string hostname(_hostname);
_thread = std::thread(&DNSLookup::run, this, self, getHostname(), _port);
_thread.detach();
// 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);
t->detach();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
while (!_done)
{
// Wait for 1 milliseconds, to see if the bg thread has terminated.
// We do not use a condition variable to wait, as destroying this one
// if the bg thread is alive can cause undefined behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
// Wait for 10 milliseconds on the condition variable, to see
// if the bg thread has terminated.
if (_condition.wait_for(lock, std::chrono::milliseconds(_wait)) == std::cv_status::no_timeout)
{
// Background thread has terminated, so we can break of this loop
break;
}
// Were we cancelled ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
@ -115,7 +113,7 @@ namespace ix
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested())
if (isCancellationRequested && isCancellationRequested())
{
errMsg = "cancellation requested";
return nullptr;
@ -125,9 +123,7 @@ 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, const 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
@ -135,16 +131,29 @@ namespace ix
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock())
if (self.lock())
{
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_condition.notify_one();
_done = true;
}
}
void DNSLookup::setHostname(const std::string& hostname)
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
_hostname = hostname;
}
const std::string& DNSLookup::getHostname()
{
std::lock_guard<std::mutex> lock(_hostnameMutex);
return _hostname;
}
void DNSLookup::setErrMsg(const std::string& errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
@ -168,4 +177,4 @@ namespace ix
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
} // namespace ix
}

View File

@ -12,10 +12,11 @@
#include "IXCancellationRequest.h"
#include <atomic>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <set>
#include <string>
#include <thread>
#include <memory>
struct addrinfo;
@ -29,19 +30,22 @@ namespace ix
struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable = true);
bool blocking = false);
private:
struct addrinfo* resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveAsync(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveBlocking(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
void run(std::weak_ptr<DNSLookup> self, std::string hostname, int port); // thread runner
void run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port); // thread runner
void setHostname(const std::string& hostname);
const std::string& getHostname();
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
@ -50,9 +54,10 @@ namespace ix
struct addrinfo* getRes();
std::string _hostname;
std::mutex _hostnameMutex;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo* _res;
std::mutex _resMutex;
@ -61,5 +66,10 @@ namespace ix
std::mutex _errMsgMutex;
std::atomic<bool> _done;
std::thread _thread;
std::condition_variable _condition;
std::mutex _conditionVariableMutex;
const static int64_t kDefaultWait;
};
} // namespace ix

View File

@ -1,25 +0,0 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXExponentialBackoff.h"
#include <cmath>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries)
{
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0;
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
{
wait_time = maxWaitBetweenReconnectionRetries;
}
return wait_time;
}
} // namespace ix

View File

@ -1,15 +0,0 @@
/*
* IXExponentialBackoff.h
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <cstdint>
namespace ix
{
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
uint32_t maxWaitBetweenReconnectionRetries);
} // namespace ix

View File

@ -5,9 +5,9 @@
*/
#include "IXHttp.h"
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include <sstream>
#include <vector>
@ -27,38 +27,7 @@ namespace ix
return out;
}
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)
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;
@ -115,8 +84,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
@ -162,6 +131,8 @@ 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
}

View File

@ -6,10 +6,9 @@
#pragma once
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXProgressCallback.h"
#include <tuple>
#include <unordered_map>
namespace ix
{
@ -66,8 +65,7 @@ namespace ix
};
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
using HttpParameters = std::unordered_map<std::string, std::string>;
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
using HttpParameters = std::map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
@ -77,7 +75,6 @@ namespace ix
std::string verb;
WebSocketHttpHeaders extraHeaders;
std::string body;
std::string multipartBoundary;
int connectTimeout;
int transferTimeout;
bool followRedirects;
@ -114,13 +111,10 @@ namespace ix
class Http
{
public:
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(
std::shared_ptr<Socket> socket);
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(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::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
static std::string trim(const std::string& str);
};
} // namespace ix
}

View File

@ -5,17 +5,15 @@
*/
#include "IXHttpClient.h"
#include "IXSocketFactory.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h"
#include <assert.h>
#include <cstring>
#include <iomanip>
#include <random>
#include "IXSocketFactory.h"
#include <sstream>
#include <iomanip>
#include <vector>
#include <cstring>
#include <zlib.h>
namespace ix
@ -26,9 +24,7 @@ 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;
@ -44,12 +40,8 @@ namespace ix
_thread.join();
}
void HttpClient::setTLSOptions(const SocketTLSOptions& tlsOptions)
{
_tlsOptions = tlsOptions;
}
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url, const std::string& verb)
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url,
const std::string& verb)
{
auto request = std::make_shared<HttpRequestArgs>();
request->url = url;
@ -60,8 +52,6 @@ namespace ix
bool HttpClient::performRequest(HttpRequestArgsPtr args,
const OnResponseCallback& onResponseCallback)
{
assert(_async && "HttpClient needs its async parameter set to true "
"in order to call performRequest");
if (!_async) return false;
// Enqueue the task
@ -112,11 +102,12 @@ 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.
@ -136,30 +127,20 @@ 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, -1, errorMsg, _tlsOptions);
_socket = createSocket(tls, errorMsg);
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
@ -169,8 +150,7 @@ namespace ix
if (args->compress)
{
ss << "Accept-Encoding: gzip"
<< "\r\n";
ss << "Accept-Encoding: gzip" << "\r\n";
}
// Append extra headers
@ -182,14 +162,13 @@ 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
if (headers.find("User-Agent") == headers.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
ss << "User-Agent: ixwebsocket" << "\r\n";
}
if (verb == kPost || verb == kPut)
@ -199,16 +178,7 @@ namespace ix
// Set default Content-Type if unspecified
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
{
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 << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
}
ss << "\r\n";
ss << body;
@ -231,14 +201,9 @@ 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
@ -252,7 +217,8 @@ 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);
@ -261,14 +227,9 @@ 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();
@ -280,14 +241,9 @@ 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)
@ -300,14 +256,9 @@ 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);
@ -317,14 +268,9 @@ 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 ?
@ -333,45 +279,30 @@ 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:
@ -384,19 +315,15 @@ 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;
}
@ -412,14 +339,9 @@ 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;
@ -430,26 +352,23 @@ 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;
@ -458,14 +377,9 @@ 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;
@ -478,14 +392,9 @@ 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();
@ -497,39 +406,32 @@ 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);
}
@ -568,7 +470,8 @@ 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);
@ -596,64 +499,21 @@ 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();
}
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)
bool HttpClient::gzipInflate(
const std::string& in,
std::string& out)
{
z_stream inflateState;
std::memset(&inflateState, 0, sizeof(inflateState));
@ -664,13 +524,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;
@ -690,31 +550,22 @@ 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
}

View File

@ -6,10 +6,9 @@
#pragma once
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXHttp.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>
@ -59,18 +58,8 @@ 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;
@ -97,7 +86,5 @@ namespace ix
std::shared_ptr<Socket> _socket;
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
SocketTLSOptions _tlsOptions;
};
} // namespace ix

View File

@ -5,12 +5,13 @@
*/
#include "IXHttpServer.h"
#include "IXNetSystem.h"
#include "IXSocketConnect.h"
#include "IXUserAgent.h"
#include <fstream>
#include "IXSocketFactory.h"
#include "IXNetSystem.h"
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
namespace
@ -27,7 +28,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);
}
@ -38,13 +39,15 @@ 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();
}
@ -68,11 +71,18 @@ namespace ix
_onConnectionCallback = callback;
}
void HttpServer::handleConnection(std::shared_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
void HttpServer::handleConnection(
int fd,
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
@ -98,33 +108,37 @@ 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->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";
@ -134,39 +148,11 @@ 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
}

View File

@ -6,9 +6,9 @@
#pragma once
#include "IXHttp.h"
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include "IXHttp.h"
#include <functional>
#include <memory>
#include <mutex>
@ -34,18 +34,17 @@ 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(std::shared_ptr<Socket>,
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
void setDefaultConnectionCallback();
};
} // namespace ix

View File

@ -15,8 +15,9 @@ namespace ix
WSADATA wsaData;
int err;
// Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
return err == 0;
@ -29,84 +30,10 @@ namespace ix
{
#ifdef _WIN32
int err = WSACleanup();
return err == 0;
#else
return true;
#endif
}
//
// That function could 'return WSAPoll(pfd, nfds, timeout);'
// but WSAPoll is said to have weird behaviors on the internet
// (the curl folks have had problems with it).
//
// So we make it a select wrapper
//
int poll(struct pollfd* fds, nfds_t nfds, int timeout)
{
#ifdef _WIN32
int maxfd = 0;
fd_set readfds, writefds, errorfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&errorfds);
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
if (fd->fd > maxfd)
{
maxfd = fd->fd;
}
if ((fd->events & POLLIN))
{
FD_SET(fd->fd, &readfds);
}
if ((fd->events & POLLOUT))
{
FD_SET(fd->fd, &writefds);
}
if ((fd->events & POLLERR))
{
FD_SET(fd->fd, &errorfds);
}
}
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
int ret = select(maxfd + 1, &readfds, &writefds, &errorfds, timeout != -1 ? &tv : NULL);
if (ret < 0)
{
return ret;
}
for (nfds_t i = 0; i < nfds; ++i)
{
struct pollfd* fd = &fds[i];
fd->revents = 0;
if (FD_ISSET(fd->fd, &readfds))
{
fd->revents |= POLLIN;
}
if (FD_ISSET(fd->fd, &writefds))
{
fd->revents |= POLLOUT;
}
if (FD_ISSET(fd->fd, &errorfds))
{
fd->revents |= POLLERR;
}
}
return ret;
#else
return ::poll(fds, nfds, timeout);
#endif
}
} // namespace ix
}

View File

@ -12,18 +12,11 @@
#include <basetsd.h>
#include <io.h>
#include <ws2def.h>
// Define our own poll on Windows, as a wrapper on top of select
typedef unsigned long int nfds_t;
#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>
#include <sys/socket.h>
#include <sys/stat.h>
@ -35,6 +28,4 @@ namespace ix
{
bool initNetSystem();
bool uninitNetSystem();
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
} // namespace ix

View File

@ -42,4 +42,5 @@ namespace ix
{
return -1;
}
} // namespace ix
}

View File

@ -26,13 +26,14 @@
#include "IXSelectInterruptEventFd.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
#include <string.h> // for strerror
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sstream>
namespace ix
{
@ -112,4 +113,4 @@ namespace ix
{
return _eventfd;
}
} // namespace ix
}

View File

@ -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
}

View File

@ -10,12 +10,12 @@
#include "IXSelectInterruptPipe.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <sstream>
#include <string.h> // for strerror
#include <unistd.h> // for write
#include <string.h> // for strerror
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sstream>
namespace ix
{
@ -143,4 +143,4 @@ namespace ix
return _fildes[kPipeReadIndex];
}
} // namespace ix
}

View File

@ -5,20 +5,21 @@
*/
#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
@ -31,9 +32,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())
{
;
}
@ -43,42 +44,41 @@ namespace ix
close();
}
PollResultType Socket::poll(bool readyToRead,
int timeoutMs,
int sockfd,
std::shared_ptr<SelectInterrupt> selectInterrupt)
PollResultType Socket::poll(int timeoutMs)
{
//
// 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.
//
nfds_t nfds = 1;
struct pollfd fds[2];
return isReadyToRead(timeoutMs);
}
fds[0].fd = sockfd;
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
fds[0].events |= POLLERR;
PollResultType Socket::select(bool readyToRead, int timeoutMs)
{
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
// File descriptor used to interrupt select when needed
int interruptFd = -1;
if (selectInterrupt)
fd_set* fds = (readyToRead) ? &rfds : & wfds;
if (_sockfd != -1)
{
interruptFd = selectInterrupt->getFd();
if (interruptFd != -1)
{
nfds = 2;
fds[1].fd = interruptFd;
fds[1].events = POLLIN;
}
FD_SET(_sockfd, fds);
}
int ret = ix::poll(fds, nfds, timeoutMs);
// File descriptor used to interrupt select when needed
int interruptFd = _selectInterrupt->getFd();
if (interruptFd != -1)
{
FD_SET(interruptFd, fds);
}
struct timeval timeout;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = 1000 * (timeoutMs % 1000);
// Compute the highest fd.
int sockfd = _sockfd;
int nfds = (std::max)(sockfd, interruptFd);
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
(timeoutMs < 0) ? nullptr : &timeout);
PollResultType pollResult = PollResultType::ReadyForRead;
if (ret < 0)
@ -89,9 +89,9 @@ namespace ix
{
pollResult = PollResultType::Timeout;
}
else if (interruptFd != -1 && fds[1].revents & POLLIN)
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
{
uint64_t value = selectInterrupt->read();
uint64_t value = _selectInterrupt->read();
if (value == kSendRequest)
{
@ -102,35 +102,13 @@ namespace ix
pollResult = PollResultType::CloseRequest;
}
}
else if (sockfd != -1 && readyToRead && fds[0].revents & POLLIN)
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
{
pollResult = PollResultType::ReadyForRead;
}
else if (sockfd != -1 && !readyToRead && fds[0].revents & POLLOUT)
else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
{
pollResult = PollResultType::ReadyForWrite;
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (fds[0].revents & POLLERR)
{
pollResult = PollResultType::Error;
}
#else
int optval = -1;
socklen_t optlen = sizeof(optval);
// 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)
{
pollResult = PollResultType::Error;
// set errno to optval so that external callers can have an
// appropriate error description when calling strerror
errno = optval;
}
#endif
}
return pollResult;
@ -144,7 +122,7 @@ namespace ix
}
bool readyToRead = true;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
return select(readyToRead, timeoutMs);
}
PollResultType Socket::isReadyToWrite(int timeoutMs)
@ -155,7 +133,7 @@ namespace ix
}
bool readyToRead = false;
return poll(readyToRead, timeoutMs, _sockfd, _selectInterrupt);
return select(readyToRead, timeoutMs);
}
// Wake up from poll/select by writing to the pipe which is watched by select
@ -164,16 +142,6 @@ 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,
@ -209,7 +177,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)
@ -264,14 +232,14 @@ namespace ix
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
int offset = 0;
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
ssize_t ret = send((char*) &str[offset], len);
ssize_t ret = send(buffer, len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
@ -282,8 +250,8 @@ namespace ix
}
else
{
offset += ret;
len -= ret;
buffer += ret;
len -= ret;
continue;
}
}
@ -300,7 +268,8 @@ namespace ix
}
}
bool Socket::readByte(void* buffer, const CancellationRequest& isCancellationRequested)
bool Socket::readByte(void* buffer,
const CancellationRequest& isCancellationRequested)
{
while (true)
{
@ -339,7 +308,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))
{
@ -372,16 +341,19 @@ 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)
{
output.insert(output.end(), _readBuffer.begin(), _readBuffer.begin() + ret);
}
else if (ret <= 0 && !Socket::isWaitNeeded())
if (ret <= 0 && !Socket::isWaitNeeded())
{
// Error
return std::make_pair(false, std::string());
}
else
{
output.insert(output.end(),
_readBuffer.begin(),
_readBuffer.begin() + ret);
}
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
@ -393,6 +365,7 @@ 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
}

View File

@ -64,8 +64,6 @@ 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,
@ -90,12 +88,6 @@ namespace ix
static bool isWaitNeeded();
static void closeSocket(int fd);
static PollResultType poll(bool readyToRead,
int timeoutMs,
int sockfd,
std::shared_ptr<SelectInterrupt> selectInterrupt = nullptr);
// Used as special codes for pipe communication
static const uint64_t kSendRequest;
static const uint64_t kCloseRequest;
@ -105,6 +97,8 @@ namespace ix
std::mutex _socketMutex;
private:
PollResultType select(bool readyToRead, int timeoutMs);
static const int kDefaultPollTimeout;
static const int kDefaultPollNoTimeout;

View File

@ -6,13 +6,11 @@
* Adapted from Satori SDK Apple SSL code.
*/
#include "IXSocketAppleSSL.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>
@ -20,16 +18,131 @@
#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(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _sslContext(nullptr)
, _tlsOptions(tlsOptions)
SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
_sslContext(nullptr)
{
;
}
@ -39,117 +152,6 @@ 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,
@ -165,37 +167,15 @@ namespace ix
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
SSLSetIOFuncs(_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetIOFuncs(_sslContext, read_from_socket, write_to_socket);
SSLSetConnection(_sslContext, (SSLConnectionRef) (long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
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);
}
do {
status = SSLHandshake(_sslContext);
} while (errSSLWouldBlock == status ||
errSSLServerAuthCompleted == status);
}
if (noErr != status)
@ -225,8 +205,7 @@ 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);
@ -235,13 +214,14 @@ 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
@ -254,11 +234,13 @@ 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;
@ -274,4 +256,4 @@ namespace ix
return -1;
}
} // namespace ix
}

View File

@ -8,7 +8,6 @@
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include "IXSocketTLSOptions.h"
#include <Security/SecureTransport.h>
#include <Security/Security.h>
#include <mutex>
@ -18,11 +17,9 @@ namespace ix
class SocketAppleSSL final : public Socket
{
public:
SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
SocketAppleSSL(int fd = -1);
~SocketAppleSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
@ -34,14 +31,8 @@ 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

View File

@ -5,35 +5,36 @@
*/
#include "IXSocketConnect.h"
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include <fcntl.h>
#include <string.h>
#include <fcntl.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";
@ -62,29 +63,55 @@ namespace ix
return -1;
}
int timeoutMs = 10;
bool readyToRead = false;
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd);
// On Linux the timeout needs to be re-initialized everytime
// http://man7.org/linux/man-pages/man2/select.2.html
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
if (pollResult == PollResultType::Timeout)
{
continue;
}
else if (pollResult == PollResultType::Error)
fd_set wfds;
fd_set efds;
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
FD_ZERO(&efds);
FD_SET(fd, &efds);
// Use select to check the status of the new connection
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
return -1;
}
else if (pollResult == PollResultType::ReadyForWrite)
// Nothing was written to the socket, wait again.
if (!FD_ISSET(fd, &wfds)) continue;
// Something was written to the socket. Check for errors.
int optval = -1;
socklen_t optlen = sizeof(optval);
#ifdef _WIN32
// On connect error, in async mode, windows will write to the exceptions fds
if (FD_ISSET(fd, &efds))
#else
// getsockopt() puts the errno value for connect into optval so 0
// means no-error.
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
optval != 0)
#endif
{
return fd;
Socket::closeSocket(fd);
errMsg = strerror(optval);
return -1;
}
else
{
Socket::closeSocket(fd);
errMsg = std::string("Connect error: ") + strerror(Socket::getErrno());
return -1;
// Success !
return fd;
}
}
@ -102,7 +129,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;
@ -111,7 +138,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)
{
//
@ -146,7 +173,8 @@ 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
}

View File

@ -8,15 +8,15 @@
#ifdef IXWEBSOCKET_USE_TLS
#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
# 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
#else
@ -27,30 +27,27 @@
namespace ix
{
std::shared_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions)
std::string& errorMsg)
{
(void) tlsOptions;
errorMsg.clear();
std::shared_ptr<Socket> socket;
if (!tls)
{
socket = std::make_shared<Socket>(fd);
socket = std::make_shared<Socket>();
}
else
{
#ifdef IXWEBSOCKET_USE_TLS
#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
# 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
#else
errorMsg = "TLS support is not enabled on this platform.";
return nullptr;
@ -64,4 +61,18 @@ namespace ix
return socket;
}
} // namespace ix
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;
}
}

View File

@ -7,15 +7,13 @@
#pragma once
#include "IXSocketTLSOptions.h"
#include <memory>
#include <string>
namespace ix
{
class Socket;
std::shared_ptr<Socket> createSocket(bool tls,
int fd,
std::string& errorMsg,
const SocketTLSOptions& tlsOptions);
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg);
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
} // namespace ix

View File

@ -9,49 +9,34 @@
*/
#include "IXSocketMbedTLS.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include <string.h>
namespace ix
{
SocketMbedTLS::SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _tlsOptions(tlsOptions)
{
initMBedTLS();
}
SocketMbedTLS::~SocketMbedTLS()
{
SocketMbedTLS::close();
close();
}
void SocketMbedTLS::initMBedTLS()
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
{
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);
}
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";
@ -59,9 +44,9 @@ namespace ix
}
if (mbedtls_ssl_config_defaults(&_conf,
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
MBEDTLS_SSL_PRESET_DEFAULT ) != 0)
{
errMsg = "Setting config default failed";
return false;
@ -69,47 +54,8 @@ namespace ix
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
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 (_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 (_tlsOptions.hasCertAndKey())
{
mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey);
}
}
// FIXME: cert verification is disabled
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
{
@ -117,7 +63,7 @@ namespace ix
return false;
}
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{
errMsg = "SNI setup failed";
return false;
@ -126,50 +72,6 @@ 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,
@ -181,9 +83,7 @@ namespace ix
if (_sockfd == -1) return false;
}
bool isClient = true;
bool initialized = init(host, isClient, errMsg);
if (!initialized)
if (!init(host, errMsg))
{
close();
return false;
@ -196,7 +96,8 @@ 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)
{
@ -221,8 +122,6 @@ 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();
}
@ -237,18 +136,13 @@ 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;
}
}
@ -257,7 +151,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)
@ -281,4 +175,4 @@ namespace ix
}
}
} // namespace ix
}

View File

@ -7,15 +7,12 @@
#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
@ -23,11 +20,9 @@ namespace ix
class SocketMbedTLS final : public Socket
{
public:
SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1);
SocketMbedTLS() = default;
~SocketMbedTLS();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
@ -43,15 +38,10 @@ 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, bool isClient, std::string& errMsg);
void initMBedTLS();
bool init(const std::string& host, std::string& errMsg);
};
} // namespace ix

View File

@ -1,38 +1,30 @@
/*
* IXSocketOpenSSL.cpp
* Author: Benjamin Sergeant, Matt DeBoer
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
*
* Adapted from Satori SDK OpenSSL code.
*/
#include "IXSocketOpenSSL.h"
#include "IXSocketConnect.h"
#include <cassert>
#include <errno.h>
#include <fnmatch.h>
#include <openssl/x509v3.h>
#include <fnmatch.h>
#include <errno.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(const SocketTLSOptions& tlsOptions, int fd)
: Socket(fd)
, _ssl_connection(nullptr)
, _ssl_context(nullptr)
, _tlsOptions(tlsOptions)
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
_ssl_connection(nullptr),
_ssl_context(nullptr)
{
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
}
@ -122,11 +114,15 @@ namespace ix
SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
if (ctx)
{
SSL_CTX_set_mode(ctx,
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
// 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_options(
ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_verify_depth(ctx, 4);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
}
return ctx;
}
@ -134,16 +130,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.";
@ -153,17 +149,18 @@ 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;
@ -176,20 +173,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;
}
@ -208,7 +205,7 @@ namespace ix
return true;
}
bool SocketOpenSSL::openSSLClientHandshake(const std::string& host, std::string& errMsg)
bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
{
while (true)
{
@ -243,274 +240,6 @@ 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,
@ -535,9 +264,13 @@ namespace ix
return false;
}
if (!handleTLSOptions(errMsg))
ERR_clear_error();
int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context);
if (cert_load_result == 0)
{
return false;
unsigned long ssl_err = ERR_get_error();
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
errMsg += ERR_error_string(ssl_err, nullptr);
}
_ssl_connection = SSL_new(_ssl_context);
@ -556,12 +289,13 @@ 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 = openSSLClientHandshake(host, errMsg);
handshakeSuccessful = openSSLHandshake(host, errMsg);
}
if (!handshakeSuccessful)
@ -608,18 +342,13 @@ 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;
}
}
@ -628,7 +357,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)
@ -660,4 +389,4 @@ namespace ix
}
}
} // namespace ix
}

View File

@ -1,14 +1,13 @@
/*
* IXSocketOpenSSL.h
* Author: Benjamin Sergeant, Matt DeBoer
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
* Author: Benjamin Sergeant
* Copyright (c) 2017-2018 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>
@ -21,11 +20,9 @@ namespace ix
class SocketOpenSSL final : public Socket
{
public:
SocketOpenSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
SocketOpenSSL(int fd = -1);
~SocketOpenSSL();
virtual bool accept(std::string& errMsg) final;
virtual bool connect(const std::string& host,
int port,
std::string& errMsg,
@ -40,17 +37,13 @@ namespace ix
void openSSLInitialize();
std::string getSSLError(int ret);
SSL_CTX* openSSLCreateContext(std::string& errMsg);
bool openSSLClientHandshake(const std::string& hostname, std::string& errMsg);
bool openSSLHandshake(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;

View File

@ -9,19 +9,16 @@
*
* 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 <WS2tcpip.h>
#include <WinSock2.h>
#include <basetsd.h>
#include <io.h>
#include <schannel.h>
#include <ws2def.h>
# include <basetsd.h>
# include <WinSock2.h>
# include <ws2def.h>
# include <WS2tcpip.h>
# include <schannel.h>
# include <io.h>
#define WIN32_LEAN_AND_MEAN
@ -29,15 +26,14 @@
#define UNICODE
#endif
#include <windows.h>
#include <winsock2.h>
#include <mstcpip.h>
#include <ntdsapi.h>
#include <ws2tcpip.h>
#include <rpc.h>
#include <ntdsapi.h>
#include <stdio.h>
#include <tchar.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#define RECV_DATA_BUF_SIZE 256
@ -54,8 +50,12 @@
// 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,9 +67,12 @@ 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);
}
@ -100,4 +103,4 @@ namespace ix
return Socket::recv(buf, nbyte);
}
} // namespace ix
}

View File

@ -5,15 +5,14 @@
*/
#include "IXSocketServer.h"
#include "IXNetSystem.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include "IXSocketFactory.h"
#include <assert.h>
#include <stdio.h>
#include "IXNetSystem.h"
#include <iostream>
#include <sstream>
#include <string.h>
#include <assert.h>
namespace ix
{
@ -25,16 +24,17 @@ 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);
fprintf(stderr, "%s\n", str.c_str());
std::cerr << str << std::endl;
}
void SocketServer::logInfo(const std::string& str)
{
std::lock_guard<std::mutex> lock(_logMutex);
fprintf(stdout, "%s\n", str.c_str());
std::cout << str << std::endl;
}
std::pair<bool, std::string> SocketServer::listen()
@ -62,18 +62,21 @@ 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());
@ -81,7 +84,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
@ -92,11 +95,12 @@ 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());
@ -109,7 +113,8 @@ 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());
@ -120,8 +125,6 @@ namespace ix
void SocketServer::start()
{
_stop = false;
if (!_thread.joinable())
{
_thread = std::thread(&SocketServer::run, this);
@ -183,7 +186,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)
{
@ -210,21 +213,28 @@ namespace ix
{
if (_stop) return;
// Use poll to check whether a new connection is in progress
int timeoutMs = 10;
bool readyToRead = true;
PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, _serverFd);
// Use select to check whether a new connection is in progress
fd_set rfds;
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 10 * 1000; // 10ms timeout
if (pollResult == PollResultType::Error)
FD_ZERO(&rfds);
FD_SET(_serverFd, &rfds);
if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
(errno == EBADF || errno == EINVAL))
{
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;
}
if (pollResult != PollResultType::ReadyForRead)
if (!FD_ISSET(_serverFd, &rfds))
{
// We reached the select timeout, and no new connections are pending
continue;
}
@ -234,15 +244,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;
@ -251,7 +261,8 @@ 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());
@ -268,33 +279,14 @@ 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, socket, connectionState)));
connectionState,
std::thread(&SocketServer::handleConnection,
this,
clientFd,
connectionState)));
}
}
@ -322,9 +314,5 @@ namespace ix
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
}
void SocketServer::setTLSOptions(const SocketTLSOptions& socketTLSOptions)
{
_socketTLSOptions = socketTLSOptions;
}
} // namespace ix

View File

@ -7,7 +7,6 @@
#pragma once
#include "IXConnectionState.h"
#include "IXSocketTLSOptions.h"
#include <atomic>
#include <condition_variable>
#include <functional>
@ -21,8 +20,6 @@
namespace ix
{
class Socket;
class SocketServer
{
public:
@ -54,8 +51,6 @@ namespace ix
std::pair<bool, std::string> listen();
void wait();
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
protected:
// Logging
void logError(const std::string& str);
@ -73,11 +68,10 @@ 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();
@ -98,14 +92,11 @@ namespace ix
// the factory to create ConnectionState objects
ConnectionStateFactory _connectionStateFactory;
virtual void handleConnection(std::shared_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) = 0;
virtual void handleConnection(int fd, 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

View File

@ -1,87 +0,0 @@
/*
* 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

View File

@ -1,52 +0,0 @@
/*
* 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

View File

@ -5,7 +5,6 @@
*/
#include "IXUrlParser.h"
#include "LUrlParser.h"
namespace ix
@ -25,9 +24,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))
{
@ -65,4 +64,4 @@ namespace ix
return true;
}
} // namespace ix
}

View File

@ -1,85 +0,0 @@
/*
* IXUserAgent.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
*/
#include "IXUserAgent.h"
#include "IXWebSocketVersion.h"
#include <sstream>
#include <zlib.h>
// Platform name
#if defined(_WIN32)
#define PLATFORM_NAME "windows" // Windows
#elif defined(_WIN64)
#define PLATFORM_NAME "windows" // Windows
#elif defined(__CYGWIN__) && !defined(_WIN32)
#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)
#elif defined(__linux__)
#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
#elif defined(__hpux)
#define PLATFORM_NAME "hp-ux" // HP-UX
#elif defined(_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
#elif defined(__sun) && defined(__SVR4)
#define PLATFORM_NAME "solaris" // Oracle Solaris, Open Indiana
#else
#define PLATFORM_NAME "unknown platform"
#endif
// SSL
#ifdef IXWEBSOCKET_USE_MBED_TLS
#include <mbedtls/version.h>
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
#include <openssl/opensslv.h>
#endif
namespace ix
{
std::string userAgent()
{
std::stringstream ss;
// IXWebSocket Version
ss << "ixwebsocket/" << IX_WEBSOCKET_VERSION;
// Platform
ss << " " << PLATFORM_NAME;
// TLS
#ifdef IXWEBSOCKET_USE_TLS
#ifdef IXWEBSOCKET_USE_MBED_TLS
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";
#endif
// Zlib version
ss << " zlib " << ZLIB_VERSION;
return ss.str();
}
} // namespace ix

View File

@ -1,14 +0,0 @@
/*
* IXUserAgent.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include <string>
namespace ix
{
std::string userAgent();
} // namespace ix

View File

@ -1,178 +0,0 @@
/*
* 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

View File

@ -5,14 +5,31 @@
*/
#include "IXWebSocket.h"
#include "IXExponentialBackoff.h"
#include "IXSetThreadName.h"
#include "IXUtf8Validator.h"
#include "IXWebSocketHandshake.h"
#include <cassert>
#include <cmath>
#include <cmath>
#include <cassert>
namespace
{
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
{
uint64_t wait_time;
if (retry_count <= 6)
{
// max wait_time is 6400 ms (2 ^ 6 = 64)
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
}
else
{
wait_time = 10 * 1000; // 10 sec
}
return wait_time;
}
}
namespace ix
{
@ -21,28 +38,26 @@ namespace ix
const int WebSocket::kDefaultPingIntervalSecs(-1);
const int WebSocket::kDefaultPingTimeoutSecs(-1);
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),
_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()
@ -55,11 +70,6 @@ namespace ix
std::lock_guard<std::mutex> lock(_configMutex);
_url = url;
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{
std::lock_guard<std::mutex> lock(_configMutex);
_extraHeaders = headers;
}
const std::string& WebSocket::getUrl() const
{
@ -67,19 +77,12 @@ 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);
@ -141,18 +144,6 @@ namespace ix
_perMessageDeflateOptions = perMessageDeflateOptions;
}
void WebSocket::setMaxWaitBetweenReconnectionRetries(uint32_t maxWaitBetweenReconnectionRetries)
{
std::lock_guard<std::mutex> lock(_configMutex);
_maxWaitBetweenReconnectionRetries = maxWaitBetweenReconnectionRetries;
}
uint32_t WebSocket::getMaxWaitBetweenReconnectionRetries() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _maxWaitBetweenReconnectionRetries;
}
void WebSocket::start()
{
if (_thread.joinable()) return; // we've already been started
@ -160,7 +151,8 @@ 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);
@ -179,65 +171,48 @@ namespace ix
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_socketTLSOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
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, status.protocol),
WebSocketCloseInfo()));
return status;
}
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(socket, timeoutSecs);
WebSocketInitResult status = _ws.connectToUrl(_url, 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;
}
WebSocketInitResult WebSocket::connectToSocket(int fd, int timeoutSecs)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
_ws.configure(_perMessageDeflateOptions,
_enablePong,
_pingIntervalSecs,
_pingTimeoutSecs);
}
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
if (!status.success)
{
return status;
}
_onMessageCallback(
std::make_shared<WebSocketMessage>(
WebSocketMessageType::Open, "", 0,
WebSocketErrorInfo(),
WebSocketOpenInfo(status.uri, status.headers),
WebSocketCloseInfo()));
return status;
}
@ -251,7 +226,8 @@ 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);
}
@ -295,22 +271,20 @@ namespace ix
if (_automaticReconnection)
{
duration = millis(calculateRetryWaitMilliseconds(
retries++, _maxWaitBetweenReconnectionRetries));
duration = millis(calculateRetryWaitMilliseconds(retries++));
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()));
}
}
}
@ -346,7 +320,8 @@ namespace ix
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
WebSocketTransport::MessageKind messageKind) {
WebSocketTransport::MessageKind messageKind)
{
WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
@ -354,26 +329,22 @@ 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;
@ -381,13 +352,11 @@ 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);
});
@ -421,7 +390,9 @@ namespace ix
bool binary,
const OnProgressCallback& onProgressCallback)
{
return (binary) ? sendBinary(data, onProgressCallback) : sendText(data, onProgressCallback);
return sendMessage(data,
(binary) ? SendMessageKind::Binary: SendMessageKind::Text,
onProgressCallback);
}
WebSocketSendInfo WebSocket::sendBinary(const std::string& text,
@ -433,12 +404,6 @@ namespace ix
WebSocketSendInfo WebSocket::sendText(const std::string& text,
const OnProgressCallback& onProgressCallback)
{
if (!validateUtf8(text))
{
close(WebSocketCloseConstants::kInvalidFramePayloadData,
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
return false;
}
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
@ -474,20 +439,17 @@ 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);
@ -499,10 +461,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;
}
}
@ -511,10 +473,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";
}
}
@ -538,16 +500,4 @@ 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
}

View File

@ -10,7 +10,6 @@
#pragma once
#include "IXProgressCallback.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketCloseConstants.h"
#include "IXWebSocketErrorInfo.h"
#include "IXWebSocketHttpHeaders.h"
@ -45,19 +44,14 @@ namespace ix
~WebSocket();
void setUrl(const std::string& url);
// send extra headers in client handshake request
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();
@ -100,9 +94,6 @@ namespace ix
void enableAutomaticReconnection();
void disableAutomaticReconnection();
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,
@ -115,31 +106,22 @@ namespace ix
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
// Server
WebSocketInitResult connectToSocket(std::shared_ptr<Socket>, int timeoutSecs);
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
WebSocketTransport _ws;
std::string _url;
WebSocketHttpHeaders _extraHeaders;
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
SocketTLSOptions _socketTLSOptions;
mutable std::mutex _configMutex; // protect all config variables access
OnMessageCallback _onMessageCallback;
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
std::atomic<bool> _stop;
std::atomic<bool> _automaticReconnection;
std::thread _thread;
std::mutex _writeMutex;
// Automatic reconnection
std::atomic<bool> _automaticReconnection;
static const uint32_t kDefaultMaxWaitBetweenReconnectionRetries;
uint32_t _maxWaitBetweenReconnectionRetries;
std::atomic<int> _handshakeTimeoutSecs;
static const int kDefaultHandShakeTimeoutSecs;
@ -153,9 +135,6 @@ namespace ix
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;
// Subprotocols
std::vector<std::string> _subProtocols;
friend class WebSocketServer;
};
} // namespace ix

View File

@ -11,7 +11,6 @@ namespace ix
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
const uint16_t WebSocketCloseConstants::kInvalidFramePayloadData(1007);
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
@ -21,16 +20,4 @@ namespace ix
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
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::kInvalidCloseCodeMessage("Invalid close code");
} // namespace ix
}

View File

@ -18,7 +18,6 @@ namespace ix
static const uint16_t kAbnormalCloseCode;
static const uint16_t kProtocolErrorCode;
static const uint16_t kNoStatusCodeErrorCode;
static const uint16_t kInvalidFramePayloadData;
static const std::string kNormalClosureMessage;
static const std::string kInternalErrorMessage;
@ -26,12 +25,5 @@ namespace ix
static const std::string kPingTimeoutMessage;
static const std::string kProtocolErrorMessage;
static const std::string kNoStatusCodeErrorMessage;
static const std::string kProtocolErrorReservedBitUsed;
static const std::string kProtocolErrorPingPayloadOversized;
static const std::string kProtocolErrorCodeControlMessageFragmented;
static const std::string kProtocolErrorCodeDataOpcodeOutOfSequence;
static const std::string kProtocolErrorCodeContinuationOpCodeOutOfSequence;
static const std::string kInvalidFramePayloadDataMessage;
static const std::string kInvalidCloseCodeMessage;
};
} // namespace ix

View File

@ -5,45 +5,49 @@
*/
#include "IXWebSocketHandshake.h"
#include "IXHttp.h"
#include "IXSocketConnect.h"
#include "IXUrlParser.h"
#include "IXUserAgent.h"
#include "IXHttp.h"
#include "libwshandshake.hpp"
#include <algorithm>
#include <random>
#include <sstream>
#include <random>
#include <algorithm>
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());
@ -69,7 +73,6 @@ 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);
@ -84,13 +87,11 @@ 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 std::string& host,
const std::string& path,
int port,
int timeoutSecs)
{
_requestInitCancellation = false;
@ -102,7 +103,9 @@ 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());
}
@ -118,23 +121,12 @@ 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";
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
// User-Agent can be customized by users
if (extraHeaders.find("User-Agent") == extraHeaders.end())
{
ss << "User-Agent: " << userAgent() << "\r\n";
}
for (auto& it : extraHeaders)
{
ss << it.first << ":" << it.second << "\r\n";
}
if (_enablePerMessageDeflate)
{
ss << _perMessageDeflateOptions.generateHeader();
@ -144,8 +136,7 @@ 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
@ -155,31 +146,30 @@ 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
auto statusLine = Http::parseStatusLine(line);
std::string httpVersion = statusLine.first;
int status = statusLine.second;
int status;
// HTTP/1.0 is too old.
if (httpVersion != "HTTP/1.1")
if (sscanf(line.c_str(), "HTTP/1.0 %d", &status) == 1)
{
std::stringstream ss;
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
<< "Rejecting connection to " << host << ":" << port << ", status: " << status
ss << "Server version is HTTP/1.0. Rejecting connection to " << host
<< ", status: " << status
<< ", HTTP Status line: " << line;
return WebSocketInitResult(false, status, ss.str());
}
// We want an 101 HTTP status
if (status != 101)
if (sscanf(line.c_str(), "HTTP/1.1 %d", &status) != 1 || status != 101)
{
std::stringstream ss;
ss << "Expecting status 101 (Switching Protocol), got " << status
<< " status connecting to " << host << ":" << port << ", HTTP Status line: " << line;
ss << "Got bad status connecting to " << host
<< ", status: " << status
<< ", HTTP Status line: " << line;
return WebSocketInitResult(false, status, ss.str());
}
@ -232,20 +222,25 @@ namespace ix
else if (!_perMessageDeflate.init(webSocketPerMessageDeflateOptions))
{
return WebSocketInitResult(
false, 0, "Failed to initialize per message deflate engine");
false, 0,"Failed to initialize per message deflate engine");
}
}
return WebSocketInitResult(true, status, "", headers, path);
}
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs)
WebSocketInitResult WebSocketHandshake::serverHandshake(int fd, 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;
@ -258,8 +253,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")
@ -269,8 +264,7 @@ 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
@ -288,17 +282,9 @@ namespace ix
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
}
if (headers.find("upgrade") == headers.end())
if (headers["upgrade"] != "websocket")
{
return sendErrorResponse(400, "Missing Upgrade header");
}
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket"))
{
return sendErrorResponse(400,
"Invalid Upgrade header, "
"need WebSocket, got " +
headers["upgrade"]);
return sendErrorResponse(400, "Invalid or missing Upgrade header");
}
if (headers.find("sec-websocket-version") == headers.end())
@ -314,10 +300,8 @@ 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());
}
}
@ -329,7 +313,6 @@ 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"];
@ -343,7 +326,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();
}
@ -352,10 +335,9 @@ namespace ix
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
{
return WebSocketInitResult(
false, 0, std::string("Failed sending response to remote end"));
return WebSocketInitResult(false, 0, std::string("Failed sending response to ") + remote);
}
return WebSocketInitResult(true, 200, "", headers, uri);
}
} // namespace ix
}

View File

@ -9,16 +9,38 @@
#include "IXCancellationRequest.h"
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketInitResult.h"
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <atomic>
#include <chrono>
#include <memory>
#include <string>
#include <tuple>
namespace ix
{
struct WebSocketInitResult
{
bool success;
int http_status;
std::string errorStr;
WebSocketHttpHeaders headers;
std::string uri;
WebSocketInitResult(bool s = false,
int status = 0,
const std::string& e = std::string(),
WebSocketHttpHeaders h = WebSocketHttpHeaders(),
const std::string& u = std::string())
{
success = s;
http_status = status;
errorStr = e;
headers = h;
uri = u;
}
};
class WebSocketHandshake
{
public:
@ -29,13 +51,12 @@ namespace ix
std::atomic<bool>& enablePerMessageDeflate);
WebSocketInitResult clientHandshake(const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs);
WebSocketInitResult serverHandshake(int fd, int timeoutSecs);
private:
std::string genRandomString(const int len);

View File

@ -5,15 +5,13 @@
*/
#include "IXWebSocketHttpHeaders.h"
#include "IXSocket.h"
#include <algorithm>
#include <locale>
namespace ix
{
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char& c1,
const unsigned char& c2) const
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char & c1, const unsigned char & c2) const
{
#ifdef _WIN32
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
@ -22,17 +20,17 @@ namespace ix
#endif
}
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const
bool CaseInsensitiveLess::operator()(const std::string & s1, const std::string & s2) const
{
return std::lexicographical_compare(s1.begin(),
s1.end(), // source range
s2.begin(),
s2.end(), // dest range
NocaseCompare()); // comparison
return std::lexicographical_compare
(s1.begin(), s1.end(), // source range
s2.begin(), s2.end(), // dest range
NocaseCompare()); // comparison
}
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
std::shared_ptr<Socket> socket, const CancellationRequest& isCancellationRequested)
std::shared_ptr<Socket> socket,
const CancellationRequest& isCancellationRequested)
{
WebSocketHttpHeaders headers;
@ -43,9 +41,11 @@ namespace ix
{
int colon = 0;
for (i = 0; i < 2 || (i < 1023 && line[i - 2] != '\r' && line[i - 1] != '\n'); ++i)
for (i = 0;
i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n');
++i)
{
if (!socket->readByte(line + i, isCancellationRequested))
if (!socket->readByte(line+i, isCancellationRequested))
{
return std::make_pair(false, headers);
}
@ -79,4 +79,4 @@ namespace ix
return std::make_pair(true, headers);
}
} // namespace ix
}

View File

@ -1,36 +0,0 @@
/*
* IXWebSocketInitResult.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#include "IXWebSocketHttpHeaders.h"
namespace ix
{
struct WebSocketInitResult
{
bool success;
int http_status;
std::string errorStr;
WebSocketHttpHeaders headers;
std::string uri;
std::string protocol;
WebSocketInitResult(bool s = false,
int status = 0,
const std::string& e = std::string(),
WebSocketHttpHeaders h = WebSocketHttpHeaders(),
const std::string& u = std::string())
{
success = s;
http_status = status;
errorStr = e;
headers = h;
uri = u;
protocol = h["Sec-WebSocket-Protocol"];
}
};
} // namespace ix

View File

@ -8,6 +8,7 @@
namespace ix
{
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
{
bindWebsocket(websocket);
@ -23,7 +24,7 @@ namespace ix
bindWebsocket(nullptr);
}
void WebSocketMessageQueue::bindWebsocket(WebSocket* websocket)
void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket)
{
if (_websocket == websocket) return;
@ -39,7 +40,8 @@ namespace ix
// bind new
if (_websocket)
{
_websocket->setOnMessageCallback([this](const WebSocketMessagePtr& msg) {
_websocket->setOnMessageCallback([this](const WebSocketMessagePtr& msg)
{
std::lock_guard<std::mutex> lock(_messagesMutex);
_messages.emplace_back(std::move(msg));
});
@ -72,7 +74,8 @@ namespace ix
void WebSocketMessageQueue::poll(int count)
{
if (!_onMessageUserCallback) return;
if (!_onMessageUserCallback)
return;
WebSocketMessagePtr message;
@ -83,4 +86,4 @@ namespace ix
}
}
} // namespace ix
}

View File

@ -12,14 +12,11 @@ namespace ix
{
std::string uri;
WebSocketHttpHeaders headers;
std::string protocol;
WebSocketOpenInfo(const std::string& u = std::string(),
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& p = std::string())
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
: uri(u)
, headers(h)
, protocol(p)
{
;
}

View File

@ -41,21 +41,19 @@
* - Added more documentation.
*
* Per message Deflate RFC: https://tools.ietf.org/html/rfc7692
* Chrome websocket ->
* https://github.com/chromium/chromium/tree/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/net/websockets
* Chrome websocket -> https://github.com/chromium/chromium/tree/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/net/websockets
*
*/
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketPerMessageDeflateCodec.h"
namespace ix
{
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
: _compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>())
, _decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate() :
_compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>()),
_decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
{
;
}
@ -65,10 +63,10 @@ namespace ix
;
}
bool WebSocketPerMessageDeflate::init(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
{
bool clientNoContextTakeover = perMessageDeflateOptions.getClientNoContextTakeover();
bool clientNoContextTakeover =
perMessageDeflateOptions.getClientNoContextTakeover();
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
@ -77,14 +75,16 @@ namespace ix
_decompressor->init(inflateBits, clientNoContextTakeover);
}
bool WebSocketPerMessageDeflate::compress(const std::string& in, std::string& out)
bool WebSocketPerMessageDeflate::compress(const std::string& in,
std::string& out)
{
return _compressor->compress(in, out);
}
bool WebSocketPerMessageDeflate::decompress(const std::string& in, std::string& out)
bool WebSocketPerMessageDeflate::decompress(const std::string& in,
std::string &out)
{
return _decompressor->decompress(in, out);
}
} // namespace ix
}

View File

@ -5,8 +5,8 @@
*/
#include "IXWebSocketPerMessageDeflateCodec.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <cassert>
#include <string.h>
@ -18,7 +18,7 @@ namespace
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
const int kBufferSize = 1 << 14;
} // namespace
}
namespace ix
{
@ -26,7 +26,7 @@ namespace ix
// Compressor
//
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
: _compressBufferSize(kBufferSize)
: _compressBufferSize(kBufferSize)
{
memset(&_deflateState, 0, sizeof(_deflateState));
@ -43,18 +43,22 @@ namespace ix
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
bool clientNoContextTakeOver)
{
int ret = deflateInit2(&_deflateState,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1 * deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY);
int ret = deflateInit2(
&_deflateState,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
-1*deflateBits,
4, // memory level 1-9
Z_DEFAULT_STRATEGY
);
if (ret != Z_OK) return false;
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
@ -66,7 +70,8 @@ namespace ix
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
std::string& out)
{
//
// 7.2.1. Compression
@ -90,7 +95,7 @@ namespace ix
if (in.empty())
{
uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
out.append((char*) (buf), 6);
out.append((char *)(buf), 6);
return true;
}
@ -107,7 +112,7 @@ namespace ix
output = _compressBufferSize - _deflateState.avail_out;
out.append((char*) (_compressBuffer.get()), output);
out.append((char *)(_compressBuffer.get()),output);
} while (_deflateState.avail_out == 0);
if (endsWith(out, kEmptyUncompressedBlock))
@ -122,7 +127,7 @@ namespace ix
// Decompressor
//
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
: _compressBufferSize(kBufferSize)
: _compressBufferSize(kBufferSize)
{
memset(&_inflateState, 0, sizeof(_inflateState));
@ -141,18 +146,24 @@ namespace ix
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
bool clientNoContextTakeOver)
{
int ret = inflateInit2(&_inflateState, -1 * inflateBits);
int ret = inflateInit2(
&_inflateState,
-1*inflateBits
);
if (ret != Z_OK) return false;
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
_flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;
_flush = (clientNoContextTakeOver)
? Z_FULL_FLUSH
: Z_SYNC_FLUSH;
return true;
}
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
std::string& out)
{
//
// 7.2.2. Decompression
@ -168,7 +179,7 @@ namespace ix
inFixed += kEmptyUncompressedBlock;
_inflateState.avail_in = (uInt) inFixed.size();
_inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));
_inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
do
{
@ -182,10 +193,13 @@ namespace ix
return false; // zlib error
}
out.append(reinterpret_cast<char*>(_compressBuffer.get()),
_compressBufferSize - _inflateState.avail_out);
out.append(
reinterpret_cast<char *>(_compressBuffer.get()),
_compressBufferSize - _inflateState.avail_out
);
} while (_inflateState.avail_out == 0);
return true;
}
} // namespace ix
}

View File

@ -6,9 +6,9 @@
#include "IXWebSocketPerMessageDeflateOptions.h"
#include <sstream>
#include <algorithm>
#include <cctype>
#include <sstream>
namespace ix
{
@ -33,8 +33,6 @@ namespace ix
_serverNoContextTakeover = serverNoContextTakeover;
_clientMaxWindowBits = clientMaxWindowBits;
_serverMaxWindowBits = serverMaxWindowBits;
sanitizeClientMaxWindowBits();
}
//
@ -48,8 +46,7 @@ namespace ix
//
// Server response could look like that:
//
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover;
// server_no_context_takeover
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover
//
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension)
{
@ -93,7 +90,8 @@ namespace ix
// Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values
_serverMaxWindowBits =
std::min(maxServerMaxWindowBits, std::max(x, minServerMaxWindowBits));
std::min(maxServerMaxWindowBits,
std::max(x, minServerMaxWindowBits));
}
if (startsWith(token, "client_max_window_bits="))
@ -107,23 +105,12 @@ namespace ix
// Sanitize values to be in the proper range [8, 15] in
// case a server would give us bogus values
_clientMaxWindowBits =
std::min(maxClientMaxWindowBits, std::max(x, minClientMaxWindowBits));
sanitizeClientMaxWindowBits();
std::min(maxClientMaxWindowBits,
std::max(x, minClientMaxWindowBits));
}
}
}
void WebSocketPerMessageDeflateOptions::sanitizeClientMaxWindowBits()
{
// zlib/deflate has a bug with windowsbits == 8, so we silently upgrade it to 9
// See https://bugs.chromium.org/p/chromium/issues/detail?id=691074
if (_clientMaxWindowBits == 8)
{
_clientMaxWindowBits = 9;
}
}
std::string WebSocketPerMessageDeflateOptions::generateHeader()
{
std::stringstream ss;
@ -174,10 +161,11 @@ namespace ix
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
{
std::string out(str);
out.erase(
std::remove_if(out.begin(), out.end(), [](unsigned char x) { return std::isspace(x); }),
out.end());
out.erase(std::remove_if(out.begin(),
out.end(),
[](unsigned char x){ return std::isspace(x); }),
out.end());
return out;
}
} // namespace ix
}

View File

@ -41,7 +41,5 @@ namespace ix
bool _serverNoContextTakeover;
int _clientMaxWindowBits;
int _serverMaxWindowBits;
void sanitizeClientMaxWindowBits();
};
} // namespace ix

View File

@ -5,13 +5,13 @@
*/
#include "IXWebSocketServer.h"
#include "IXNetSystem.h"
#include "IXSocketConnect.h"
#include "IXWebSocket.h"
#include "IXWebSocketTransport.h"
#include <future>
#include "IXWebSocket.h"
#include "IXSocketConnect.h"
#include "IXNetSystem.h"
#include <sstream>
#include <future>
#include <string.h>
namespace ix
@ -23,11 +23,11 @@ namespace ix
const std::string& host,
int backlog,
size_t maxConnections,
int handshakeTimeoutSecs)
: SocketServer(port, host, backlog, maxConnections)
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
_handshakeTimeoutSecs(handshakeTimeoutSecs),
_enablePong(kDefaultEnablePong)
{
}
WebSocketServer::~WebSocketServer()
@ -63,8 +63,9 @@ namespace ix
_onConnectionCallback = callback;
}
void WebSocketServer::handleConnection(std::shared_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
void WebSocketServer::handleConnection(
int fd,
std::shared_ptr<ConnectionState> connectionState)
{
auto webSocket = std::make_shared<WebSocket>();
_onConnectionCallback(webSocket, connectionState);
@ -82,7 +83,7 @@ namespace ix
_clients.insert(webSocket);
}
auto status = webSocket->connectToSocket(socket, _handshakeTimeoutSecs);
auto status = webSocket->connectToSocket(fd, _handshakeTimeoutSecs);
if (status.success)
{
// Process incoming messages and execute callbacks
@ -92,8 +93,10 @@ namespace ix
else
{
std::stringstream ss;
ss << "WebSocketServer::handleConnection() HTTP status: " << status.http_status
<< " error: " << status.errorStr;
ss << "WebSocketServer::handleConnection() error: "
<< status.http_status
<< " error: "
<< status.errorStr;
logError(ss.str());
}
@ -121,4 +124,4 @@ namespace ix
std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients.size();
}
} // namespace ix
}

View File

@ -55,7 +55,7 @@ namespace ix
const static bool kDefaultEnablePong;
// Methods
virtual void handleConnection(std::shared_ptr<Socket> socket,
virtual void handleConnection(int fd,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
};

View File

@ -33,22 +33,21 @@
//
#include "IXWebSocketTransport.h"
#include "IXSocketFactory.h"
#include "IXSocketTLSOptions.h"
#include "IXUrlParser.h"
#include "IXUtf8Validator.h"
#include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h"
#include <chrono>
#include <cstdarg>
#include <cstdlib>
#include <sstream>
#include <stdlib.h>
#include "IXUrlParser.h"
#include "IXSocketFactory.h"
#include <string.h>
#include <string>
#include <thread>
#include <stdlib.h>
#include <cstdlib>
#include <vector>
#include <string>
#include <cstdarg>
#include <sstream>
#include <chrono>
#include <thread>
namespace
@ -64,7 +63,7 @@ namespace
return a;
}
} // namespace
}
namespace ix
{
@ -75,25 +74,23 @@ namespace ix
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
constexpr size_t WebSocketTransport::kChunkSize;
WebSocketTransport::WebSocketTransport()
: _useMask(true)
, _blockingSend(false)
, _compressedMessage(false)
, _readyState(ReadyState::CLOSED)
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
, _closeReason(WebSocketCloseConstants::kInternalErrorMessage)
, _closeWireSize(0)
, _closeRemote(false)
, _enablePerMessageDeflate(false)
, _requestInitCancellation(false)
, _closingTimePoint(std::chrono::steady_clock::now())
, _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pingTimeoutSecs(kDefaultPingTimeoutSecs)
, _pingIntervalOrTimeoutGCDSecs(-1)
, _nextGCDTimePoint(std::chrono::steady_clock::now())
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
, _lastReceivePongTimePoint(std::chrono::steady_clock::now())
WebSocketTransport::WebSocketTransport() :
_useMask(true),
_readyState(ReadyState::CLOSED),
_closeCode(WebSocketCloseConstants::kInternalErrorCode),
_closeReason(WebSocketCloseConstants::kInternalErrorMessage),
_closeWireSize(0),
_closeRemote(false),
_enablePerMessageDeflate(false),
_requestInitCancellation(false),
_closingTimePoint(std::chrono::steady_clock::now()),
_enablePong(kDefaultEnablePong),
_pingIntervalSecs(kDefaultPingIntervalSecs),
_pingTimeoutSecs(kDefaultPingTimeoutSecs),
_pingIntervalOrTimeoutGCDSecs(-1),
_nextGCDTimePoint(std::chrono::steady_clock::now()),
_lastSendPingTimePoint(std::chrono::steady_clock::now()),
_lastReceivePongTimePoint(std::chrono::steady_clock::now())
{
_readbuf.resize(kChunkSize);
}
@ -103,24 +100,21 @@ namespace ix
;
}
void WebSocketTransport::configure(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions,
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs)
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs)
{
_perMessageDeflateOptions = perMessageDeflateOptions;
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
_socketTLSOptions = socketTLSOptions;
_enablePong = enablePong;
_pingIntervalSecs = pingIntervalSecs;
_pingTimeoutSecs = pingTimeoutSecs;
if (pingIntervalSecs > 0 && pingTimeoutSecs > 0)
{
_pingIntervalOrTimeoutGCDSecs =
greatestCommonDivisor(pingIntervalSecs, pingTimeoutSecs);
_pingIntervalOrTimeoutGCDSecs = greatestCommonDivisor(pingIntervalSecs,
pingTimeoutSecs);
}
else if (_pingTimeoutSecs > 0)
{
@ -134,7 +128,6 @@ namespace ix
// Client
WebSocketInitResult WebSocketTransport::connectToUrl(const std::string& url,
const WebSocketHttpHeaders& headers,
int timeoutSecs)
{
std::lock_guard<std::mutex> lock(_socketMutex);
@ -144,14 +137,13 @@ namespace ix
if (!UrlParser::parse(url, protocol, host, path, query, port))
{
std::stringstream ss;
ss << "Could not parse url: '" << url << "'";
return WebSocketInitResult(false, 0, ss.str());
return WebSocketInitResult(false, 0,
std::string("Could not parse URL ") + url);
}
std::string errorMsg;
bool tls = protocol == "wss";
_socket = createSocket(tls, -1, errorMsg, _socketTLSOptions);
_socket = createSocket(tls, errorMsg);
if (!_socket)
{
@ -164,8 +156,8 @@ namespace ix
_perMessageDeflateOptions,
_enablePerMessageDeflate);
auto result =
webSocketHandshake.clientHandshake(url, headers, host, path, port, timeoutSecs);
auto result = webSocketHandshake.clientHandshake(url, host, path, port,
timeoutSecs);
if (result.success)
{
setReadyState(ReadyState::OPEN);
@ -174,16 +166,20 @@ namespace ix
}
// Server
WebSocketInitResult WebSocketTransport::connectToSocket(std::shared_ptr<Socket> socket,
int timeoutSecs)
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
{
std::lock_guard<std::mutex> lock(_socketMutex);
// Server should not mask the data it sends to the client
_useMask = false;
_blockingSend = true;
_socket = socket;
std::string errorMsg;
_socket = createSocket(fd, errorMsg);
if (!_socket)
{
return WebSocketInitResult(false, 0, errorMsg);
}
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
_socket,
@ -191,7 +187,7 @@ namespace ix
_perMessageDeflateOptions,
_enablePerMessageDeflate);
auto result = webSocketHandshake.serverHandshake(timeoutSecs);
auto result = webSocketHandshake.serverHandshake(fd, timeoutSecs);
if (result.success)
{
setReadyState(ReadyState::OPEN);
@ -244,15 +240,15 @@ namespace ix
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
_nextGCDTimePoint = std::chrono::steady_clock::now() +
std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
_nextGCDTimePoint = std::chrono::steady_clock::now() + std::chrono::seconds(_pingIntervalOrTimeoutGCDSecs);
}
}
// Only consider send PING time points for that computation.
bool WebSocketTransport::pingIntervalExceeded()
{
if (_pingIntervalSecs <= 0) return false;
if (_pingIntervalSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastSendPingTimePointMutex);
auto now = std::chrono::steady_clock::now();
@ -261,7 +257,8 @@ namespace ix
bool WebSocketTransport::pingTimeoutExceeded()
{
if (_pingTimeoutSecs <= 0) return false;
if (_pingTimeoutSecs <= 0)
return false;
std::lock_guard<std::mutex> lock(_lastReceivePongTimePointMutex);
auto now = std::chrono::steady_clock::now();
@ -298,8 +295,7 @@ namespace ix
// No timeout if state is not OPEN, otherwise computed
// pingIntervalOrTimeoutGCD (equals to -1 if no ping and no ping timeout are set)
int lastingTimeoutDelayInMs =
(_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
int lastingTimeoutDelayInMs = (_readyState != ReadyState::OPEN) ? 0 : _pingIntervalOrTimeoutGCDSecs;
if (_pingIntervalOrTimeoutGCDSecs > 0)
{
@ -314,10 +310,7 @@ namespace ix
}
else
{
lastingTimeoutDelayInMs =
(int) std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint -
now)
.count();
lastingTimeoutDelayInMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(_nextGCDTimePoint - now).count();
}
}
@ -337,22 +330,35 @@ namespace ix
}
// poll the socket
PollResultType pollResult = _socket->isReadyToRead(lastingTimeoutDelayInMs);
PollResultType pollResult = _socket->poll(lastingTimeoutDelayInMs);
// Make sure we send all the buffered data
// there can be a lot of it for large messages.
if (pollResult == PollResultType::SendRequest)
{
if (!flushSendBuffer())
while (!isSendBufferEmpty() && !_requestInitCancellation)
{
return PollResult::CannotFlushSendBuffer;
// Wait with a 10ms timeout until the socket is ready to write.
// This way we are not busy looping
PollResultType result = _socket->isReadyToWrite(10);
if (result == PollResultType::Error)
{
closeSocket();
setReadyState(ReadyState::CLOSED);
break;
}
else if (result == PollResultType::ReadyForWrite)
{
sendOnSocket();
}
}
}
else if (pollResult == PollResultType::ReadyForRead)
{
while (true)
{
ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size());
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
if (ret < 0 && Socket::isWaitNeeded())
{
@ -360,9 +366,8 @@ namespace ix
}
else if (ret <= 0)
{
// if there are received data pending to be processed, then delay the abnormal
// closure to after dispatch (other close code/reason could be read from the
// buffer)
// if there are received data pending to be processed, then delay the abnormal closure
// to after dispatch (other close code/reason could be read from the buffer)
closeSocket();
@ -370,7 +375,9 @@ namespace ix
}
else
{
_rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret);
_rxbuf.insert(_rxbuf.end(),
_readbuf.begin(),
_readbuf.begin() + ret);
}
}
}
@ -415,7 +422,7 @@ namespace ix
{
for (size_t i = 0; i != (size_t) message_size; ++i)
{
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i & 0x3];
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
}
}
}
@ -426,7 +433,7 @@ namespace ix
{
for (size_t j = 0; j != ws.N; ++j)
{
_rxbuf[j + ws.header_size] ^= ws.masking_key[j & 0x3];
_rxbuf[j+ws.header_size] ^= ws.masking_key[j&0x3];
}
}
}
@ -459,27 +466,16 @@ namespace ix
while (true)
{
wsheader_type ws;
if (_rxbuf.size() < 2) break; /* Need at least 2 */
const uint8_t* data = (uint8_t*) &_rxbuf[0]; // peek, but don't consume
if (_rxbuf.size() < 2) break; /* Need at least 2 */
const uint8_t * data = (uint8_t *) &_rxbuf[0]; // peek, but don't consume
ws.fin = (data[0] & 0x80) == 0x80;
ws.rsv1 = (data[0] & 0x40) == 0x40;
ws.rsv2 = (data[0] & 0x20) == 0x20;
ws.rsv3 = (data[0] & 0x10) == 0x10;
ws.opcode = (wsheader_type::opcode_type)(data[0] & 0x0f);
ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f);
ws.mask = (data[1] & 0x80) == 0x80;
ws.N0 = (data[1] & 0x7f);
ws.header_size =
2 + (ws.N0 == 126 ? 2 : 0) + (ws.N0 == 127 ? 8 : 0) + (ws.mask ? 4 : 0);
ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0);
if (_rxbuf.size() < ws.header_size) break; /* Need: ws.header_size - _rxbuf.size() */
if ((ws.rsv1 && !_enablePerMessageDeflate) || ws.rsv2 || ws.rsv3)
{
close(WebSocketCloseConstants::kProtocolErrorCode,
WebSocketCloseConstants::kProtocolErrorReservedBitUsed,
_rxbuf.size());
return;
}
//
// Calculate payload length:
// 0-125 mean the payload is that long.
@ -520,10 +516,10 @@ namespace ix
if (ws.mask)
{
ws.masking_key[0] = ((uint8_t) data[i + 0]) << 0;
ws.masking_key[1] = ((uint8_t) data[i + 1]) << 0;
ws.masking_key[2] = ((uint8_t) data[i + 2]) << 0;
ws.masking_key[3] = ((uint8_t) data[i + 3]) << 0;
ws.masking_key[0] = ((uint8_t) data[i+0]) << 0;
ws.masking_key[1] = ((uint8_t) data[i+1]) << 0;
ws.masking_key[2] = ((uint8_t) data[i+2]) << 0;
ws.masking_key[3] = ((uint8_t) data[i+3]) << 0;
}
else
{
@ -533,68 +529,34 @@ namespace ix
ws.masking_key[3] = 0;
}
// Prevent integer overflow in the next conditional
const uint64_t maxFrameSize(1ULL << 63);
if (ws.N > maxFrameSize)
{
return;
}
if (_rxbuf.size() < ws.header_size + ws.N)
if (_rxbuf.size() < ws.header_size+ws.N)
{
return; /* Need: ws.header_size+ws.N - _rxbuf.size() */
}
if (!ws.fin && (ws.opcode == wsheader_type::PING || ws.opcode == wsheader_type::PONG ||
ws.opcode == wsheader_type::CLOSE))
{
// Control messages should not be fragmented
close(WebSocketCloseConstants::kProtocolErrorCode,
WebSocketCloseConstants::kProtocolErrorCodeControlMessageFragmented);
return;
}
unmaskReceiveBuffer(ws);
std::string frameData(_rxbuf.begin() + ws.header_size,
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
// We got a whole message, now do something with it:
if (ws.opcode == wsheader_type::TEXT_FRAME ||
ws.opcode == wsheader_type::BINARY_FRAME ||
ws.opcode == wsheader_type::CONTINUATION)
{
if (ws.opcode != wsheader_type::CONTINUATION)
{
_fragmentedMessageKind = (ws.opcode == wsheader_type::TEXT_FRAME)
? MessageKind::MSG_TEXT
: MessageKind::MSG_BINARY;
if (
ws.opcode == wsheader_type::TEXT_FRAME
|| ws.opcode == wsheader_type::BINARY_FRAME
|| ws.opcode == wsheader_type::CONTINUATION
) {
unmaskReceiveBuffer(ws);
_compressedMessage = _enablePerMessageDeflate && ws.rsv1;
// Continuation message needs to follow a non-fin TEXT or BINARY message
if (!_chunks.empty())
{
close(WebSocketCloseConstants::kProtocolErrorCode,
WebSocketCloseConstants::kProtocolErrorCodeDataOpcodeOutOfSequence);
}
}
else if (_chunks.empty())
{
// Continuation message need to follow a non-fin TEXT or BINARY message
close(
WebSocketCloseConstants::kProtocolErrorCode,
WebSocketCloseConstants::kProtocolErrorCodeContinuationOpCodeOutOfSequence);
}
MessageKind messageKind =
(ws.opcode == wsheader_type::TEXT_FRAME)
? MessageKind::MSG_TEXT
: MessageKind::MSG_BINARY;
//
// Usual case. Small unfragmented messages
//
if (ws.fin && _chunks.empty())
{
emitMessage(
_fragmentedMessageKind, frameData, _compressedMessage, onMessageCallback);
_compressedMessage = false;
emitMessage(messageKind,
std::string(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
ws,
onMessageCallback);
}
else
{
@ -605,89 +567,65 @@ namespace ix
// the internal buffer which is slow and can let the internal OS
// receive buffer fill out.
//
_chunks.emplace_back(frameData);
_chunks.emplace_back(
std::vector<uint8_t>(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
if (ws.fin)
{
emitMessage(_fragmentedMessageKind,
getMergedChunks(),
_compressedMessage,
onMessageCallback);
emitMessage(messageKind, getMergedChunks(), ws, onMessageCallback);
_chunks.clear();
_compressedMessage = false;
}
else
{
emitMessage(MessageKind::FRAGMENT, std::string(), false, onMessageCallback);
emitMessage(MessageKind::FRAGMENT, std::string(), ws, onMessageCallback);
}
}
}
else if (ws.opcode == wsheader_type::PING)
{
// too large
if (frameData.size() > 125)
{
// Unexpected frame type
close(WebSocketCloseConstants::kProtocolErrorCode,
WebSocketCloseConstants::kProtocolErrorPingPayloadOversized);
return;
}
unmaskReceiveBuffer(ws);
std::string pingData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
if (_enablePong)
{
// Reply back right away
bool compress = false;
sendData(wsheader_type::PONG, frameData, compress);
sendData(wsheader_type::PONG, pingData, compress);
}
emitMessage(MessageKind::PING, frameData, false, onMessageCallback);
emitMessage(MessageKind::PING, pingData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::PONG)
{
unmaskReceiveBuffer(ws);
std::string pongData(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
std::lock_guard<std::mutex> lck(_lastReceivePongTimePointMutex);
_lastReceivePongTimePoint = std::chrono::steady_clock::now();
emitMessage(MessageKind::PONG, frameData, false, onMessageCallback);
emitMessage(MessageKind::PONG, pongData, ws, onMessageCallback);
}
else if (ws.opcode == wsheader_type::CLOSE)
{
std::string reason;
uint16_t code = 0;
unmaskReceiveBuffer(ws);
if (ws.N >= 2)
{
// Extract the close code first, available as the first 2 bytes
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
code |= ((uint64_t) _rxbuf[ws.header_size + 1]) << 0;
code |= ((uint64_t) _rxbuf[ws.header_size]) << 8;
code |= ((uint64_t) _rxbuf[ws.header_size+1]) << 0;
// Get the reason.
if (ws.N > 2)
{
reason = frameData.substr(2, frameData.size());
}
// Validate that the reason is proper utf-8. Autobahn 7.5.1
if (!validateUtf8(reason))
{
code = WebSocketCloseConstants::kInvalidFramePayloadData;
reason = WebSocketCloseConstants::kInvalidFramePayloadDataMessage;
}
//
// Validate close codes. Autobahn 7.9.*
// 1014, 1015 are debattable. The firefox MSDN has a description for them.
// Full list of status code and status range is defined in the dedicated
// RFC section at https://tools.ietf.org/html/rfc6455#page-45
//
if (code < 1000 || code == 1004 || code == 1006 || (code > 1013 && code < 3000))
{
// build up an error message containing the bad error code
std::stringstream ss;
ss << WebSocketCloseConstants::kInvalidCloseCodeMessage << ": " << code;
reason = ss.str();
code = WebSocketCloseConstants::kProtocolErrorCode;
reason.assign(_rxbuf.begin()+ws.header_size + 2,
_rxbuf.begin()+ws.header_size + (size_t) ws.N);
}
}
else
@ -710,8 +648,8 @@ namespace ix
}
else
{
// we got the CLOSE frame answer from our close, so we can close the connection
// if the code/reason are the same
// we got the CLOSE frame answer from our close, so we can close the connection if
// the code/reason are the same
bool identicalReason;
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
@ -734,7 +672,8 @@ namespace ix
}
// Erase the message that has been processed from the input/read buffer
_rxbuf.erase(_rxbuf.begin(), _rxbuf.begin() + ws.header_size + (size_t) ws.N);
_rxbuf.erase(_rxbuf.begin(),
_rxbuf.begin() + ws.header_size + (size_t) ws.N);
}
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
@ -743,8 +682,7 @@ namespace ix
{
_rxbuf.clear();
// if we previously closed the connection (CLOSING state), then set state to CLOSED
// (code/reason were set before)
// if we previously closed the connection (CLOSING state), then set state to CLOSED (code/reason were set before)
if (_readyState == ReadyState::CLOSING)
{
closeSocket();
@ -755,8 +693,7 @@ namespace ix
{
closeSocketAndSwitchToClosedState(WebSocketCloseConstants::kAbnormalCloseCode,
WebSocketCloseConstants::kAbnormalCloseMessage,
0,
false);
0, false);
}
}
}
@ -774,7 +711,8 @@ namespace ix
for (auto&& chunk : _chunks)
{
msg += chunk;
std::string str(chunk.begin(), chunk.end());
msg += str;
}
return msg;
@ -782,38 +720,21 @@ namespace ix
void WebSocketTransport::emitMessage(MessageKind messageKind,
const std::string& message,
bool compressedMessage,
const wsheader_type& ws,
const OnMessageCallback& onMessageCallback)
{
size_t wireSize = message.size();
// When the RSV1 bit is 1 it means the message is compressed
if (compressedMessage && messageKind != MessageKind::FRAGMENT)
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != MessageKind::FRAGMENT)
{
std::string decompressedMessage;
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(decompressedMessage))
{
close(WebSocketCloseConstants::kInvalidFramePayloadData,
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
}
else
{
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
}
onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
}
else
{
if (messageKind == MessageKind::MSG_TEXT && !validateUtf8(message))
{
close(WebSocketCloseConstants::kInvalidFramePayloadData,
WebSocketCloseConstants::kInvalidFramePayloadDataMessage);
}
else
{
onMessageCallback(message, wireSize, false, messageKind);
}
onMessageCallback(message, wireSize, false, messageKind);
}
}
@ -821,18 +742,20 @@ namespace ix
{
auto now = std::chrono::system_clock::now();
auto seconds =
std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count();
return static_cast<unsigned>(seconds);
}
WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type,
const std::string& message,
bool compress,
const OnProgressCallback& onProgressCallback)
WebSocketSendInfo WebSocketTransport::sendData(
wsheader_type::opcode_type type,
const std::string& message,
bool compress,
const OnProgressCallback& onProgressCallback)
{
if (_readyState != ReadyState::OPEN && _readyState != ReadyState::CLOSING)
{
return WebSocketSendInfo(false);
return WebSocketSendInfo();
}
size_t payloadSize = message.size();
@ -860,11 +783,6 @@ namespace ix
message_end = compressedMessage.end();
}
{
std::lock_guard<std::mutex> lock(_txbufMutex);
_txbuf.reserve(wireSize);
}
// Common case for most message. No fragmentation required.
if (wireSize < kChunkSize)
{
@ -885,10 +803,10 @@ namespace ix
std::string::const_iterator begin = message_begin;
std::string::const_iterator end = message_end;
for (uint64_t i = 0; i < steps; ++i)
for (uint64_t i = 0 ; i < steps; ++i)
{
bool firstStep = i == 0;
bool lastStep = (i + 1) == steps;
bool lastStep = (i+1) == steps;
bool fin = lastStep;
end = begin + kChunkSize;
@ -906,7 +824,7 @@ namespace ix
// Send message
sendFragment(opcodeType, fin, begin, end, compress);
if (onProgressCallback && !onProgressCallback((int) i, (int) steps))
if (onProgressCallback && !onProgressCallback((int)i, (int) steps))
{
break;
}
@ -915,21 +833,13 @@ namespace ix
}
}
bool success = true;
// Request to flush the send buffer on the background thread if it isn't empty
if (!isSendBufferEmpty())
{
_socket->wakeUpFromPoll(Socket::kSendRequest);
// FIXME: we should have a timeout when sending large messages: see #131
if (_blockingSend && !flushSendBuffer())
{
success = false;
}
}
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
}
void WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
@ -944,13 +854,14 @@ namespace ix
uint8_t masking_key[4] = {};
masking_key[0] = (x >> 24);
masking_key[1] = (x >> 16) & 0xff;
masking_key[2] = (x >> 8) & 0xff;
masking_key[3] = (x) &0xff;
masking_key[2] = (x >> 8) & 0xff;
masking_key[3] = (x) & 0xff;
std::vector<uint8_t> header;
header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) +
(_useMask ? 4 : 0),
0);
header.assign(2 +
(message_size >= 126 ? 2 : 0) +
(message_size >= 65536 ? 6 : 0) +
(_useMask ? 4 : 0), 0);
header[0] = type;
// The fin bit indicate that this is the last fragment. Fin is French for end.
@ -959,9 +870,8 @@ namespace ix
header[0] |= 0x80;
}
// The rsv1 bit indicate that the frame is compressed
// continuation opcodes should not set it. Autobahn 12.2.10 and others 12.X
if (compress && type != wsheader_type::CONTINUATION)
// This bit indicate that the frame is compressed
if (compress)
{
header[0] |= 0x40;
}
@ -1001,8 +911,8 @@ namespace ix
header[5] = (message_size >> 32) & 0xff;
header[6] = (message_size >> 24) & 0xff;
header[7] = (message_size >> 16) & 0xff;
header[8] = (message_size >> 8) & 0xff;
header[9] = (message_size >> 0) & 0xff;
header[8] = (message_size >> 8) & 0xff;
header[9] = (message_size >> 0) & 0xff;
if (_useMask)
{
@ -1014,7 +924,8 @@ namespace ix
}
// _txbuf will keep growing until it can be transmitted over the socket:
appendToSendBuffer(header, message_begin, message_end, message_size, masking_key);
appendToSendBuffer(header, message_begin, message_end,
message_size, masking_key);
// Now actually send this data
sendOnSocket();
@ -1034,26 +945,28 @@ namespace ix
return info;
}
WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message,
const OnProgressCallback& onProgressCallback)
WebSocketSendInfo WebSocketTransport::sendBinary(
const std::string& message,
const OnProgressCallback& onProgressCallback)
{
return sendData(
wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate, onProgressCallback);
return sendData(wsheader_type::BINARY_FRAME, message,
_enablePerMessageDeflate, onProgressCallback);
}
WebSocketSendInfo WebSocketTransport::sendText(const std::string& message,
const OnProgressCallback& onProgressCallback)
WebSocketSendInfo WebSocketTransport::sendText(
const std::string& message,
const OnProgressCallback& onProgressCallback)
{
return sendData(
wsheader_type::TEXT_FRAME, message, _enablePerMessageDeflate, onProgressCallback);
return sendData(wsheader_type::TEXT_FRAME, message,
_enablePerMessageDeflate, onProgressCallback);
}
ssize_t WebSocketTransport::send()
{
std::lock_guard<std::mutex> lock(_socketMutex);
return _socket->send((char*) &_txbuf[0], _txbuf.size());
return _socket->send((char*)&_txbuf[0], _txbuf.size());
}
void WebSocketTransport::sendOnSocket()
@ -1090,7 +1003,7 @@ namespace ix
{
// See list of close events here:
// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
std::string closure {(char) (code >> 8), (char) (code & 0xff)};
std::string closure{(char)(code >> 8), (char)(code & 0xff)};
// copy reason after code
closure.append(reason);
@ -1110,10 +1023,8 @@ namespace ix
_socket->close();
}
void WebSocketTransport::closeSocketAndSwitchToClosedState(uint16_t code,
const std::string& reason,
size_t closeWireSize,
bool remote)
void WebSocketTransport::closeSocketAndSwitchToClosedState(
uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
{
closeSocket();
@ -1129,20 +1040,13 @@ namespace ix
_requestInitCancellation = false;
}
void WebSocketTransport::close(uint16_t code,
const std::string& reason,
size_t closeWireSize,
bool remote)
void WebSocketTransport::close(
uint16_t code, const std::string& reason, size_t closeWireSize, bool remote)
{
_requestInitCancellation = true;
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
if (closeWireSize == 0)
{
closeWireSize = reason.size();
}
{
std::lock_guard<std::mutex> lock(_closeDataMutex);
_closeCode = code;
@ -1167,27 +1071,4 @@ namespace ix
return _txbuf.size();
}
bool WebSocketTransport::flushSendBuffer()
{
while (!isSendBufferEmpty() && !_requestInitCancellation)
{
// Wait with a 10ms timeout until the socket is ready to write.
// This way we are not busy looping
PollResultType result = _socket->isReadyToWrite(10);
if (result == PollResultType::Error)
{
closeSocket();
setReadyState(ReadyState::CLOSED);
return false;
}
else if (result == PollResultType::ReadyForWrite)
{
sendOnSocket();
}
}
return true;
}
} // namespace ix

View File

@ -12,7 +12,6 @@
#include "IXCancellationRequest.h"
#include "IXProgressCallback.h"
#include "IXSocketTLSOptions.h"
#include "IXWebSocketCloseConstants.h"
#include "IXWebSocketHandshake.h"
#include "IXWebSocketHttpHeaders.h"
@ -61,8 +60,7 @@ namespace ix
enum class PollResult
{
Succeeded,
AbnormalClose,
CannotFlushSendBuffer
AbnormalClose
};
using OnMessageCallback =
@ -73,18 +71,14 @@ namespace ix
~WebSocketTransport();
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
const SocketTLSOptions& socketTLSOptions,
bool enablePong,
int pingIntervalSecs,
int pingTimeoutSecs);
// Client
WebSocketInitResult connectToUrl(const std::string& url,
const WebSocketHttpHeaders& headers,
WebSocketInitResult connectToUrl(const std::string& url, // Client
int timeoutSecs);
// Server
WebSocketInitResult connectToSocket(std::shared_ptr<Socket> socket, int timeoutSecs);
WebSocketInitResult connectToSocket(int fd, // Server
int timeoutSecs);
PollResult poll();
WebSocketSendInfo sendBinary(const std::string& message,
@ -115,8 +109,6 @@ namespace ix
unsigned header_size;
bool fin;
bool rsv1;
bool rsv2;
bool rsv3;
bool mask;
enum opcode_type
{
@ -136,10 +128,6 @@ namespace ix
// client should mask but server should not
std::atomic<bool> _useMask;
// Tells whether we should flush the send buffer before
// saying that a send is complete. This is the mode for server code.
std::atomic<bool> _blockingSend;
// Buffer for reading from our socket. That buffer is never resized.
std::vector<uint8_t> _readbuf;
@ -156,15 +144,7 @@ namespace ix
// messages (tested messages up to 700M) and we cannot put them in a single
// buffer that is resized, as this operation can be slow when a buffer has its
// size increased 2 fold, while appending to a list has a fixed cost.
std::list<std::string> _chunks;
// Record the message kind (will be TEXT or BINARY) for a fragmented
// message, present in the first chunk, since the final chunk will be a
// CONTINUATION opcode and doesn't tell the full message kind
MessageKind _fragmentedMessageKind;
// Ditto for whether a message is compressed
bool _compressedMessage;
std::list<std::vector<uint8_t>> _chunks;
// Fragments are 32K long
static constexpr size_t kChunkSize = 1 << 15;
@ -188,9 +168,6 @@ namespace ix
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
std::atomic<bool> _enablePerMessageDeflate;
// Used to control TLS connection behavior
SocketTLSOptions _socketTLSOptions;
// Used to cancel dns lookup + socket connect + http upgrade
std::atomic<bool> _requestInitCancellation;
@ -243,7 +220,6 @@ namespace ix
size_t closeWireSize,
bool remote);
bool flushSendBuffer();
void sendOnSocket();
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
const std::string& message,
@ -258,7 +234,7 @@ namespace ix
void emitMessage(MessageKind messageKind,
const std::string& message,
bool compressedMessage,
const wsheader_type& ws,
const OnMessageCallback& onMessageCallback);
bool isSendBufferEmpty() const;

View File

@ -1,9 +0,0 @@
/*
* IXWebSocketVersion.h
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#pragma once
#define IX_WEBSOCKET_VERSION "7.8.5"

View File

@ -32,249 +32,232 @@
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid(const std::string& SchemeName)
static bool IsSchemeValid( const std::string& SchemeName )
{
for (auto c : SchemeName)
{
if (!isalpha(c) && c != '+' && c != '-' && c != '.') return false;
}
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
return true;
}
bool LUrlParser::clParseURL::GetPort(int* OutPort) const
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if (!IsValid())
{
return false;
}
if ( !IsValid() ) { return false; }
int Port = atoi(m_Port.c_str());
int Port = atoi( m_Port.c_str() );
if (Port <= 0 || Port > 65535)
{
return false;
}
if ( Port <= 0 || Port > 65535 ) { return false; }
if (OutPort)
{
*OutPort = Port;
}
if ( OutPort ) { *OutPort = Port; }
return true;
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL(const std::string& URL)
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to
*lower case in scheme names
*/
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr(CurrentString, ':');
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if (!LocalString)
{
return clParseURL(LUrlParserError_NoUrlCharacter);
}
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string(CurrentString, LocalString - CurrentString);
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if (!IsSchemeValid(Result.m_Scheme))
{
return clParseURL(LUrlParserError_InvalidSchemeName);
}
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform(
Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower);
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString + 1;
}
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
if (*CurrentString++ != '/') return clParseURL(LUrlParserError_NoDoubleSlash);
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
const char* LocalString = CurrentString;
while (*LocalString)
{
if (*LocalString == '@')
{
// user name and password are specified
bHasUserName = true;
break;
}
else if (*LocalString == '/')
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
// user name and password
LocalString = CurrentString;
if (bHasUserName)
{
// read user name
while (*LocalString && *LocalString != ':' && *LocalString != '@')
LocalString++;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string(CurrentString, LocalString - CurrentString);
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
// proceed with the current pointer
CurrentString = LocalString;
if (*CurrentString == ':')
{
// skip ':'
CurrentString++;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
// read password
LocalString = CurrentString;
while (*LocalString && *LocalString != '@')
LocalString++;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string(CurrentString, LocalString - CurrentString);
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
CurrentString = LocalString;
}
// skip '@'
if (*CurrentString != '@')
{
return clParseURL(LUrlParserError_NoAtSign);
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
CurrentString++;
}
bool bHasBracket = (*CurrentString == '[');
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
// go ahead, read the host name
LocalString = CurrentString;
while (*LocalString)
{
if (bHasBracket && *LocalString == ']')
{
// end of IPv6 address
LocalString++;
break;
}
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
{
// port number is specified
break;
}
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
LocalString++;
}
Result.m_Host = std::string(CurrentString, LocalString - CurrentString);
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
CurrentString = LocalString;
// is port number specified?
if (*CurrentString == ':')
{
CurrentString++;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
// read port number
LocalString = CurrentString;
while (*LocalString && *LocalString != '/')
LocalString++;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string(CurrentString, LocalString - CurrentString);
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
CurrentString = LocalString;
}
// end of string
if (!*CurrentString)
{
Result.m_ErrorCode = LUrlParserError_Ok;
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
return Result;
}
// skip '/'
if (*CurrentString != '/')
{
return clParseURL(LUrlParserError_NoSlash);
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
CurrentString++;
// parse the path
LocalString = CurrentString;
// parse the path
LocalString = CurrentString;
while (*LocalString && *LocalString != '#' && *LocalString != '?')
LocalString++;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string(CurrentString, LocalString - CurrentString);
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
CurrentString = LocalString;
// check for query
if (*CurrentString == '?')
{
// skip '?'
CurrentString++;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
// read query
LocalString = CurrentString;
while (*LocalString && *LocalString != '#')
LocalString++;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string(CurrentString, LocalString - CurrentString);
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
CurrentString = LocalString;
}
// check for fragment
if (*CurrentString == '#')
{
// skip '#'
CurrentString++;
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
// read fragment
LocalString = CurrentString;
while (*LocalString)
LocalString++;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string(CurrentString, LocalString - CurrentString);
}
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
}
Result.m_ErrorCode = LUrlParserError_Ok;
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
return Result;
}

View File

@ -62,10 +62,7 @@ namespace LUrlParser
}
/// return 'true' if the parsing was successful
bool IsValid() const
{
return m_ErrorCode == LUrlParserError_Ok;
}
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the
/// 0..65535 range)

View File

@ -17,4 +17,4 @@ namespace ix
//
pthread_setname_np(name.substr(0, 63).c_str());
}
} // namespace ix
}

View File

@ -1,16 +0,0 @@
/*
* IXSetThreadName_freebsd.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "../IXSetThreadName.h"
#include <pthread.h>
#include <pthread_np.h>
namespace ix
{
void setThreadName(const std::string& name)
{
pthread_set_name_np(pthread_self(), name.substr(0, 15).c_str());
}
} // namespace ix

View File

@ -15,6 +15,7 @@ namespace ix
// See prctl and PR_SET_NAME property in
// http://man7.org/linux/man-pages/man2/prctl.2.html
//
pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
pthread_setname_np(pthread_self(),
name.substr(0, 15).c_str());
}
} // namespace ix
}

View File

@ -4,14 +4,13 @@
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
*/
#include "../IXSetThreadName.h"
#include <Windows.h>
namespace ix
{
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack(push, 8)
#pragma pack(push,8)
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
@ -31,8 +30,7 @@ namespace ix
__try
{
RaiseException(
MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*) &info);
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)& info);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
@ -43,4 +41,4 @@ namespace ix
{
SetThreadName(-1, name.c_str());
}
} // namespace ix
}

View File

@ -9,58 +9,32 @@ install: brew
# on osx it is good practice to make /usr/local user writable
# sudo chown -R `whoami`/staff /usr/local
brew:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4 install)
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install)
ws:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j 4)
ws_install:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j 4 install)
ws_openssl:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; make -j 4)
ws_mbedtls:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
ws_no_ssl:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .. ; make -j)
uninstall:
xargs rm -fv < build/install_manifest.txt
tag:
git tag v"`sh tools/extract_version.sh`"
xcode:
cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 -GXcode && open ixwebsocket.xcodeproj
xcode_openssl:
cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 -DUSE_OPEN_SSL=1 -GXcode && open ixwebsocket.xcodeproj
.PHONY: docker
NAME := ${DOCKER_REPO}/ws
TAG := $(shell sh tools/extract_version.sh)
NAME := bsergean/ws
TAG := $(shell cat DOCKER_VERSION)
IMG := ${NAME}:${TAG}
LATEST := ${NAME}:latest
BUILD := ${NAME}:build
print_version:
@echo 'IXWebSocket version =>' ${TAG}
docker_test:
docker build -f docker/Dockerfile.debian -t bsergean/ixwebsocket_test:build .
docker:
git clean -dfx
docker build -t ${IMG} .
docker tag ${IMG} ${BUILD}
docker_push:
docker tag ${IMG} ${LATEST}
docker push ${LATEST}
docker push ${IMG}
run:
docker run --cap-add sys_ptrace --entrypoint=sh -it bsergean/ws:build
@ -70,9 +44,9 @@ trail:
sh third_party/remote_trailing_whitespaces.sh
format:
clang-format -i `find test ixwebsocket ws -name '*.cpp' -o -name '*.h'`
find test ixwebsocket ws -name '*.cpp' -o -name '*.h' -exec clang-format -i {} \;
# That target is used to start a node server, but isn't required as we have
# That target is used to start a node server, but isn't required as we have
# a builtin C++ server started in the unittest now
test_server:
(cd test && npm i ws && node broadcast-server.js)
@ -81,27 +55,11 @@ test_server:
# env TEST=Websocket_chat make test
# env TEST=heartbeat make test
test:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; make -j 4)
(cd test ; python2.7 run.py -r)
test_openssl:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_OPEN_SSL=1 -DUSE_TEST=1 .. ; make -j 4)
(cd test ; python2.7 run.py -r)
test_mbedtls:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_MBED_TLS=1 -DUSE_TEST=1 .. ; make -j 4)
(cd test ; python2.7 run.py -r)
test_no_ssl:
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TEST=1 .. ; make -j 4)
(cd test ; python2.7 run.py -r)
python2.7 test/run.py
ws_test: ws
(cd ws ; env DEBUG=1 PATH=../ws/build:$$PATH bash test_ws.sh)
autobahn_report:
cp -rvf ~/sandbox/reports/clients/* ../bsergean.github.io/IXWebSocket/autobahn/
# For the fork that is configured with appveyor
rebase_upstream:
git fetch upstream
@ -113,12 +71,6 @@ install_cmake_for_linux:
mkdir -p /tmp/cmake
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-Linux-x86_64.tar.gz)
# python -m venv venv
# source venv/bin/activate
# pip install mkdocs
doc:
mkdocs gh-deploy
.PHONY: test
.PHONY: build
.PHONY: ws

View File

@ -1 +0,0 @@
site_name: IXWebSocket

View File

@ -15,30 +15,22 @@ if (MAC)
option(USE_TLS "Add TLS support" ON)
endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
include_directories(
${PROJECT_SOURCE_DIR}/Catch2/single_include
../third_party
../third_party/msgpack11
../third_party/spdlog/include
../ws
)
add_definitions(-DSPDLOG_COMPILED_LIB=1)
find_package(JsonCpp)
if (NOT JSONCPP_FOUND)
include_directories(../third_party/jsoncpp)
set(JSONCPP_SOURCES ../third_party/jsoncpp/jsoncpp.cpp)
endif()
# Shared sources
set (SOURCES
${JSONCPP_SOURCES}
set (SOURCES
test_runner.cpp
IXTest.cpp
IXGetFreePort.cpp
../third_party/msgpack11/msgpack11.cpp
../ws/ixcore/utils/IXCoreLogger.cpp
IXSocketTest.cpp
IXSocketConnectTest.cpp
@ -49,18 +41,13 @@ set (SOURCES
IXHttpClientTest.cpp
IXHttpServerTest.cpp
IXUnityBuildsTest.cpp
IXHttpTest.cpp
IXCobraChatTest.cpp
IXCobraMetricsPublisherTest.cpp
IXDNSLookupTest.cpp
IXWebSocketSubProtocolTest.cpp
IXSentryClientTest.cpp
)
# Some unittest don't work on windows yet
if (UNIX)
list(APPEND SOURCES
IXWebSocketChatTest.cpp
IXDNSLookupTest.cpp
cmd_websocket_chat.cpp
IXWebSocketCloseTest.cpp
)
endif()
@ -85,21 +72,8 @@ if (MAC)
endif()
if (APPLE AND USE_TLS)
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
endif()
if (JSONCPP_FOUND)
target_include_directories(ixwebsocket_unittest PUBLIC ${JSONCPP_INCLUDE_DIRS})
target_link_libraries(ixwebsocket_unittest ${JSONCPP_LIBRARIES})
endif()
target_link_libraries(ixwebsocket_unittest ixsnake)
target_link_libraries(ixwebsocket_unittest ixcobra)
target_link_libraries(ixwebsocket_unittest ixwebsocket)
target_link_libraries(ixwebsocket_unittest ixcrypto)
target_link_libraries(ixwebsocket_unittest ixcore)
target_link_libraries(ixwebsocket_unittest ixsentry)
target_link_libraries(ixwebsocket_unittest spdlog)
install(TARGETS ixwebsocket_unittest DESTINATION bin)

View File

@ -1,353 +0,0 @@
/*
* cmd_satori_chat.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
#include "IXTest.h"
#include "catch.hpp"
#include <chrono>
#include <iostream>
#include <ixcobra/IXCobraConnection.h>
#include <ixcrypto/IXUuid.h>
#include <ixsnake/IXRedisServer.h>
#include <ixsnake/IXSnakeServer.h>
using namespace ix;
namespace
{
std::atomic<size_t> incomingBytes(0);
std::atomic<size_t> outgoingBytes(0);
void setupTrafficTrackerCallback()
{
ix::CobraConnection::setTrafficTrackerCallback([](size_t size, bool incoming) {
if (incoming)
{
incomingBytes += size;
}
else
{
outgoingBytes += size;
}
});
}
class CobraChat
{
public:
CobraChat(const std::string& user,
const std::string& session,
const std::string& endpoint);
void subscribe(const std::string& channel);
void start();
void stop();
void run();
bool isReady() const;
void sendMessage(const std::string& text);
size_t getReceivedMessagesCount() const;
bool hasPendingMessages() const;
Json::Value popMessage();
private:
std::string _user;
std::string _session;
std::string _endpoint;
std::queue<Json::Value> _publish_queue;
mutable std::mutex _queue_mutex;
std::thread _thread;
std::atomic<bool> _stop;
ix::CobraConnection _conn;
std::atomic<bool> _connectedAndSubscribed;
std::queue<Json::Value> _receivedQueue;
std::mutex _logMutex;
};
CobraChat::CobraChat(const std::string& user,
const std::string& session,
const std::string& endpoint)
: _user(user)
, _session(session)
, _endpoint(endpoint)
, _stop(false)
, _connectedAndSubscribed(false)
{
}
void CobraChat::start()
{
_thread = std::thread(&CobraChat::run, this);
}
void CobraChat::stop()
{
_stop = true;
_thread.join();
}
bool CobraChat::isReady() const
{
return _connectedAndSubscribed;
}
size_t CobraChat::getReceivedMessagesCount() const
{
return _receivedQueue.size();
}
bool CobraChat::hasPendingMessages() const
{
std::unique_lock<std::mutex> lock(_queue_mutex);
return !_publish_queue.empty();
}
Json::Value CobraChat::popMessage()
{
std::unique_lock<std::mutex> lock(_queue_mutex);
auto msg = _publish_queue.front();
_publish_queue.pop();
return msg;
}
//
// Callback to handle received messages, that are printed on the console
//
void CobraChat::subscribe(const std::string& channel)
{
std::string filter;
_conn.subscribe(channel, filter, [this](const Json::Value& msg) {
spdlog::info("receive {}", msg.toStyledString());
if (!msg.isObject()) return;
if (!msg.isMember("user")) return;
if (!msg.isMember("text")) return;
if (!msg.isMember("session")) return;
std::string msg_user = msg["user"].asString();
std::string msg_text = msg["text"].asString();
std::string msg_session = msg["session"].asString();
// We are not interested in messages
// from a different session.
if (msg_session != _session) return;
// We are not interested in our own messages
if (msg_user == _user) return;
_receivedQueue.push(msg);
std::stringstream ss;
ss << std::endl << msg_user << " > " << msg_text << std::endl << _user << " > ";
log(ss.str());
});
}
void CobraChat::sendMessage(const std::string& text)
{
Json::Value msg;
msg["user"] = _user;
msg["session"] = _session;
msg["text"] = text;
std::unique_lock<std::mutex> lock(_queue_mutex);
_publish_queue.push(msg);
}
//
// Do satori communication on a background thread, where we can have
// something like an event loop that publish, poll and receive data
//
void CobraChat::run()
{
// "chat" conf
std::string appkey("FC2F10139A2BAc53BB72D9db967b024f");
std::string channel = _session;
std::string role = "_sub";
std::string secret = "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba";
SocketTLSOptions socketTLSOptions;
_conn.configure(appkey,
_endpoint,
role,
secret,
ix::WebSocketPerMessageDeflateOptions(true),
socketTLSOptions);
_conn.connect();
_conn.setEventCallback([this, channel](ix::CobraConnectionEventType eventType,
const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers,
const std::string& subscriptionId,
CobraConnection::MsgId msgId) {
if (eventType == ix::CobraConnection_EventType_Open)
{
log("Subscriber connected: " + _user);
for (auto&& it : headers)
{
log("Headers " + it.first + " " + it.second);
}
}
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
log("Subscriber authenticated: " + _user);
subscribe(channel);
}
else if (eventType == ix::CobraConnection_EventType_Error)
{
log(errMsg + _user);
}
else if (eventType == ix::CobraConnection_EventType_Closed)
{
log("Connection closed: " + _user);
}
else if (eventType == ix::CobraConnection_EventType_Subscribed)
{
log("Subscription ok: " + _user + " subscription_id " + subscriptionId);
_connectedAndSubscribed = true;
}
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
{
log("Unsubscription ok: " + _user + " subscription_id " + subscriptionId);
}
else if (eventType == ix::CobraConnection_EventType_Published)
{
Logger() << "Subscriber: published message acked: " << msgId;
}
});
while (!_stop)
{
{
while (hasPendingMessages())
{
auto msg = popMessage();
std::string text = msg["text"].asString();
std::stringstream ss;
ss << "Sending msg [" << text << "]";
log(ss.str());
Json::Value channels;
channels.append(channel);
_conn.publish(channels, msg);
}
}
ix::msleep(50);
}
_conn.unsubscribe(channel);
ix::msleep(50);
_conn.disconnect();
_conn.setEventCallback([](ix::CobraConnectionEventType /*eventType*/,
const std::string& /*errMsg*/,
const ix::WebSocketHttpHeaders& /*headers*/,
const std::string& /*subscriptionId*/,
CobraConnection::MsgId /*msgId*/) { ; });
}
} // namespace
TEST_CASE("Cobra_chat", "[cobra_chat]")
{
SECTION("Exchange and count sent/received messages.")
{
int port = getFreePort();
snake::AppConfig appConfig = makeSnakeServerConfig(port);
// Start a redis server
ix::RedisServer redisServer(appConfig.redisPort);
auto res = redisServer.listen();
REQUIRE(res.first);
redisServer.start();
// Start a snake server
snake::SnakeServer snakeServer(appConfig);
snakeServer.run();
int timeout;
setupTrafficTrackerCallback();
std::string session = ix::generateSessionId();
std::stringstream ss;
ss << "ws://localhost:" << port;
std::string endpoint = ss.str();
CobraChat chatA("jean", session, endpoint);
CobraChat chatB("paul", session, endpoint);
chatA.start();
chatB.start();
// Wait for all chat instance to be ready
timeout = 10 * 1000; // 10s
while (true)
{
if (chatA.isReady() && chatB.isReady()) break;
ix::msleep(10);
timeout -= 10;
if (timeout <= 0)
{
snakeServer.stop();
redisServer.stop();
REQUIRE(false); // timeout
}
}
// Add a bit of extra time, for the subscription to be active
ix::msleep(1000);
chatA.sendMessage("from A1");
chatA.sendMessage("from A2");
chatA.sendMessage("from A3");
chatB.sendMessage("from B1");
chatB.sendMessage("from B2");
// 1. Wait for all messages to be sent
timeout = 10 * 1000; // 10s
while (chatA.hasPendingMessages() || chatB.hasPendingMessages())
{
ix::msleep(10);
timeout -= 10;
if (timeout <= 0)
{
snakeServer.stop();
redisServer.stop();
REQUIRE(false); // timeout
}
}
// Give us 1s for all messages to be received
ix::msleep(1000);
chatA.stop();
chatB.stop();
REQUIRE(chatA.getReceivedMessagesCount() == 2);
REQUIRE(chatB.getReceivedMessagesCount() == 3);
spdlog::info("Incoming bytes {}", incomingBytes);
spdlog::info("Outgoing bytes {}", outgoingBytes);
spdlog::info("Stopping snake server...");
snakeServer.stop();
spdlog::info("Stopping redis server...");
redisServer.stop();
}
}

View File

@ -1,316 +0,0 @@
/*
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone. All rights reserved.
*/
#include "IXTest.h"
#include "catch.hpp"
#include <iostream>
#include <ixcobra/IXCobraMetricsPublisher.h>
#include <ixcrypto/IXUuid.h>
#include <ixsnake/IXRedisServer.h>
#include <ixsnake/IXSnakeServer.h>
#include <set>
using namespace ix;
namespace
{
std::atomic<size_t> incomingBytes(0);
std::atomic<size_t> outgoingBytes(0);
void setupTrafficTrackerCallback()
{
ix::CobraConnection::setTrafficTrackerCallback([](size_t size, bool incoming) {
if (incoming)
{
incomingBytes += size;
}
else
{
outgoingBytes += size;
}
});
}
//
// This project / appkey is configure on cobra to not do any batching.
// This way we can start a subscriber and receive all messages as they come in.
//
std::string APPKEY("FC2F10139A2BAc53BB72D9db967b024f");
std::string CHANNEL("unittest_channel");
std::string PUBLISHER_ROLE("_pub");
std::string PUBLISHER_SECRET("1c04DB8fFe76A4EeFE3E318C72d771db");
std::string SUBSCRIBER_ROLE("_sub");
std::string SUBSCRIBER_SECRET("66B1dA3ED5fA074EB5AE84Dd8CE3b5ba");
std::atomic<bool> gStop;
std::atomic<bool> gSubscriberConnectedAndSubscribed;
std::atomic<size_t> gUniqueMessageIdsCount;
std::atomic<int> gMessageCount;
std::set<std::string> gIds;
std::mutex gProtectIds; // std::set is no thread-safe, so protect access with this mutex.
//
// Background thread subscribe to the channel and validates what was sent
//
void startSubscriber(const std::string& endpoint)
{
gSubscriberConnectedAndSubscribed = false;
gUniqueMessageIdsCount = 0;
gMessageCount = 0;
ix::CobraConnection conn;
SocketTLSOptions socketTLSOptions;
conn.configure(APPKEY,
endpoint,
SUBSCRIBER_ROLE,
SUBSCRIBER_SECRET,
ix::WebSocketPerMessageDeflateOptions(true),
socketTLSOptions);
conn.connect();
conn.setEventCallback([&conn](ix::CobraConnectionEventType eventType,
const std::string& errMsg,
const ix::WebSocketHttpHeaders& headers,
const std::string& subscriptionId,
CobraConnection::MsgId msgId) {
if (eventType == ix::CobraConnection_EventType_Open)
{
Logger() << "Subscriber connected:";
for (auto&& it : headers)
{
log("Headers " + it.first + " " + it.second);
}
}
if (eventType == ix::CobraConnection_EventType_Error)
{
Logger() << "Subscriber error:" << errMsg;
}
else if (eventType == ix::CobraConnection_EventType_Authenticated)
{
log("Subscriber authenticated");
std::string filter;
conn.subscribe(CHANNEL, filter, [](const Json::Value& msg) {
log(msg.toStyledString());
std::string id = msg["id"].asString();
{
std::lock_guard<std::mutex> guard(gProtectIds);
gIds.insert(id);
}
gMessageCount++;
});
}
else if (eventType == ix::CobraConnection_EventType_Subscribed)
{
Logger() << "Subscriber: subscribed to channel " << subscriptionId;
if (subscriptionId == CHANNEL)
{
gSubscriberConnectedAndSubscribed = true;
}
else
{
Logger() << "Subscriber: unexpected channel " << subscriptionId;
}
}
else if (eventType == ix::CobraConnection_EventType_UnSubscribed)
{
Logger() << "Subscriber: ununexpected from channel " << subscriptionId;
if (subscriptionId != CHANNEL)
{
Logger() << "Subscriber: unexpected channel " << subscriptionId;
}
}
else if (eventType == ix::CobraConnection_EventType_Published)
{
Logger() << "Subscriber: published message acked: " << msgId;
}
});
while (!gStop)
{
std::chrono::duration<double, std::milli> duration(10);
std::this_thread::sleep_for(duration);
}
conn.unsubscribe(CHANNEL);
conn.disconnect();
gUniqueMessageIdsCount = gIds.size();
}
// publish 100 messages, during roughly 100ms
// this is used to test thread safety of CobraMetricsPublisher::push
void runAdditionalPublisher(ix::CobraMetricsPublisher* cobraMetricsPublisher)
{
Json::Value data;
data["foo"] = "bar";
for (int i = 0; i < 100; ++i)
{
cobraMetricsPublisher->push("sms_metric_F_id", data);
ix::msleep(1);
}
}
} // namespace
TEST_CASE("Cobra_Metrics_Publisher", "[cobra]")
{
int port = getFreePort();
snake::AppConfig appConfig = makeSnakeServerConfig(port);
// Start a redis server
ix::RedisServer redisServer(appConfig.redisPort);
auto res = redisServer.listen();
REQUIRE(res.first);
redisServer.start();
// Start a snake server
snake::SnakeServer snakeServer(appConfig);
snakeServer.run();
setupTrafficTrackerCallback();
std::stringstream ss;
ss << "ws://localhost:" << port;
std::string endpoint = ss.str();
// Make channel name unique
CHANNEL += uuid4();
gStop = false;
std::thread bgThread(&startSubscriber, endpoint);
int timeout = 10 * 1000; // 10s
// Wait until the subscriber is ready (authenticated + subscription successful)
while (!gSubscriberConnectedAndSubscribed)
{
std::chrono::duration<double, std::milli> duration(10);
std::this_thread::sleep_for(duration);
timeout -= 10;
if (timeout <= 0)
{
snakeServer.stop();
redisServer.stop();
REQUIRE(false); // timeout
}
}
ix::CobraMetricsPublisher cobraMetricsPublisher;
SocketTLSOptions socketTLSOptions;
bool perMessageDeflate = true;
cobraMetricsPublisher.configure(APPKEY,
endpoint,
CHANNEL,
PUBLISHER_ROLE,
PUBLISHER_SECRET,
perMessageDeflate,
socketTLSOptions);
cobraMetricsPublisher.setSession(uuid4());
cobraMetricsPublisher.enable(true); // disabled by default, needs to be enabled to be active
Json::Value data;
data["foo"] = "bar";
// (1) Publish without restrictions
cobraMetricsPublisher.push("sms_metric_A_id", data); // (msg #1)
cobraMetricsPublisher.push("sms_metric_B_id", data); // (msg #2)
// (2) Restrict what is sent using a blacklist
// Add one entry to the blacklist
// (will send msg #3)
cobraMetricsPublisher.setBlacklist({
"sms_metric_B_id" // this id will not be sent
});
// (msg #4)
cobraMetricsPublisher.push("sms_metric_A_id", data);
// ...
cobraMetricsPublisher.push("sms_metric_B_id", data); // this won't be sent
// Reset the blacklist
// (msg #5)
cobraMetricsPublisher.setBlacklist({}); // 4.
// (3) Restrict what is sent using rate control
// (msg #6)
cobraMetricsPublisher.setRateControl({
{"sms_metric_C_id", 1}, // published once per minute (60 seconds) max
});
// (msg #7)
cobraMetricsPublisher.push("sms_metric_C_id", data);
cobraMetricsPublisher.push("sms_metric_C_id", data); // this won't be sent
ix::msleep(1400);
// (msg #8)
cobraMetricsPublisher.push("sms_metric_C_id", data); // now this will be sent
ix::msleep(600); // wait a bit so that the last message is sent and can be received
log("Testing suspend/resume now, which will disconnect the cobraMetricsPublisher.");
// Test suspend + resume
for (int i = 0; i < 3; ++i)
{
cobraMetricsPublisher.suspend();
ix::msleep(500);
REQUIRE(!cobraMetricsPublisher.isConnected()); // Check that we are not connected anymore
cobraMetricsPublisher.push("sms_metric_D_id", data); // will not be sent this time
cobraMetricsPublisher.resume();
ix::msleep(2000); // give cobra 2s to connect
REQUIRE(cobraMetricsPublisher.isConnected()); // Check that we are connected now
cobraMetricsPublisher.push("sms_metric_E_id", data);
}
ix::msleep(500);
// Test multi-threaded publish
std::thread bgPublisher1(&runAdditionalPublisher, &cobraMetricsPublisher);
std::thread bgPublisher2(&runAdditionalPublisher, &cobraMetricsPublisher);
std::thread bgPublisher3(&runAdditionalPublisher, &cobraMetricsPublisher);
std::thread bgPublisher4(&runAdditionalPublisher, &cobraMetricsPublisher);
std::thread bgPublisher5(&runAdditionalPublisher, &cobraMetricsPublisher);
bgPublisher1.join();
bgPublisher2.join();
bgPublisher3.join();
bgPublisher4.join();
bgPublisher5.join();
// Now stop the thread
gStop = true;
bgThread.join();
//
// Validate that we received all message kinds, and the correct number of messages
//
CHECK(gIds.count("sms_metric_A_id") == 1);
CHECK(gIds.count("sms_metric_B_id") == 1);
CHECK(gIds.count("sms_metric_C_id") == 1);
CHECK(gIds.count("sms_metric_D_id") == 1);
CHECK(gIds.count("sms_metric_E_id") == 1);
CHECK(gIds.count("sms_metric_F_id") == 1);
CHECK(gIds.count("sms_set_rate_control_id") == 1);
CHECK(gIds.count("sms_set_blacklist_id") == 1);
spdlog::info("Incoming bytes {}", incomingBytes);
spdlog::info("Outgoing bytes {}", outgoingBytes);
spdlog::info("Stopping snake server...");
snakeServer.stop();
spdlog::info("Stopping redis server...");
redisServer.stop();
}

Some files were not shown because too many files have changed in this diff Show More