Compare commits
105 Commits
Author | SHA1 | Date | |
---|---|---|---|
1ee8e31483 | |||
e927dcca3a | |||
86c2ab450e | |||
f3d562ff02 | |||
3c27228585 | |||
9d7807b25c | |||
6256264d78 | |||
04c9985030 | |||
99fe6ea493 | |||
acab19fe9f | |||
2f59fcc02d | |||
8a344788ba | |||
5d641bc267 | |||
0c4866420a | |||
8ed7c660d4 | |||
4cbf85f4d4 | |||
a8ff91ac3c | |||
4cb3326078 | |||
87625a8805 | |||
b75264b98f | |||
21f42210a2 | |||
27287aea50 | |||
39f69bd2b6 | |||
00dc8bacee | |||
9e24475fd6 | |||
61bcc9d27d | |||
1a47656ba0 | |||
7e521e38ef | |||
8410a65754 | |||
6db028fd67 | |||
925eb2f1d0 | |||
5020870cdf | |||
f75684a412 | |||
7c63232157 | |||
c10ff1d210 | |||
40e344a958 | |||
c992cb4e42 | |||
0bc4c4c136 | |||
196f3d4b8a | |||
162e228b34 | |||
e34d960b28 | |||
d62a102aba | |||
18091f49ab | |||
24cd529693 | |||
4ba92832ce | |||
0520329350 | |||
ba88a05b74 | |||
72f8e76369 | |||
0389b0b1a3 | |||
ac0c218455 | |||
299dc0452e | |||
f4af84dc06 | |||
6522bc06ba | |||
50bea7dffa | |||
c4e9abfe80 | |||
a805270d02 | |||
e13b57c73b | |||
5be84926ef | |||
33e7271b85 | |||
d72e5e70f6 | |||
e2c5f751bd | |||
351b86e266 | |||
d0cbff4f4e | |||
cbfc9b9f94 | |||
ca816d801f | |||
2f354d31eb | |||
2c6c1edd37 | |||
9799e7e84b | |||
81be970679 | |||
52221906f6 | |||
3e786fe23a | |||
de24aac7d5 | |||
cd4b0ccf6f | |||
4e1888ac19 | |||
237ede56aa | |||
ba3b1c1a0f | |||
c60c606e0f | |||
5897de6bd9 | |||
cca304fc18 | |||
432624df0d | |||
9c047fac72 | |||
615df9cef0 | |||
094d16304d | |||
c2377e8747 | |||
a63c0d6e78 | |||
aa3bface30 | |||
5d75a3aac3 | |||
c75959fcb5 | |||
bfe0212250 | |||
af1a54f2ad | |||
8a385449ce | |||
396a6985ae | |||
92db53c470 | |||
49865fed0a | |||
f3f71314d9 | |||
2d593dd63b | |||
779b1e6077 | |||
6054dd4b49 | |||
69aa3839bf | |||
6808a0b500 | |||
a7df6120d5 | |||
d20ab19fa9 | |||
b946cda65e | |||
2cfadd93b5 | |||
8607dc1a4a |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
build
|
||||
*.pyc
|
||||
venv
|
||||
ixsnake/ixsnake/.certs/
|
||||
site/
|
||||
ws/.certs/
|
||||
ws/.srl
|
||||
|
19
CMake/FindJsonCpp.cmake
Normal file
19
CMake/FindJsonCpp.cmake
Normal file
@ -0,0 +1,19 @@
|
||||
# Find package structure taken from libcurl
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_path(JSONCPP_INCLUDE_DIRS json/json.h)
|
||||
find_library(JSONCPP_LIBRARY jsoncpp)
|
||||
|
||||
find_package_handle_standard_args(JSONCPP
|
||||
FOUND_VAR
|
||||
JSONCPP_FOUND
|
||||
REQUIRED_VARS
|
||||
JSONCPP_LIBRARY
|
||||
JSONCPP_INCLUDE_DIRS
|
||||
FAIL_MESSAGE
|
||||
"Could NOT find jsoncpp"
|
||||
)
|
||||
|
||||
set(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIRS})
|
||||
set(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY})
|
@ -113,22 +113,24 @@ else()
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(USE_MBED_TLS TRUE)
|
||||
endif()
|
||||
option(USE_TLS "Enable TLS support" FALSE)
|
||||
|
||||
if (USE_TLS)
|
||||
if (WIN32)
|
||||
option(USE_MBED_TLS "Use Mbed TLS" ON)
|
||||
else()
|
||||
option(USE_MBED_TLS "Use Mbed TLS" OFF)
|
||||
endif()
|
||||
option(USE_OPEN_SSL "Use OpenSSL" OFF)
|
||||
|
||||
if (USE_MBED_TLS)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
||||
elseif (APPLE AND NOT USE_OPEN_SSL)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
||||
elseif (WIN32)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
||||
else()
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
set(USE_OPEN_SSL ON)
|
||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
||||
endif()
|
||||
@ -145,19 +147,15 @@ if (USE_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)
|
||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS AND NOT USE_OPEN_SSL)
|
||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(ixwebsocket wsock32 ws2_32)
|
||||
target_link_libraries(ixwebsocket wsock32 ws2_32 shlwapi)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
@ -174,7 +172,9 @@ if (USE_TLS AND USE_OPEN_SSL)
|
||||
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /usr/local/opt/openssl/include)
|
||||
endif()
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if(NOT OPENSSL_FOUND)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
add_definitions(${OPENSSL_DEFINITIONS})
|
||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
@ -182,6 +182,7 @@ if (USE_TLS AND USE_OPEN_SSL)
|
||||
endif()
|
||||
|
||||
if (USE_TLS AND USE_MBED_TLS)
|
||||
# FIXME I'm not too sure that this USE_VENDORED_THIRD_PARTY thing works
|
||||
if (USE_VENDORED_THIRD_PARTY)
|
||||
set (ENABLE_PROGRAMS OFF)
|
||||
add_subdirectory(third_party/mbedtls)
|
||||
@ -206,7 +207,7 @@ else()
|
||||
endif()
|
||||
|
||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||
.
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
|
@ -1 +0,0 @@
|
||||
7.3.1
|
34
Dockerfile
34
Dockerfile
@ -1,34 +0,0 @@
|
||||
FROM alpine as build
|
||||
|
||||
RUN apk add --no-cache gcc g++ musl-dev linux-headers cmake openssl-dev
|
||||
RUN apk add --no-cache make
|
||||
RUN apk add --no-cache zlib-dev
|
||||
|
||||
RUN addgroup -S app && adduser -S -G app app
|
||||
RUN chown -R app:app /opt
|
||||
RUN chown -R app:app /usr/local
|
||||
|
||||
# There is a bug in CMake where we cannot build from the root top folder
|
||||
# So we build from /opt
|
||||
COPY --chown=app:app . /opt
|
||||
WORKDIR /opt
|
||||
|
||||
USER app
|
||||
RUN [ "make", "ws_install" ]
|
||||
|
||||
FROM alpine as runtime
|
||||
|
||||
RUN apk add --no-cache libstdc++
|
||||
|
||||
RUN addgroup -S app && adduser -S -G app app
|
||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
||||
RUN chmod +x /usr/local/bin/ws
|
||||
RUN ldd /usr/local/bin/ws
|
||||
|
||||
# Now run in usermode
|
||||
USER app
|
||||
WORKDIR /home/app
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
EXPOSE 8008
|
||||
CMD ["--help"]
|
1
Dockerfile
Symbolic link
1
Dockerfile
Symbolic link
@ -0,0 +1 @@
|
||||
docker/Dockerfile.centos
|
20
README.md
20
README.md
@ -1,16 +1,16 @@
|
||||
## Hello world
|
||||
|
||||

|
||||

|
||||
|
||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||
|
||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||
|
||||
```
|
||||
# Required on Windows
|
||||
```cpp
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
# Our websocket object
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("ws://localhost:8080/");
|
||||
@ -34,8 +34,14 @@ webSocket.start();
|
||||
webSocket.send("hello world");
|
||||
```
|
||||
|
||||
Interested ? Go read the [docs](https://machinezone.github.io/IXWebSocket/) ! If things don't work as expected, please create an issue in github, or even better a pull request if you know how to fix your problem.
|
||||
Interested? Go read the [docs](https://machinezone.github.io/IXWebSocket/)! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||
|
||||
IXWebSocket is actively being developed, check out the [changelog](CHANGELOG.md) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra).
|
||||
IXWebSocket is actively being developed, check out the [changelog](https://machinezone.github.io/IXWebSocket/CHANGELOG/) to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at [cobra](https://github.com/machinezone/cobra).
|
||||
|
||||
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/IXWebSocket/autobahn/index.html). Some tests are still failing in the server code.
|
||||
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
|
||||
|
||||
## Users
|
||||
|
||||
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
||||
|
||||
- [Machine Zone](https://www.mz.com)
|
||||
|
@ -1,43 +1,67 @@
|
||||
version: "3"
|
||||
services:
|
||||
snake:
|
||||
image: bsergean/ws:build
|
||||
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
|
||||
ports:
|
||||
- "8765:8765"
|
||||
networks:
|
||||
- ws-net
|
||||
depends_on:
|
||||
- redis1
|
||||
# snake:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: ws snake --port 8767 --host 0.0.0.0 --redis_hosts redis1
|
||||
# ports:
|
||||
# - "8767:8767"
|
||||
# networks:
|
||||
# - ws-net
|
||||
# depends_on:
|
||||
# - redis1
|
||||
|
||||
ws:
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
# proxy:
|
||||
# image: bsergean/ws:build
|
||||
# entrypoint: strace ws proxy_server --remote_host 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765 -v
|
||||
# ports:
|
||||
# - "8765:8765"
|
||||
# networks:
|
||||
# - ws-net
|
||||
|
||||
#pyproxy:
|
||||
# image: bsergean/ws_proxy:build
|
||||
# entrypoint: /usr/bin/ws_proxy.py --remote_url 'wss://cobra.addsrv.com' --host 0.0.0.0 --port 8765
|
||||
# ports:
|
||||
# - "8765:8765"
|
||||
# networks:
|
||||
# - ws-net
|
||||
|
||||
# # ws:
|
||||
# # security_opt:
|
||||
# # - seccomp:unconfined
|
||||
# # cap_add:
|
||||
# # - SYS_PTRACE
|
||||
# # stdin_open: true
|
||||
# # tty: true
|
||||
# # image: bsergean/ws:build
|
||||
# # entrypoint: sh
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
# # depends_on:
|
||||
# # - redis1
|
||||
# #
|
||||
# # redis1:
|
||||
# # image: redis:alpine
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
# #
|
||||
# # statsd:
|
||||
# # image: jaconel/statsd
|
||||
# # ports:
|
||||
# # - "8125:8125"
|
||||
# # environment:
|
||||
# # - STATSD_DUMP_MSG=true
|
||||
# # - GRAPHITE_HOST=127.0.0.1
|
||||
# # networks:
|
||||
# # - ws-net
|
||||
|
||||
compile:
|
||||
image: alpine
|
||||
entrypoint: sh
|
||||
stdin_open: true
|
||||
tty: true
|
||||
image: bsergean/ws:build
|
||||
entrypoint: bash
|
||||
networks:
|
||||
- ws-net
|
||||
depends_on:
|
||||
- redis1
|
||||
|
||||
redis1:
|
||||
image: redis:alpine
|
||||
networks:
|
||||
- ws-net
|
||||
|
||||
statsd:
|
||||
image: jaconel/statsd
|
||||
ports:
|
||||
- "8125:8125"
|
||||
environment:
|
||||
- STATSD_DUMP_MSG=true
|
||||
- GRAPHITE_HOST=127.0.0.1
|
||||
networks:
|
||||
- ws-net
|
||||
volumes:
|
||||
- /Users/bsergeant/src/foss:/home/bsergean/src/foss
|
||||
|
||||
networks:
|
||||
ws-net:
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM alpine as build
|
||||
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
|
||||
@ -15,20 +15,25 @@ WORKDIR /opt
|
||||
|
||||
USER app
|
||||
RUN [ "make", "ws_install" ]
|
||||
RUN [ "rm", "-rf", "build" ]
|
||||
|
||||
FROM alpine as runtime
|
||||
FROM alpine:3.11 as runtime
|
||||
|
||||
RUN apk add --no-cache libstdc++
|
||||
RUN apk add --no-cache strace
|
||||
RUN apk add --no-cache gdb
|
||||
|
||||
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
|
||||
|
||||
# Copy source code for gcc
|
||||
COPY --chown=app:app --from=build /opt /opt
|
||||
|
||||
# Now run in usermode
|
||||
USER app
|
||||
WORKDIR /home/app
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
EXPOSE 8008
|
||||
CMD ["--help"]
|
||||
|
35
docker/Dockerfile.centos
Normal file
35
docker/Dockerfile.centos
Normal file
@ -0,0 +1,35 @@
|
||||
FROM centos:8 as build
|
||||
|
||||
RUN yum install -y gcc-c++ make cmake zlib-devel openssl-devel redhat-rpm-config
|
||||
|
||||
RUN groupadd app && useradd -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" ]
|
||||
RUN [ "rm", "-rf", "build" ]
|
||||
|
||||
FROM centos:8 as runtime
|
||||
|
||||
RUN yum install -y gdb strace
|
||||
|
||||
RUN groupadd app && useradd -g app app
|
||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
||||
RUN chmod +x /usr/local/bin/ws
|
||||
RUN ldd /usr/local/bin/ws
|
||||
|
||||
# Copy source code for gcc
|
||||
COPY --chown=app:app --from=build /opt /opt
|
||||
|
||||
# Now run in usermode
|
||||
USER app
|
||||
WORKDIR /home/app
|
||||
|
||||
ENTRYPOINT ["ws"]
|
||||
EXPOSE 8008
|
@ -1,5 +1,208 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
All changes to this project will be documented in this file.
|
||||
|
||||
## [8.0.0] - 2020-01-26
|
||||
|
||||
(SocketServer) add ability to bind on an ipv6 address
|
||||
|
||||
## [7.9.6] - 2020-01-22
|
||||
|
||||
(ws) add a dnslookup sub-command, to get the ip address of a remote host
|
||||
|
||||
## [7.9.5] - 2020-01-14
|
||||
|
||||
(windows) fix #144, get rid of stubbed/un-implemented windows schannel ssl backend
|
||||
|
||||
## [7.9.4] - 2020-01-12
|
||||
|
||||
(openssl + mbedssl) fix #140, can send large files with ws send over ssl / still broken with apple ssl
|
||||
|
||||
## [7.9.3] - 2020-01-10
|
||||
|
||||
(apple ssl) model write method after the OpenSSL one for consistency
|
||||
|
||||
## [7.9.2] - 2020-01-06
|
||||
|
||||
(apple ssl) unify read and write ssl utility code
|
||||
|
||||
## [7.9.1] - 2020-01-06
|
||||
|
||||
(websocket client) better error propagation when errors are detected while sending data
|
||||
(ws send) detect failures to send big files, terminate in those cases and report error
|
||||
|
||||
## [7.9.0] - 2020-01-04
|
||||
|
||||
(ws send) add option (-x) to disable per message deflate compression
|
||||
|
||||
## [7.8.9] - 2020-01-04
|
||||
|
||||
(ws send + receive) handle all message types (ping + pong + fragment) / investigate #140
|
||||
|
||||
## [7.8.8] - 2019-12-28
|
||||
|
||||
(mbedtls) fix related to private key file parsing and initialization
|
||||
|
||||
## [7.8.6] - 2019-12-28
|
||||
|
||||
(ws cobra to sentry/statsd) fix for handling null events properly for empty queues + use queue to send data to statsd
|
||||
|
||||
## [7.8.5] - 2019-12-28
|
||||
|
||||
(ws cobra to sentry) handle null events for empty queues
|
||||
|
||||
## [7.8.4] - 2019-12-27
|
||||
|
||||
(ws cobra to sentry) game is picked in a fair manner, so that all games get the same share of sent events
|
||||
|
||||
## [7.8.3] - 2019-12-27
|
||||
|
||||
(ws cobra to sentry) refactor queue related code into a class
|
||||
|
||||
## [7.8.2] - 2019-12-25
|
||||
|
||||
(ws cobra to sentry) bound the queue size used to hold up cobra messages before they are sent to sentry. Default queue size is a 100 messages. Without such limit the program runs out of memory when a subscriber receive a lot of messages that cannot make it to sentry
|
||||
|
||||
## [7.8.1] - 2019-12-25
|
||||
|
||||
(ws client) use correct compilation defines so that spdlog is not used as a header only library (reduce binary size and increase compilation speed)
|
||||
|
||||
## [7.8.0] - 2019-12-24
|
||||
|
||||
(ws client) all commands use spdlog instead of std::cerr or std::cout for logging
|
||||
|
||||
## [7.6.5] - 2019-12-24
|
||||
|
||||
(cobra client) send a websocket ping every 30s to keep the connection opened
|
||||
|
||||
## [7.6.4] - 2019-12-22
|
||||
|
||||
(client) error handling, quote url in error case when failing to parse one
|
||||
(ws) ws_cobra_publish: register callbacks before connecting
|
||||
(doc) mention mbedtls in supported ssl server backend
|
||||
|
||||
## [7.6.3] - 2019-12-20
|
||||
|
||||
(tls) add a simple description of the TLS configuration routine for debugging
|
||||
|
||||
## [7.6.2] - 2019-12-20
|
||||
|
||||
(mbedtls) correct support for using own certificate and private key
|
||||
|
||||
## [7.6.1] - 2019-12-20
|
||||
|
||||
(ws commands) in websocket proxy, disable automatic reconnections + in Dockerfile, use alpine 3.11
|
||||
|
||||
## [7.6.0] - 2019-12-19
|
||||
|
||||
(cobra) Add TLS options to all cobra commands and classes. Add example to the doc.
|
||||
|
||||
## [7.5.8] - 2019-12-18
|
||||
|
||||
(cobra-to-sentry) capture application version from device field
|
||||
|
||||
## [7.5.7] - 2019-12-18
|
||||
|
||||
(tls) Experimental TLS server support with mbedtls (windows) + process cert tlsoption (client + server)
|
||||
|
||||
## [7.5.6] - 2019-12-18
|
||||
|
||||
(tls servers) Make it clear that apple ssl and mbedtls backends do not support SSL in server mode
|
||||
|
||||
## [7.5.5] - 2019-12-17
|
||||
|
||||
(tls options client) TLSOptions struct _validated member should be initialized to false
|
||||
|
||||
## [7.5.4] - 2019-12-16
|
||||
|
||||
(websocket client) improve the error message when connecting to a non websocket server
|
||||
|
||||
Before:
|
||||
|
||||
```
|
||||
Connection error: Got bad status connecting to example.com:443, status: 200, HTTP Status line: HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```
|
||||
Connection error: Expecting status 101 (Switching Protocol), got 200 status connecting to example.com:443, HTTP Status line: HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
## [7.5.3] - 2019-12-12
|
||||
|
||||
(server) attempt at fixing #131 by using blocking writes in server mode
|
||||
|
||||
## [7.5.2] - 2019-12-11
|
||||
|
||||
(ws) cobra to sentry - created events with sentry tags based on tags present in the cobra messages
|
||||
|
||||
## [7.5.1] - 2019-12-06
|
||||
|
||||
(mac) convert SSL errors to utf8
|
||||
|
||||
## [7.5.0] - 2019-12-05
|
||||
|
||||
- (ws) cobra to sentry. Handle Error 429 Too Many Requests and politely wait before sending more data to sentry.
|
||||
|
||||
In the example below sentry we are sending data too fast, sentry asks us to slow down which we do. Notice how the sent count stop increasing, while we are waiting for 41 seconds.
|
||||
|
||||
```
|
||||
[2019-12-05 15:50:33.759] [info] messages received 2449 sent 3
|
||||
[2019-12-05 15:50:34.759] [info] messages received 5533 sent 7
|
||||
[2019-12-05 15:50:35.759] [info] messages received 8612 sent 11
|
||||
[2019-12-05 15:50:36.759] [info] messages received 11562 sent 15
|
||||
[2019-12-05 15:50:37.759] [info] messages received 14410 sent 19
|
||||
[2019-12-05 15:50:38.759] [info] messages received 17236 sent 23
|
||||
[2019-12-05 15:50:39.282] [error] Error sending data to sentry: 429
|
||||
[2019-12-05 15:50:39.282] [error] Body: {"exception":[{"stacktrace":{"frames":[{"filename":"WorldScene.lua","function":"WorldScene.lua:1935","lineno":1958},{"filename":"WorldScene.lua","function":"onUpdate_WorldCam","lineno":1921},{"filename":"WorldMapTile.lua","function":"__index","lineno":239}]},"value":"noisytypes: Attempt to call nil(nil,2224139838)!"}],"platform":"python","sdk":{"name":"ws","version":"1.0.0"},"tags":[["game","niso"],["userid","107638363"],["environment","live"]],"timestamp":"2019-12-05T23:50:39Z"}
|
||||
|
||||
[2019-12-05 15:50:39.282] [error] Response: {"error_name":"rate_limit","error":"Creation of this event was denied due to rate limiting"}
|
||||
[2019-12-05 15:50:39.282] [warning] Error 429 - Too Many Requests. ws will sleep and retry after 41 seconds
|
||||
[2019-12-05 15:50:39.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:40.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:41.760] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:42.761] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:43.762] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:44.763] [info] messages received 18839 sent 25
|
||||
[2019-12-05 15:50:45.768] [info] messages received 18839 sent 25
|
||||
```
|
||||
|
||||
## [7.4.5] - 2019-12-03
|
||||
|
||||
- (ws) #125 / fix build problem when jsoncpp is not installed locally
|
||||
|
||||
## [7.4.4] - 2019-12-03
|
||||
|
||||
- (ws) #125 / cmake detects an already installed jsoncpp and will try to use this one if present
|
||||
|
||||
## [7.4.3] - 2019-12-03
|
||||
|
||||
- (http client) use std::unordered_map instead of std::map for HttpParameters and HttpFormDataParameters class aliases
|
||||
|
||||
## [7.4.2] - 2019-12-02
|
||||
|
||||
- (client) internal IXDNSLookup class requires a valid cancellation request function callback to be passed in
|
||||
|
||||
## [7.4.1] - 2019-12-02
|
||||
|
||||
- (client) fix an overflow in the exponential back off code
|
||||
|
||||
## [7.4.0] - 2019-11-25
|
||||
|
||||
- (http client) Add support for multipart HTTP POST upload
|
||||
- (ixsentry) Add support for uploading a minidump to sentry
|
||||
|
||||
## [7.3.5] - 2019-11-20
|
||||
|
||||
- On Darwin SSL, add ability to skip peer verification.
|
||||
|
||||
## [7.3.4] - 2019-11-20
|
||||
|
||||
- 32-bits compile fix, courtesy of @fcojavmc
|
||||
|
||||
## [7.3.1] - 2019-11-16
|
||||
|
||||
- ws proxy_server / remote server close not forwarded to the client
|
||||
|
||||
## [7.3.0] - 2019-11-15
|
||||
|
||||
|
@ -33,11 +33,16 @@ vcpkg install ixwebsocket
|
||||
|
||||
### Conan
|
||||
|
||||
Support for building with conan was contributed by Olivia Zoe (thanks !). The package name to reference is `IXWebSocket/5.0.0@LunarWatcher/stable`. The package is in the process to be published to the official conan package repo, but in the meantime, it can be accessed by adding a new remote
|
||||
[  ](https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion)
|
||||
|
||||
```
|
||||
conan remote add remote_name_here https://api.bintray.com/conan/oliviazoe0/conan-packages
|
||||
```
|
||||
Conan is currently supported through a recipe in [Conan Center](https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket) ([Bintray entry](https://bintray.com/conan/conan-center/ixwebsocket%3A_)).
|
||||
|
||||
Package reference
|
||||
|
||||
* Conan 1.21.0 and up: `ixwebsocket/7.9.2`
|
||||
* Earlier versions: `ixwebsocket/7.9.2@_/_`
|
||||
|
||||
Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference _has_ to be lower-case.
|
||||
|
||||
### Docker
|
||||
|
||||
|
81
docs/cobra.md
Normal file
81
docs/cobra.md
Normal file
@ -0,0 +1,81 @@
|
||||
## General
|
||||
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messaging server. The `ws` utility can run a cobra server (named snake), and has client to publish and subscribe to a cobra server.
|
||||
|
||||
Bring up 3 terminals and run a server, a publisher and a subscriber in each one. As you publish data you should see it being received by the subscriber. You can run `redis-cli MONITOR` too to see how redis is being used.
|
||||
|
||||
### Server
|
||||
|
||||
You will need to have a redis server running locally. To run the server:
|
||||
|
||||
```bash
|
||||
$ cd <ixwebsocket-top-level-folder>/ixsnake/ixsnake
|
||||
$ ws snake
|
||||
{
|
||||
"apps": {
|
||||
"FC2F10139A2BAc53BB72D9db967b024f": {
|
||||
"roles": {
|
||||
"_sub": {
|
||||
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
|
||||
},
|
||||
"_pub": {
|
||||
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redis host: 127.0.0.1
|
||||
redis password:
|
||||
redis port: 6379
|
||||
```
|
||||
|
||||
### Publisher
|
||||
|
||||
```bash
|
||||
$ cd <ixwebsocket-top-level-folder>/ws
|
||||
$ ws cobra_publish --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel cobraMetricsSample.json
|
||||
[2019-11-27 09:06:12.980] [info] Publisher connected
|
||||
[2019-11-27 09:06:12.980] [info] Connection: Upgrade
|
||||
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Accept: zTtQKMKbvwjdivURplYXwCVUCWM=
|
||||
[2019-11-27 09:06:12.980] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-11-27 09:06:12.980] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
|
||||
[2019-11-27 09:06:12.980] [info] Upgrade: websocket
|
||||
[2019-11-27 09:06:12.982] [info] Publisher authenticated
|
||||
[2019-11-27 09:06:12.982] [info] Published msg 3
|
||||
[2019-11-27 09:06:12.982] [info] Published message id 3 acked
|
||||
```
|
||||
|
||||
### Subscriber
|
||||
|
||||
```bash
|
||||
$ ws cobra_subscribe --appkey FC2F10139A2BAc53BB72D9db967b024f --endpoint ws://127.0.0.1:8008 --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db test_channel
|
||||
#messages 0 msg/s 0
|
||||
[2019-11-27 09:07:39.341] [info] Subscriber connected
|
||||
[2019-11-27 09:07:39.341] [info] Connection: Upgrade
|
||||
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Accept: 9vkQWofz49qMCUlTSptCCwHWm+Q=
|
||||
[2019-11-27 09:07:39.341] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-11-27 09:07:39.341] [info] Server: ixwebsocket/7.4.0 macos ssl/DarwinSSL zlib 1.2.11
|
||||
[2019-11-27 09:07:39.341] [info] Upgrade: websocket
|
||||
[2019-11-27 09:07:39.342] [info] Subscriber authenticated
|
||||
[2019-11-27 09:07:39.345] [info] Subscriber: subscribed to channel test_channel
|
||||
#messages 0 msg/s 0
|
||||
#messages 0 msg/s 0
|
||||
#messages 0 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 1 msg/s 1
|
||||
#messages 1 msg/s 0
|
||||
#messages 1 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 3 msg/s 2
|
||||
#messages 3 msg/s 0
|
||||
{"baz":123,"foo":"bar"}
|
||||
|
||||
#messages 4 msg/s 1
|
||||
^C
|
||||
```
|
@ -24,13 +24,13 @@ Large frames are broken up into smaller chunks or messages to avoid filling up t
|
||||
|
||||
The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries.
|
||||
|
||||
The unittest tries to be comprehensive, and has been running on multiple platoform, with different sanitizers such as thread sanitizer to catch data races or the undefined behavior sanitizer.
|
||||
The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.
|
||||
|
||||
The regression test is running after each commit on travis.
|
||||
|
||||
## Limitations
|
||||
|
||||
* On Windows TLS is not setup yet to validate certificates.
|
||||
* On Windows and Android certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. On Windows with mbedtls the message will contain `error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed`.
|
||||
* There is no convenient way to embed a ca cert.
|
||||
* 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.
|
||||
|
@ -13,11 +13,11 @@
|
||||
|
||||
## Example code
|
||||
|
||||
```
|
||||
# Required on Windows
|
||||
```cpp
|
||||
// Required on Windows
|
||||
ix::initNetSystem();
|
||||
|
||||
# Our websocket object
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("ws://localhost:8080/");
|
||||
@ -40,7 +40,7 @@ webSocket.start();
|
||||
webSocket.send("hello world");
|
||||
```
|
||||
|
||||
## Why another library ?
|
||||
## Why another library?
|
||||
|
||||
There are 2 main reasons that explain why IXWebSocket got written. First, we needed a C++ cross-platform client library, which should have few dependencies. What looked like the most solid one, [websocketpp](https://github.com/zaphoyd/websocketpp) did depend on boost and this was not an option for us. Secondly, there were other available libraries with fewer dependencies (C ones), but they required calling an explicit poll routine periodically to know if a client had received data from a server, which was not elegant.
|
||||
|
||||
@ -48,4 +48,4 @@ We started by solving those 2 problems, then we added server websocket code, the
|
||||
|
||||
## 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 in github is ok for that. If things don't work as expected, please create an issue in github, or even better a pull request if you know how to fix your problem.
|
||||
IXWebSocket is developed on [GitHub](https://github.com/machinezone/IXWebSocket). We'd love to hear about how you use it; opening up an issue on GitHub is ok for that. If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.
|
||||
|
@ -6,7 +6,7 @@ The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder cou
|
||||
|
||||
To use the network system on Windows, you need to initialize it once with *WSAStartup()* and clean it up with *WSACleanup()*. We have helpers for that which you can use, see below. This init would typically take place in your main function.
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXNetSystem.h>
|
||||
|
||||
int main()
|
||||
@ -22,12 +22,12 @@ int main()
|
||||
|
||||
## WebSocket client API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
|
||||
...
|
||||
|
||||
# Our websocket object
|
||||
// Our websocket object
|
||||
ix::WebSocket webSocket;
|
||||
|
||||
std::string url("ws://localhost:8080/");
|
||||
@ -82,9 +82,9 @@ If the connection was closed and sending failed, the return value will be set to
|
||||
|
||||
### Open and Close notifications
|
||||
|
||||
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
|
||||
The onMessage event will be fired when the connection is opened or closed. This is similar to the [JavaScript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Open)
|
||||
@ -115,7 +115,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Error)
|
||||
@ -140,7 +140,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
|
||||
|
||||
```
|
||||
```cpp
|
||||
std::string url("wss://example.com");
|
||||
websocket.configure(url);
|
||||
```
|
||||
@ -149,7 +149,7 @@ websocket.configure(url);
|
||||
|
||||
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
{
|
||||
if (msg->type == ix::WebSocketMessageType::Ping ||
|
||||
@ -163,7 +163,7 @@ webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||
|
||||
A ping message can be sent to the server, with an optional data string.
|
||||
|
||||
```
|
||||
```cpp
|
||||
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
|
||||
```
|
||||
|
||||
@ -173,7 +173,7 @@ You can configure an optional heart beat / keep-alive, sent every 45 seconds
|
||||
when there is no any traffic to make sure that load balancers do not kill an
|
||||
idle connection.
|
||||
|
||||
```
|
||||
```cpp
|
||||
webSocket.setHeartBeatPeriod(45);
|
||||
```
|
||||
|
||||
@ -181,7 +181,7 @@ webSocket.setHeartBeatPeriod(45);
|
||||
|
||||
You can set extra HTTP headers to be sent during the WebSocket handshake.
|
||||
|
||||
```
|
||||
```cpp
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["foo"] = "bar";
|
||||
webSocket.setExtraHeaders(headers);
|
||||
@ -191,14 +191,14 @@ webSocket.setExtraHeaders(headers);
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
@ -206,7 +206,7 @@ std::cout << "protocol: " << msg->openInfo.protocol << std::endl;
|
||||
|
||||
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
|
||||
@ -239,7 +239,7 @@ 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();
|
||||
```
|
||||
@ -253,7 +253,7 @@ 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",
|
||||
@ -279,7 +279,7 @@ For a server, specifying `caFile` implies that:
|
||||
|
||||
## WebSocket server API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXWebSocketServer.h>
|
||||
|
||||
...
|
||||
@ -344,7 +344,7 @@ server.wait();
|
||||
|
||||
## HTTP client API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
|
||||
...
|
||||
@ -427,7 +427,7 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
||||
|
||||
## HTTP server API
|
||||
|
||||
```
|
||||
```cpp
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
|
||||
ix::HttpServer server(port, hostname);
|
||||
@ -445,7 +445,7 @@ server.wait();
|
||||
|
||||
If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example.
|
||||
|
||||
```
|
||||
```cpp
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||
|
125
docs/ws.md
125
docs/ws.md
@ -243,6 +243,127 @@ Options:
|
||||
--transfer-timeout INT Transfer timeout
|
||||
```
|
||||
|
||||
## Cobra Client
|
||||
## Cobra client and server
|
||||
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has sub-command to interacti with cobra.
|
||||
[cobra](https://github.com/machinezone/cobra) is a real time messenging server. ws has several sub-command to interact with cobra. There is also a minimal cobra compatible server named snake available.
|
||||
|
||||
Below are examples on running a snake server and clients with TLS enabled (the server only works with the OpenSSL and the Mbed TLS backend for now).
|
||||
|
||||
First, generate certificates.
|
||||
|
||||
```
|
||||
$ cd /path/to/IXWebSocket
|
||||
$ cd ixsnake/ixsnake
|
||||
$ bash ../../ws/generate_certs.sh
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
.....+++
|
||||
.................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-ca-key.pem
|
||||
generated ./.certs/trusted-ca-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..+++
|
||||
.......................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-server-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=trusted-server
|
||||
Getting CA Private Key
|
||||
generated ./.certs/trusted-server-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
...................................+++
|
||||
..................................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/trusted-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=trusted-client
|
||||
Getting CA Private Key
|
||||
generated ./.certs/trusted-client-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..............+++
|
||||
.......................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/untrusted-ca-key.pem
|
||||
generated ./.certs/untrusted-ca-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
..........+++
|
||||
................................................+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/untrusted-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=untrusted-client
|
||||
Getting CA Private Key
|
||||
generated ./.certs/untrusted-client-crt.pem
|
||||
Generating RSA private key, 2048 bit long modulus
|
||||
.....................................................................................+++
|
||||
...........+++
|
||||
e is 65537 (0x10001)
|
||||
generated ./.certs/selfsigned-client-key.pem
|
||||
Signature ok
|
||||
subject=/O=machinezone/O=IXWebSocket/CN=selfsigned-client
|
||||
Getting Private key
|
||||
generated ./.certs/selfsigned-client-crt.pem
|
||||
```
|
||||
|
||||
Now run the snake server.
|
||||
|
||||
```
|
||||
$ export certs=.certs
|
||||
$ ws snake --tls --port 8765 --cert-file ${certs}/trusted-server-crt.pem --key-file ${certs}/trusted-server-key.pem --ca-file ${certs}/trusted-ca-crt.pem
|
||||
{
|
||||
"apps": {
|
||||
"FC2F10139A2BAc53BB72D9db967b024f": {
|
||||
"roles": {
|
||||
"_sub": {
|
||||
"secret": "66B1dA3ED5fA074EB5AE84Dd8CE3b5ba"
|
||||
},
|
||||
"_pub": {
|
||||
"secret": "1c04DB8fFe76A4EeFE3E318C72d771db"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redis host: 127.0.0.1
|
||||
redis password:
|
||||
redis port: 6379
|
||||
```
|
||||
|
||||
As a new connection comes in, such output should be printed
|
||||
|
||||
```
|
||||
[2019-12-19 20:27:19.724] [info] New connection
|
||||
id: 0
|
||||
Uri: /v2?appkey=_health
|
||||
Headers:
|
||||
Connection: Upgrade
|
||||
Host: 127.0.0.1:8765
|
||||
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
Sec-WebSocket-Key: d747B0fE61Db73f7Eh47c0==
|
||||
Sec-WebSocket-Protocol: json
|
||||
Sec-WebSocket-Version: 13
|
||||
Upgrade: websocket
|
||||
User-Agent: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
|
||||
```
|
||||
|
||||
To connect and publish a message, do:
|
||||
|
||||
```
|
||||
$ export certs=.certs
|
||||
$ cd /path/to/ws/folder
|
||||
$ ls cobraMetricsSample.json
|
||||
cobraMetricsSample.json
|
||||
$ ws cobra_publish --endpoint wss://127.0.0.1:8765 --appkey FC2F10139A2BAc53BB72D9db967b024f --rolename _pub --rolesecret 1c04DB8fFe76A4EeFE3E318C72d771db --channel foo --cert-file ${certs}/trusted-client-crt.pem --key-file ${certs}/trusted-client-key.pem --ca-file ${certs}/trusted-ca-crt.pem cobraMetricsSample.json
|
||||
[2019-12-19 20:46:42.656] [info] Publisher connected
|
||||
[2019-12-19 20:46:42.657] [info] Connection: Upgrade
|
||||
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Accept: rs99IFThoBrhSg+k8G4ixH9yaq4=
|
||||
[2019-12-19 20:46:42.657] [info] Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15; client_max_window_bits=15
|
||||
[2019-12-19 20:46:42.657] [info] Server: ixwebsocket/7.5.8 macos ssl/OpenSSL OpenSSL 1.0.2q 20 Nov 2018 zlib 1.2.11
|
||||
[2019-12-19 20:46:42.657] [info] Upgrade: websocket
|
||||
[2019-12-19 20:46:42.658] [info] Publisher authenticated
|
||||
[2019-12-19 20:46:42.658] [info] Published msg 3
|
||||
[2019-12-19 20:46:42.659] [info] Published message id 3 acked
|
||||
```
|
||||
|
||||
To use OpenSSL on macOS, compile with `make ws_openssl`. First you will have to install OpenSSL libraries, which can be done with Homebrew. Use `make ws_mbedtls` accordingly to use MbedTLS.
|
||||
|
@ -20,11 +20,16 @@ add_library(ixcobra STATIC
|
||||
${IXCOBRA_HEADERS}
|
||||
)
|
||||
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
set(IXCOBRA_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../ixcore
|
||||
../ixcrypto
|
||||
../third_party)
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories( ixcobra PUBLIC ${IXCOBRA_INCLUDE_DIRS} )
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "IXCobraConnection.h"
|
||||
#include <ixcrypto/IXHMac.h>
|
||||
#include <ixwebsocket/IXWebSocket.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
@ -14,6 +15,7 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -22,6 +24,7 @@ namespace ix
|
||||
PublishTrackerCallback CobraConnection::_publishTrackerCallback = nullptr;
|
||||
constexpr size_t CobraConnection::kQueueMaxSize;
|
||||
constexpr CobraConnection::MsgId CobraConnection::kInvalidMsgId;
|
||||
constexpr int CobraConnection::kPingIntervalSecs;
|
||||
|
||||
CobraConnection::CobraConnection() :
|
||||
_webSocket(new WebSocket()),
|
||||
@ -226,6 +229,10 @@ namespace ix
|
||||
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
|
||||
invokeErrorCallback(ss.str(), std::string());
|
||||
}
|
||||
else if (msg->type == ix::WebSocketMessageType::Pong)
|
||||
{
|
||||
invokeEventCallback(ix::CobraConnection_EventType_Pong);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -243,7 +250,8 @@ namespace ix
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions)
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_roleName = rolename;
|
||||
_roleSecret = rolesecret;
|
||||
@ -256,6 +264,8 @@ namespace ix
|
||||
std::string url = ss.str();
|
||||
_webSocket->setUrl(url);
|
||||
_webSocket->setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
||||
_webSocket->setTLSOptions(socketTLSOptions);
|
||||
_webSocket->setPingInterval(kPingIntervalSecs);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@ -20,6 +20,7 @@
|
||||
namespace ix
|
||||
{
|
||||
class WebSocket;
|
||||
struct SocketTLSOptions;
|
||||
|
||||
enum CobraConnectionEventType
|
||||
{
|
||||
@ -29,7 +30,8 @@ namespace ix
|
||||
CobraConnection_EventType_Closed = 3,
|
||||
CobraConnection_EventType_Subscribed = 4,
|
||||
CobraConnection_EventType_UnSubscribed = 5,
|
||||
CobraConnection_EventType_Published = 6
|
||||
CobraConnection_EventType_Published = 6,
|
||||
CobraConnection_EventType_Pong = 7
|
||||
};
|
||||
|
||||
enum CobraConnectionPublishMode
|
||||
@ -62,7 +64,8 @@ namespace ix
|
||||
const std::string& endpoint,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions);
|
||||
const WebSocketPerMessageDeflateOptions& webSocketPerMessageDeflateOptions,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Set the traffic tracker callback
|
||||
static void setTrafficTrackerCallback(const TrafficTrackerCallback& callback);
|
||||
@ -213,6 +216,9 @@ namespace ix
|
||||
|
||||
// Each pdu sent should have an incremental unique id
|
||||
std::atomic<uint64_t> _id;
|
||||
|
||||
// Frequency at which we send a websocket ping to the backing cobra connection
|
||||
static constexpr int kPingIntervalSecs = 30;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "IXCobraMetricsPublisher.h"
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
@ -31,14 +32,15 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
// Configure the satori connection and start its publish background thread
|
||||
_cobra_metrics_theaded_publisher.start();
|
||||
|
||||
_cobra_metrics_theaded_publisher.configure(appkey, endpoint, channel,
|
||||
rolename, rolesecret,
|
||||
enablePerMessageDeflate);
|
||||
enablePerMessageDeflate, socketTLSOptions);
|
||||
}
|
||||
|
||||
Json::Value& CobraMetricsPublisher::getGenericAttributes()
|
||||
|
@ -9,12 +9,14 @@
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct SocketTLSOptions;
|
||||
|
||||
class CobraMetricsPublisher
|
||||
{
|
||||
public:
|
||||
@ -43,7 +45,8 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Setter for the list of blacklisted metrics ids.
|
||||
/// That list is sorted internally for fast lookups
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXCobraMetricsThreadedPublisher.h"
|
||||
#include <ixwebsocket/IXSetThreadName.h>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
#include <algorithm>
|
||||
@ -13,6 +14,7 @@
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -63,6 +65,10 @@ namespace ix
|
||||
{
|
||||
ss << "Published message " << msgId << " acked";
|
||||
}
|
||||
else if (eventType == ix::CobraConnection_EventType_Pong)
|
||||
{
|
||||
ss << "Received websocket pong";
|
||||
}
|
||||
|
||||
ix::IXCoreLogger::Log(ss.str().c_str());
|
||||
});
|
||||
@ -91,14 +97,17 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions)
|
||||
{
|
||||
_channel = channel;
|
||||
|
||||
ix::IXCoreLogger::Log(socketTLSOptions.getDescription().c_str());
|
||||
|
||||
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(enablePerMessageDeflate);
|
||||
_cobra_connection.configure(appkey, endpoint,
|
||||
rolename, rolesecret,
|
||||
webSocketPerMessageDeflateOptions);
|
||||
webSocketPerMessageDeflateOptions, socketTLSOptions);
|
||||
}
|
||||
|
||||
void CobraMetricsThreadedPublisher::pushMessage(MessageKind messageKind)
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "IXCobraConnection.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
@ -18,6 +18,8 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
struct SocketTLSOptions;
|
||||
|
||||
class CobraMetricsThreadedPublisher
|
||||
{
|
||||
public:
|
||||
@ -30,7 +32,8 @@ namespace ix
|
||||
const std::string& channel,
|
||||
const std::string& rolename,
|
||||
const std::string& rolesecret,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
const SocketTLSOptions& socketTLSOptions);
|
||||
|
||||
/// Start the worker thread, used for background publishing
|
||||
void start();
|
||||
|
@ -16,10 +16,15 @@ add_library(ixsentry STATIC
|
||||
${IXSENTRY_HEADERS}
|
||||
)
|
||||
|
||||
find_package(JsonCpp)
|
||||
if (NOT JSONCPP_FOUND)
|
||||
set(JSONCPP_INCLUDE_DIRS ../third_party/jsoncpp)
|
||||
endif()
|
||||
|
||||
set(IXSENTRY_INCLUDE_DIRS
|
||||
.
|
||||
..
|
||||
../third_party
|
||||
../third_party/spdlog/include)
|
||||
../ixcore
|
||||
${JSONCPP_INCLUDE_DIRS})
|
||||
|
||||
target_include_directories( ixsentry PUBLIC ${IXSENTRY_INCLUDE_DIRS} )
|
||||
|
@ -8,8 +8,11 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <ixwebsocket/IXWebSocketVersion.h>
|
||||
#include <ixcore/utils/IXCoreLogger.h>
|
||||
|
||||
|
||||
namespace ix
|
||||
@ -18,6 +21,7 @@ namespace ix
|
||||
: _dsn(dsn)
|
||||
, _validDsn(false)
|
||||
, _luaFrameRegex("\t([^/]+):([0-9]+): in function ['<]([^/]+)['>]")
|
||||
, _httpClient(std::make_shared<HttpClient>(true))
|
||||
{
|
||||
const std::regex dsnRegex("(http[s]?)://([^:]+):([^@]+)@([^/]+)/([0-9]+)");
|
||||
std::smatch group;
|
||||
@ -117,26 +121,6 @@ namespace ix
|
||||
{
|
||||
Json::Value payload;
|
||||
|
||||
payload["platform"] = "python";
|
||||
payload["sdk"]["name"] = "ws";
|
||||
payload["sdk"]["version"] = "1.0.0";
|
||||
payload["timestamp"] = SentryClient::getIso8601();
|
||||
|
||||
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
|
||||
|
||||
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
|
||||
std::string stack(msg["data"][stackTraceFieldName].asString());
|
||||
|
||||
Json::Value exception;
|
||||
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
|
||||
exception["value"] = isNoisyTypes ? parseExceptionName(stack) : msg["data"]["message"];
|
||||
|
||||
payload["exception"].append(exception);
|
||||
|
||||
Json::Value extra;
|
||||
extra["cobra_event"] = msg;
|
||||
extra["cobra_event"] = msg;
|
||||
|
||||
//
|
||||
// "tags": [
|
||||
// [
|
||||
@ -145,8 +129,61 @@ namespace ix
|
||||
// ],
|
||||
// ]
|
||||
//
|
||||
Json::Value tags;
|
||||
Json::Value tags(Json::arrayValue);
|
||||
|
||||
payload["platform"] = "python";
|
||||
payload["sdk"]["name"] = "ws";
|
||||
payload["sdk"]["version"] = IX_WEBSOCKET_VERSION;
|
||||
payload["timestamp"] = SentryClient::getIso8601();
|
||||
|
||||
bool isNoisyTypes = msg["id"].asString() == "game_noisytypes_id";
|
||||
|
||||
std::string stackTraceFieldName = isNoisyTypes ? "traceback" : "stack";
|
||||
std::string stack;
|
||||
std::string message;
|
||||
|
||||
if (isNoisyTypes)
|
||||
{
|
||||
stack = msg["data"][stackTraceFieldName].asString();
|
||||
message = parseExceptionName(stack);
|
||||
}
|
||||
else // logging
|
||||
{
|
||||
if (msg["data"].isMember("info"))
|
||||
{
|
||||
stack = msg["data"]["info"][stackTraceFieldName].asString();
|
||||
message = msg["data"]["info"]["message"].asString();
|
||||
|
||||
if (msg["data"].isMember("tags"))
|
||||
{
|
||||
auto members = msg["data"]["tags"].getMemberNames();
|
||||
|
||||
for (auto member : members)
|
||||
{
|
||||
Json::Value tag;
|
||||
tag.append(member);
|
||||
tag.append(msg["data"]["tags"][member]);
|
||||
tags.append(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack = msg["data"][stackTraceFieldName].asString();
|
||||
message = msg["data"]["message"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value exception;
|
||||
exception["stacktrace"]["frames"] = parseLuaStackTrace(stack);
|
||||
exception["value"] = message;
|
||||
|
||||
payload["exception"].append(exception);
|
||||
|
||||
Json::Value extra;
|
||||
extra["cobra_event"] = msg;
|
||||
|
||||
// Builtin tags
|
||||
Json::Value gameTag;
|
||||
gameTag.append("game");
|
||||
gameTag.append(msg["device"]["game"]);
|
||||
@ -162,6 +199,11 @@ namespace ix
|
||||
environmentTag.append(msg["device"]["environment"]);
|
||||
tags.append(environmentTag);
|
||||
|
||||
Json::Value clientVersionTag;
|
||||
clientVersionTag.append("client_version");
|
||||
clientVersionTag.append(msg["device"]["app_version"]);
|
||||
tags.append(clientVersionTag);
|
||||
|
||||
payload["tags"] = tags;
|
||||
|
||||
return _jsonWriter.write(payload);
|
||||
@ -169,39 +211,63 @@ namespace ix
|
||||
|
||||
std::pair<HttpResponsePtr, std::string> SentryClient::send(const Json::Value& msg, bool verbose)
|
||||
{
|
||||
auto args = _httpClient.createRequest();
|
||||
auto args = _httpClient->createRequest();
|
||||
args->extraHeaders["X-Sentry-Auth"] = SentryClient::computeAuthHeader();
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->logger = [](const std::string& msg) { spdlog::info("request logger: {}", msg); };
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
|
||||
std::string body = computePayload(msg);
|
||||
HttpResponsePtr response = _httpClient.post(_url, body, args);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
for (auto it : response->headers)
|
||||
{
|
||||
spdlog::info("{}: {}", it.first, it.second);
|
||||
}
|
||||
|
||||
spdlog::info("Upload size: {}", response->uploadSize);
|
||||
spdlog::info("Download size: {}", response->downloadSize);
|
||||
|
||||
spdlog::info("Status: {}", response->statusCode);
|
||||
if (response->errorCode != HttpErrorCode::Ok)
|
||||
{
|
||||
spdlog::info("error message: {}", response->errorMsg);
|
||||
}
|
||||
|
||||
if (response->headers["Content-Type"] != "application/octet-stream")
|
||||
{
|
||||
spdlog::info("payload: {}", response->payload);
|
||||
}
|
||||
}
|
||||
HttpResponsePtr response = _httpClient->post(_url, body, args);
|
||||
|
||||
return std::make_pair(response, body);
|
||||
}
|
||||
|
||||
// https://sentry.io/api/12345/minidump?sentry_key=abcdefgh");
|
||||
std::string SentryClient::computeUrl(const std::string& project, const std::string& key)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "https://sentry.io/api/"
|
||||
<< project
|
||||
<< "/minidump?sentry_key="
|
||||
<< key;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
//
|
||||
// curl -v -X POST -F upload_file_minidump=@ws/crash.dmp 'https://sentry.io/api/123456/minidump?sentry_key=12344567890'
|
||||
//
|
||||
void SentryClient::uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback)
|
||||
{
|
||||
std::string multipartBoundary = _httpClient->generateMultipartBoundary();
|
||||
|
||||
auto args = _httpClient->createRequest();
|
||||
args->verb = HttpClient::kPost;
|
||||
args->connectTimeout = 60;
|
||||
args->transferTimeout = 5 * 60;
|
||||
args->followRedirects = true;
|
||||
args->verbose = verbose;
|
||||
args->multipartBoundary = multipartBoundary;
|
||||
args->logger = [](const std::string& msg) { ix::IXCoreLogger::Log(msg.c_str()); };
|
||||
|
||||
HttpFormDataParameters httpFormDataParameters;
|
||||
httpFormDataParameters["upload_file_minidump"] = minidumpBytes;
|
||||
|
||||
HttpParameters httpParameters;
|
||||
httpParameters["sentry"] = sentryMetadata;
|
||||
|
||||
args->url = computeUrl(project, key);
|
||||
args->body = _httpClient->serializeHttpFormDataParameters(multipartBoundary, httpFormDataParameters, httpParameters);
|
||||
|
||||
_httpClient->performRequest(args, onResponseCallback);
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -8,8 +8,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <json/json.h>
|
||||
#include <regex>
|
||||
#include <memory>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -23,12 +24,24 @@ namespace ix
|
||||
|
||||
Json::Value parseLuaStackTrace(const std::string& stack);
|
||||
|
||||
void uploadMinidump(
|
||||
const std::string& sentryMetadata,
|
||||
const std::string& minidumpBytes,
|
||||
const std::string& project,
|
||||
const std::string& key,
|
||||
bool verbose,
|
||||
const OnResponseCallback& onResponseCallback);
|
||||
|
||||
private:
|
||||
int64_t getTimestamp();
|
||||
std::string computeAuthHeader();
|
||||
std::string getIso8601();
|
||||
std::string computePayload(const Json::Value& msg);
|
||||
|
||||
std::string computeUrl(const std::string& project, const std::string& key);
|
||||
|
||||
void displayReponse(HttpResponsePtr response);
|
||||
|
||||
std::string _dsn;
|
||||
bool _validDsn;
|
||||
std::string _url;
|
||||
@ -41,7 +54,7 @@ namespace ix
|
||||
|
||||
std::regex _luaFrameRegex;
|
||||
|
||||
HttpClient _httpClient;
|
||||
std::shared_ptr<HttpClient> _httpClient;
|
||||
};
|
||||
|
||||
} // namespace ix
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <ixwebsocket/IXSocketTLSOptions.h>
|
||||
|
||||
namespace snake
|
||||
{
|
||||
@ -26,6 +27,9 @@ namespace snake
|
||||
// AppKeys
|
||||
nlohmann::json apps;
|
||||
|
||||
// TLS options
|
||||
ix::SocketTLSOptions socketTLSOptions;
|
||||
|
||||
// Misc
|
||||
bool verbose;
|
||||
};
|
||||
|
@ -29,8 +29,13 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
CancellationRequest cancellationRequest = []() -> bool
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
std::string errMsg;
|
||||
return _socket->connect(hostname, port, errMsg, nullptr);
|
||||
return _socket->connect(hostname, port, errMsg, cancellationRequest);
|
||||
}
|
||||
|
||||
void RedisClient::stop()
|
||||
|
@ -17,8 +17,8 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections)
|
||||
: SocketServer(port, host, backlog, maxConnections)
|
||||
RedisServer::RedisServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
, _stopHandlingConnections(false)
|
||||
{
|
||||
|
@ -25,7 +25,8 @@ namespace ix
|
||||
RedisServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
virtual ~RedisServer();
|
||||
virtual void stop() final;
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace snake
|
||||
: _appConfig(appConfig)
|
||||
, _server(appConfig.port, appConfig.hostname)
|
||||
{
|
||||
;
|
||||
_server.setTLSOptions(appConfig.socketTLSOptions);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -61,7 +61,7 @@ namespace ix
|
||||
errMsg = "no error";
|
||||
|
||||
// Maybe a cancellation request got in before the background thread terminated ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
@ -107,7 +107,7 @@ namespace ix
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
|
||||
|
||||
// Were we cancelled ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
@ -115,7 +115,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Maybe a cancellation request got in before the bg terminated ?
|
||||
if (isCancellationRequested && isCancellationRequested())
|
||||
if (isCancellationRequested())
|
||||
{
|
||||
errMsg = "cancellation requested";
|
||||
return nullptr;
|
||||
|
@ -13,7 +13,7 @@ namespace ix
|
||||
uint32_t calculateRetryWaitMilliseconds(uint32_t retry_count,
|
||||
uint32_t maxWaitBetweenReconnectionRetries)
|
||||
{
|
||||
uint32_t wait_time = std::pow(2, retry_count) * 100;
|
||||
uint32_t wait_time = (retry_count < 26) ? (std::pow(2, retry_count) * 100) : 0;
|
||||
|
||||
if (wait_time > maxWaitBetweenReconnectionRetries || wait_time == 0)
|
||||
{
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -65,7 +66,8 @@ namespace ix
|
||||
};
|
||||
|
||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||
using HttpParameters = std::map<std::string, std::string>;
|
||||
using HttpParameters = std::unordered_map<std::string, std::string>;
|
||||
using HttpFormDataParameters = std::unordered_map<std::string, std::string>;
|
||||
using Logger = std::function<void(const std::string&)>;
|
||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||
|
||||
@ -75,6 +77,7 @@ namespace ix
|
||||
std::string verb;
|
||||
WebSocketHttpHeaders extraHeaders;
|
||||
std::string body;
|
||||
std::string multipartBoundary;
|
||||
int connectTimeout;
|
||||
int transferTimeout;
|
||||
bool followRedirects;
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <zlib.h>
|
||||
@ -198,8 +199,16 @@ namespace ix
|
||||
// Set default Content-Type if unspecified
|
||||
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
|
||||
{
|
||||
ss << "Content-Type: application/x-www-form-urlencoded"
|
||||
<< "\r\n";
|
||||
if (args->multipartBoundary.empty())
|
||||
{
|
||||
ss << "Content-Type: application/x-www-form-urlencoded"
|
||||
<< "\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "Content-Type: multipart/form-data; boundary=" << args->multipartBoundary
|
||||
<< "\r\n";
|
||||
}
|
||||
}
|
||||
ss << "\r\n";
|
||||
ss << body;
|
||||
@ -597,6 +606,53 @@ namespace ix
|
||||
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)
|
||||
{
|
||||
z_stream inflateState;
|
||||
@ -649,4 +705,16 @@ namespace ix
|
||||
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
|
||||
|
@ -64,6 +64,13 @@ namespace ix
|
||||
|
||||
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;
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@ -43,8 +42,8 @@ namespace
|
||||
|
||||
namespace ix
|
||||
{
|
||||
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections)
|
||||
: SocketServer(port, host, backlog, maxConnections)
|
||||
HttpServer::HttpServer(int port, const std::string& host, int backlog, size_t maxConnections, int addressFamily)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
|
@ -28,7 +28,8 @@ namespace ix
|
||||
HttpServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
virtual ~HttpServer();
|
||||
virtual void stop() final;
|
||||
|
||||
|
@ -7,9 +7,9 @@
|
||||
#include "IXSelectInterruptFactory.h"
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
#include <ixwebsocket/IXSelectInterruptPipe.h>
|
||||
#include "IXSelectInterruptPipe.h"
|
||||
#else
|
||||
#include <ixwebsocket/IXSelectInterrupt.h>
|
||||
#include "IXSelectInterrupt.h"
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
|
@ -54,14 +54,17 @@ namespace ix
|
||||
// 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
|
||||
// have 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];
|
||||
memset(fds, 0, sizeof(fds));
|
||||
|
||||
fds[0].fd = sockfd;
|
||||
fds[0].events = (readyToRead) ? POLLIN : POLLOUT;
|
||||
|
||||
// this is ignored by poll, but our select based poll wrapper on Windows needs it
|
||||
fds[0].events |= POLLERR;
|
||||
|
||||
// File descriptor used to interrupt select when needed
|
||||
@ -132,6 +135,11 @@ namespace ix
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (sockfd != -1 && (fds[0].revents & POLLERR || fds[0].revents & POLLHUP ||
|
||||
fds[0].revents & POLLNVAL))
|
||||
{
|
||||
pollResult = PollResultType::Error;
|
||||
}
|
||||
|
||||
return pollResult;
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ namespace ix
|
||||
virtual void close();
|
||||
|
||||
virtual ssize_t send(char* buffer, size_t length);
|
||||
virtual ssize_t send(const std::string& buffer);
|
||||
ssize_t send(const std::string& buffer);
|
||||
virtual ssize_t recv(void* buffer, size_t length);
|
||||
|
||||
// Blocking and cancellable versions, working with socket that can be set
|
||||
|
@ -24,9 +24,47 @@
|
||||
|
||||
#include <Security/SecureTransport.h>
|
||||
|
||||
namespace
|
||||
namespace ix
|
||||
{
|
||||
OSStatus read_from_socket(SSLConnectionRef connection, void* data, size_t* len)
|
||||
SocketAppleSSL::SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd)
|
||||
: Socket(fd)
|
||||
, _sslContext(nullptr)
|
||||
, _tlsOptions(tlsOptions)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
SocketAppleSSL::~SocketAppleSSL()
|
||||
{
|
||||
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;
|
||||
@ -42,11 +80,15 @@ namespace
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (requested_sz > *len)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
else if (0 == status)
|
||||
else if (status == 0)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
@ -58,7 +100,8 @@ namespace
|
||||
{
|
||||
case ENOENT: return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN: return errSSLWouldBlock;
|
||||
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
|
||||
case EINPROGRESS: return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET: return errSSLClosedAbort;
|
||||
|
||||
@ -67,7 +110,9 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus write_to_socket(SSLConnectionRef connection, const void* data, size_t* len)
|
||||
OSStatus SocketAppleSSL::writeToSocket(SSLConnectionRef connection,
|
||||
const void* data,
|
||||
size_t* len)
|
||||
{
|
||||
int fd = (int) (long) connection;
|
||||
if (fd < 0) return errSSLInternal;
|
||||
@ -82,11 +127,15 @@ namespace
|
||||
{
|
||||
*len = (size_t) status;
|
||||
if (to_write_sz > *len)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return noErr;
|
||||
}
|
||||
}
|
||||
else if (0 == status)
|
||||
else if (status == 0)
|
||||
{
|
||||
*len = 0;
|
||||
return errSSLClosedGraceful;
|
||||
@ -94,58 +143,25 @@ namespace
|
||||
else
|
||||
{
|
||||
*len = 0;
|
||||
if (EAGAIN == errno)
|
||||
switch (errno)
|
||||
{
|
||||
return errSSLWouldBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
return errSecIO;
|
||||
case ENOENT: return errSSLClosedGraceful;
|
||||
|
||||
case EAGAIN: return errSSLWouldBlock; // EWOULDBLOCK is a define for EAGAIN on osx
|
||||
case EINPROGRESS: return errSSLWouldBlock;
|
||||
|
||||
case ECONNRESET: return errSSLClosedAbort;
|
||||
|
||||
default: return errSecIO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSSLErrorDescription(OSStatus status)
|
||||
|
||||
bool SocketAppleSSL::accept(std::string& errMsg)
|
||||
{
|
||||
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()
|
||||
{
|
||||
SocketAppleSSL::close();
|
||||
errMsg = "TLS not supported yet in server mode with apple ssl backend";
|
||||
return false;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
@ -163,18 +179,41 @@ namespace ix
|
||||
|
||||
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
|
||||
|
||||
SSLSetIOFuncs(_sslContext, read_from_socket, write_to_socket);
|
||||
SSLSetIOFuncs(
|
||||
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
do
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (errSSLWouldBlock == status || errSSLServerAuthCompleted == status);
|
||||
Boolean option(1);
|
||||
SSLSetSessionOption(_sslContext, kSSLSessionOptionBreakOnServerAuth, option);
|
||||
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
|
||||
|
||||
if (status == errSSLServerAuthCompleted)
|
||||
{
|
||||
// proceed with the handshake
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
status = SSLHandshake(_sslContext);
|
||||
} while (status == errSSLWouldBlock || status == errSSLServerAuthCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
if (noErr != status)
|
||||
if (status != noErr)
|
||||
{
|
||||
errMsg = getSSLErrorDescription(status);
|
||||
close();
|
||||
@ -199,32 +238,38 @@ namespace ix
|
||||
|
||||
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
OSStatus status;
|
||||
do
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (status == errSSLWouldBlock)
|
||||
{
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
status = SSLWrite(_sslContext, buf, nbyte, &processed);
|
||||
ret += processed;
|
||||
buf += processed;
|
||||
nbyte -= processed;
|
||||
} while (nbyte > 0 && errSSLWouldBlock == status);
|
||||
|
||||
if (ret == 0 && errSSLClosedAbort != status) ret = -1;
|
||||
return ret;
|
||||
}
|
||||
if (processed > 0) return (ssize_t) processed;
|
||||
|
||||
ssize_t SocketAppleSSL::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
// The connection was reset, inform the caller that this
|
||||
// Socket should close
|
||||
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify ||
|
||||
status == errSSLClosedAbort)
|
||||
{
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (status == errSSLWouldBlock)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// No wait support
|
||||
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
|
||||
{
|
||||
OSStatus status = errSSLWouldBlock;
|
||||
while (errSSLWouldBlock == status)
|
||||
while (status == errSSLWouldBlock)
|
||||
{
|
||||
size_t processed = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
@ -21,6 +21,8 @@ namespace ix
|
||||
SocketAppleSSL(const SocketTLSOptions& tlsOptions, int fd = -1);
|
||||
~SocketAppleSSL();
|
||||
|
||||
virtual bool accept(std::string& errMsg) final;
|
||||
|
||||
virtual bool connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -28,10 +30,13 @@ namespace ix
|
||||
virtual void close() final;
|
||||
|
||||
virtual ssize_t send(char* buffer, size_t length) final;
|
||||
virtual ssize_t send(const std::string& buffer) final;
|
||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||
|
||||
private:
|
||||
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
|
||||
|
||||
|
@ -9,18 +9,16 @@
|
||||
#ifdef IXWEBSOCKET_USE_TLS
|
||||
|
||||
#ifdef IXWEBSOCKET_USE_MBED_TLS
|
||||
#include <ixwebsocket/IXSocketMbedTLS.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <ixwebsocket/IXSocketSChannel.h>
|
||||
#include "IXSocketMbedTLS.h"
|
||||
#elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
||||
#include <ixwebsocket/IXSocketOpenSSL.h>
|
||||
#include "IXSocketOpenSSL.h"
|
||||
#elif __APPLE__
|
||||
#include <ixwebsocket/IXSocketAppleSSL.h>
|
||||
#include "IXSocketAppleSSL.h"
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#include <ixwebsocket/IXSocket.h>
|
||||
#include "IXSocket.h"
|
||||
|
||||
#endif
|
||||
|
||||
@ -31,6 +29,7 @@ namespace ix
|
||||
std::string& errorMsg,
|
||||
const SocketTLSOptions& tlsOptions)
|
||||
{
|
||||
(void) tlsOptions;
|
||||
errorMsg.clear();
|
||||
std::shared_ptr<Socket> socket;
|
||||
|
||||
@ -45,8 +44,6 @@ namespace ix
|
||||
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
|
||||
|
@ -38,9 +38,11 @@ namespace ix
|
||||
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
||||
mbedtls_entropy_init(&_entropy);
|
||||
mbedtls_x509_crt_init(&_cacert);
|
||||
mbedtls_x509_crt_init(&_cert);
|
||||
mbedtls_pk_init(&_pkey);
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
|
||||
bool SocketMbedTLS::init(const std::string& host, bool isClient, std::string& errMsg)
|
||||
{
|
||||
initMBedTLS();
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
@ -58,7 +60,7 @@ namespace ix
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_config_defaults(&_conf,
|
||||
MBEDTLS_SSL_IS_CLIENT,
|
||||
(isClient) ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
|
||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
||||
MBEDTLS_SSL_PRESET_DEFAULT) != 0)
|
||||
{
|
||||
@ -68,13 +70,32 @@ 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 (mbedtls_ssl_conf_own_cert(&_conf, &_cert, &_pkey) < 0)
|
||||
{
|
||||
errMsg = "Problem configuring cert '" + _tlsOptions.certFile + "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
// FIXME: should we call mbedtls_ssl_conf_verify ?
|
||||
|
||||
@ -87,7 +108,8 @@ namespace ix
|
||||
errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
|
||||
return false;
|
||||
}
|
||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||||
|
||||
mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
|
||||
@ -96,7 +118,7 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
{
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
@ -105,6 +127,50 @@ namespace ix
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::accept(std::string& errMsg)
|
||||
{
|
||||
bool isClient = false;
|
||||
bool initialized = init(std::string(), isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
||||
|
||||
int res;
|
||||
do
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
res = mbedtls_ssl_handshake(&_ssl);
|
||||
} while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
||||
|
||||
if (res != 0)
|
||||
{
|
||||
char buf[256];
|
||||
mbedtls_strerror(res, buf, sizeof(buf));
|
||||
|
||||
errMsg = "error in handshake : ";
|
||||
errMsg += buf;
|
||||
|
||||
if (res == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
|
||||
{
|
||||
char verifyBuf[512];
|
||||
uint32_t flags = mbedtls_ssl_get_verify_result(&_ssl);
|
||||
|
||||
mbedtls_x509_crt_verify_info(verifyBuf, sizeof(verifyBuf), " ! ", flags);
|
||||
errMsg += " : ";
|
||||
errMsg += verifyBuf;
|
||||
}
|
||||
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SocketMbedTLS::connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -116,7 +182,8 @@ namespace ix
|
||||
if (_sockfd == -1) return false;
|
||||
}
|
||||
|
||||
bool initialized = init(host, errMsg);
|
||||
bool isClient = true;
|
||||
bool initialized = init(host, isClient, errMsg);
|
||||
if (!initialized)
|
||||
{
|
||||
close();
|
||||
@ -156,41 +223,30 @@ namespace ix
|
||||
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
||||
mbedtls_entropy_free(&_entropy);
|
||||
mbedtls_x509_crt_free(&_cacert);
|
||||
mbedtls_x509_crt_free(&_cert);
|
||||
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
|
||||
{
|
||||
ssize_t sent = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
while (nbyte > 0)
|
||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
||||
|
||||
if (res > 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
||||
|
||||
if (res > 0)
|
||||
{
|
||||
nbyte -= res;
|
||||
sent += res;
|
||||
}
|
||||
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
}
|
||||
|
||||
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
|
||||
|
@ -26,6 +26,8 @@ namespace ix
|
||||
SocketMbedTLS(const SocketTLSOptions& tlsOptions, int fd = -1);
|
||||
~SocketMbedTLS();
|
||||
|
||||
virtual bool accept(std::string& errMsg) final;
|
||||
|
||||
virtual bool connect(const std::string& host,
|
||||
int port,
|
||||
std::string& errMsg,
|
||||
@ -33,7 +35,6 @@ namespace ix
|
||||
virtual void close() final;
|
||||
|
||||
virtual ssize_t send(char* buffer, size_t length) final;
|
||||
virtual ssize_t send(const std::string& buffer) final;
|
||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||
|
||||
private:
|
||||
@ -42,11 +43,13 @@ namespace ix
|
||||
mbedtls_entropy_context _entropy;
|
||||
mbedtls_ctr_drbg_context _ctr_drbg;
|
||||
mbedtls_x509_crt _cacert;
|
||||
mbedtls_x509_crt _cert;
|
||||
mbedtls_pk_context _pkey;
|
||||
|
||||
std::mutex _mutex;
|
||||
SocketTLSOptions _tlsOptions;
|
||||
|
||||
bool init(const std::string& host, std::string& errMsg);
|
||||
bool init(const std::string& host, bool isClient, std::string& errMsg);
|
||||
void initMBedTLS();
|
||||
};
|
||||
|
||||
|
@ -11,8 +11,14 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include <cassert>
|
||||
#include <errno.h>
|
||||
#ifdef _WIN32
|
||||
#include <Shlwapi.h>
|
||||
#else
|
||||
#include <fnmatch.h>
|
||||
#endif
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#include <openssl/x509v3.h>
|
||||
#endif
|
||||
#define socketerrno errno
|
||||
|
||||
namespace ix
|
||||
@ -136,7 +142,11 @@ namespace ix
|
||||
*/
|
||||
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return PathMatchSpecA(host.c_str(), pattern);
|
||||
#else
|
||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
|
||||
@ -593,42 +603,30 @@ namespace ix
|
||||
|
||||
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
|
||||
{
|
||||
ssize_t sent = 0;
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
while (nbyte > 0)
|
||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ERR_clear_error();
|
||||
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)
|
||||
{
|
||||
nbyte -= write_result;
|
||||
sent += write_result;
|
||||
}
|
||||
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
ssize_t SocketOpenSSL::send(const std::string& buffer)
|
||||
{
|
||||
return send((char*) &buffer[0], buffer.size());
|
||||
ERR_clear_error();
|
||||
ssize_t write_result = SSL_write(_ssl_connection, buf, (int) nbyte);
|
||||
int reason = SSL_get_error(_ssl_connection, (int) write_result);
|
||||
|
||||
if (reason == SSL_ERROR_NONE)
|
||||
{
|
||||
return write_result;
|
||||
}
|
||||
else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
|
||||
|
@ -33,7 +33,6 @@ namespace ix
|
||||
virtual void close() final;
|
||||
|
||||
virtual ssize_t send(char* buffer, size_t length) final;
|
||||
virtual ssize_t send(const std::string& buffer) final;
|
||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||
|
||||
private:
|
||||
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* IXSocketSChannel.cpp
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||
*
|
||||
* See https://docs.microsoft.com/en-us/windows/desktop/WinSock/using-secure-socket-extensions
|
||||
*
|
||||
* https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/netds/winsock/securesocket/stcpclient/tcpclient.c
|
||||
*
|
||||
* This is the right example to look at:
|
||||
* https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
|
||||
*/
|
||||
#include "IXSocketSChannel.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <WS2tcpip.h>
|
||||
#include <WinSock2.h>
|
||||
#include <basetsd.h>
|
||||
#include <io.h>
|
||||
#include <schannel.h>
|
||||
#include <ws2def.h>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include <mstcpip.h>
|
||||
#include <ntdsapi.h>
|
||||
#include <rpc.h>
|
||||
#include <stdio.h>
|
||||
#include <tchar.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define RECV_DATA_BUF_SIZE 256
|
||||
|
||||
// Link with ws2_32.lib
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
// link with fwpuclnt.lib for Winsock secure socket extensions
|
||||
#pragma comment(lib, "fwpuclnt.lib")
|
||||
|
||||
// link with ntdsapi.lib for DsMakeSpn function
|
||||
#pragma comment(lib, "ntdsapi.lib")
|
||||
|
||||
// The following function assumes that Winsock
|
||||
// has already been initialized
|
||||
|
||||
|
||||
#else
|
||||
#error("This file should only be built on Windows")
|
||||
#endif
|
||||
|
||||
namespace ix
|
||||
{
|
||||
SocketSChannel::SocketSChannel()
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
SocketSChannel::~SocketSChannel()
|
||||
{
|
||||
}
|
||||
|
||||
bool SocketSChannel::connect(const std::string& host, int port, std::string& errMsg)
|
||||
{
|
||||
return Socket::connect(host, port, errMsg, nullptr);
|
||||
}
|
||||
|
||||
|
||||
void SocketSChannel::secureSocket()
|
||||
{
|
||||
// there will be a lot to do here ...
|
||||
}
|
||||
|
||||
void SocketSChannel::close()
|
||||
{
|
||||
Socket::close();
|
||||
}
|
||||
|
||||
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
|
||||
{
|
||||
return Socket::send(buf, nbyte);
|
||||
}
|
||||
|
||||
ssize_t SocketSChannel::send(const std::string& buffer)
|
||||
{
|
||||
return Socket::send(buffer);
|
||||
}
|
||||
|
||||
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
|
||||
{
|
||||
return Socket::recv(buf, nbyte);
|
||||
}
|
||||
|
||||
} // namespace ix
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* IXSocketSChannel.h
|
||||
* Author: Benjamin Sergeant
|
||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IXSocket.h"
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class SocketSChannel final : public Socket
|
||||
{
|
||||
public:
|
||||
SocketSChannel();
|
||||
~SocketSChannel();
|
||||
|
||||
virtual bool connect(const std::string& host, int port, std::string& errMsg) final;
|
||||
virtual void close() final;
|
||||
|
||||
// The important override
|
||||
virtual void secureSocket() final;
|
||||
|
||||
virtual ssize_t send(char* buffer, size_t length) final;
|
||||
virtual ssize_t send(const std::string& buffer) final;
|
||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace ix
|
@ -11,8 +11,8 @@
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXSocketFactory.h"
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace ix
|
||||
@ -21,15 +21,18 @@ namespace ix
|
||||
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
||||
const int SocketServer::kDefaultTcpBacklog(5);
|
||||
const size_t SocketServer::kDefaultMaxConnections(32);
|
||||
const int SocketServer::kDefaultAddressFamily(AF_INET);
|
||||
|
||||
SocketServer::SocketServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections)
|
||||
size_t maxConnections,
|
||||
int addressFamily)
|
||||
: _port(port)
|
||||
, _host(host)
|
||||
, _backlog(backlog)
|
||||
, _maxConnections(maxConnections)
|
||||
, _addressFamily(addressFamily)
|
||||
, _serverFd(-1)
|
||||
, _stop(false)
|
||||
, _stopGc(false)
|
||||
@ -45,21 +48,26 @@ namespace ix
|
||||
void SocketServer::logError(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
std::cerr << str << std::endl;
|
||||
fprintf(stderr, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
void SocketServer::logInfo(const std::string& str)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_logMutex);
|
||||
std::cout << str << std::endl;
|
||||
fprintf(stdout, "%s\n", str.c_str());
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> SocketServer::listen()
|
||||
{
|
||||
struct sockaddr_in server; // server address information
|
||||
if (_addressFamily != AF_INET && _addressFamily != AF_INET6)
|
||||
{
|
||||
std::string errMsg("SocketServer::listen() AF_INET and AF_INET6 are currently "
|
||||
"the only supported address families");
|
||||
return std::make_pair(false, errMsg);
|
||||
}
|
||||
|
||||
// Get a socket for accepting connections.
|
||||
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
if ((_serverFd = socket(_addressFamily, SOCK_STREAM, 0)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error creating socket): " << strerror(Socket::getErrno());
|
||||
@ -79,27 +87,59 @@ namespace ix
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
server.sin_family = AF_INET;
|
||||
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
|
||||
// to allow that, but this is a bit of a pain. (this is what node or python would do).
|
||||
//
|
||||
// Using INADDR_LOOPBACK also does not work ... while it should.
|
||||
// We default to 127.0.0.1 (localhost)
|
||||
//
|
||||
server.sin_addr.s_addr = inet_addr(_host.c_str());
|
||||
|
||||
if (bind(_serverFd, (struct sockaddr*) &server, sizeof(server)) < 0)
|
||||
if (_addressFamily == AF_INET)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling bind "
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
struct sockaddr_in server;
|
||||
server.sin_family = _addressFamily;
|
||||
server.sin_port = htons(_port);
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
if (inet_pton(_addressFamily, _host.c_str(), &server.sin_addr.s_addr) <= 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
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());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
}
|
||||
else // AF_INET6
|
||||
{
|
||||
struct sockaddr_in6 server;
|
||||
server.sin6_family = _addressFamily;
|
||||
server.sin6_port = htons(_port);
|
||||
|
||||
if (inet_pton(_addressFamily, _host.c_str(), &server.sin6_addr) <= 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "SocketServer::listen() error calling inet_pton "
|
||||
<< "at address " << _host << ":" << _port << " : " << strerror(Socket::getErrno());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
|
||||
// Bind the socket to the server address.
|
||||
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());
|
||||
|
||||
Socket::closeSocket(_serverFd);
|
||||
return std::make_pair(false, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -36,7 +36,8 @@ namespace ix
|
||||
SocketServer(int port = SocketServer::kDefaultPort,
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
virtual ~SocketServer();
|
||||
virtual void stop();
|
||||
|
||||
@ -49,6 +50,7 @@ namespace ix
|
||||
const static std::string kDefaultHost;
|
||||
const static int kDefaultTcpBacklog;
|
||||
const static size_t kDefaultMaxConnections;
|
||||
const static int kDefaultAddressFamily;
|
||||
|
||||
void start();
|
||||
std::pair<bool, std::string> listen();
|
||||
@ -69,6 +71,7 @@ namespace ix
|
||||
std::string _host;
|
||||
int _backlog;
|
||||
size_t _maxConnections;
|
||||
int _addressFamily;
|
||||
|
||||
// socket for accepting connections
|
||||
int _serverFd;
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ix
|
||||
{
|
||||
@ -71,4 +72,16 @@ namespace ix
|
||||
{
|
||||
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
|
||||
|
@ -43,8 +43,10 @@ namespace ix
|
||||
|
||||
const std::string& getErrorMsg() const;
|
||||
|
||||
std::string getDescription() const;
|
||||
|
||||
private:
|
||||
mutable std::string _errMsg;
|
||||
mutable bool _validated;
|
||||
mutable bool _validated = false;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -178,8 +178,8 @@ namespace ix
|
||||
if (status != 101)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Got bad status connecting to " << host << ":" << port << ", status: " << status
|
||||
<< ", HTTP Status line: " << line;
|
||||
ss << "Expecting status 101 (Switching Protocol), got " << status
|
||||
<< " status connecting to " << host << ":" << port << ", HTTP Status line: " << line;
|
||||
return WebSocketInitResult(false, status, ss.str());
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,9 @@ namespace ix
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int handshakeTimeoutSecs)
|
||||
: SocketServer(port, host, backlog, maxConnections)
|
||||
int handshakeTimeoutSecs,
|
||||
int addressFamily)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
{
|
||||
@ -106,7 +107,6 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
logInfo("WebSocketServer::handleConnection() done");
|
||||
connectionState->setTerminated();
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,8 @@ namespace ix
|
||||
const std::string& host = SocketServer::kDefaultHost,
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
|
||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
virtual ~WebSocketServer();
|
||||
virtual void stop() final;
|
||||
|
||||
@ -41,6 +42,8 @@ namespace ix
|
||||
// Get all the connected clients
|
||||
std::set<std::shared_ptr<WebSocket>> getClients();
|
||||
|
||||
const static int kDefaultHandShakeTimeoutSecs;
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
int _handshakeTimeoutSecs;
|
||||
@ -51,7 +54,6 @@ namespace ix
|
||||
std::mutex _clientsMutex;
|
||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||
|
||||
const static int kDefaultHandShakeTimeoutSecs;
|
||||
const static bool kDefaultEnablePong;
|
||||
|
||||
// Methods
|
||||
|
@ -77,6 +77,7 @@ namespace ix
|
||||
|
||||
WebSocketTransport::WebSocketTransport()
|
||||
: _useMask(true)
|
||||
, _blockingSend(false)
|
||||
, _compressedMessage(false)
|
||||
, _readyState(ReadyState::CLOSED)
|
||||
, _closeCode(WebSocketCloseConstants::kInternalErrorCode)
|
||||
@ -143,7 +144,9 @@ namespace ix
|
||||
|
||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
||||
{
|
||||
return WebSocketInitResult(false, 0, std::string("Could not parse URL ") + url);
|
||||
std::stringstream ss;
|
||||
ss << "Could not parse url: '" << url << "'";
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
std::string errorMsg;
|
||||
@ -178,6 +181,7 @@ namespace ix
|
||||
|
||||
// Server should not mask the data it sends to the client
|
||||
_useMask = false;
|
||||
_blockingSend = true;
|
||||
|
||||
_socket = socket;
|
||||
|
||||
@ -339,48 +343,16 @@ namespace ix
|
||||
// there can be a lot of it for large messages.
|
||||
if (pollResult == PollResultType::SendRequest)
|
||||
{
|
||||
while (!isSendBufferEmpty() && !_requestInitCancellation)
|
||||
if (!flushSendBuffer())
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
return PollResult::CannotFlushSendBuffer;
|
||||
}
|
||||
}
|
||||
else if (pollResult == PollResultType::ReadyForRead)
|
||||
{
|
||||
while (true)
|
||||
if (!receiveFromSocket())
|
||||
{
|
||||
ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size());
|
||||
|
||||
if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
break;
|
||||
}
|
||||
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)
|
||||
|
||||
closeSocket();
|
||||
|
||||
return PollResult::AbnormalClose;
|
||||
}
|
||||
else
|
||||
{
|
||||
_rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret);
|
||||
}
|
||||
return PollResult::AbnormalClose;
|
||||
}
|
||||
}
|
||||
else if (pollResult == PollResultType::Error)
|
||||
@ -543,7 +515,7 @@ namespace ix
|
||||
}
|
||||
|
||||
// Prevent integer overflow in the next conditional
|
||||
const uint64_t maxFrameSize(1 << 63);
|
||||
const uint64_t maxFrameSize(1ULL << 63);
|
||||
if (ws.N > maxFrameSize)
|
||||
{
|
||||
return;
|
||||
@ -748,7 +720,7 @@ namespace ix
|
||||
|
||||
// if an abnormal closure was raised in poll, and nothing else triggered a CLOSED state in
|
||||
// the received and processed data then close the connection
|
||||
if (pollResult == PollResult::AbnormalClose)
|
||||
if (pollResult != PollResult::Succeeded)
|
||||
{
|
||||
_rxbuf.clear();
|
||||
|
||||
@ -874,10 +846,12 @@ namespace ix
|
||||
_txbuf.reserve(wireSize);
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
// Common case for most message. No fragmentation required.
|
||||
if (wireSize < kChunkSize)
|
||||
{
|
||||
sendFragment(type, true, message_begin, message_end, compress);
|
||||
success = sendFragment(type, true, message_begin, message_end, compress);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -913,7 +887,10 @@ namespace ix
|
||||
}
|
||||
|
||||
// Send message
|
||||
sendFragment(opcodeType, fin, begin, end, compress);
|
||||
if (!sendFragment(opcodeType, fin, begin, end, compress))
|
||||
{
|
||||
return WebSocketSendInfo(false);
|
||||
}
|
||||
|
||||
if (onProgressCallback && !onProgressCallback((int) i, (int) steps))
|
||||
{
|
||||
@ -928,12 +905,18 @@ namespace ix
|
||||
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(true, compressionError, payloadSize, wireSize);
|
||||
return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
|
||||
}
|
||||
|
||||
void WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
|
||||
bool WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
|
||||
bool fin,
|
||||
std::string::const_iterator message_begin,
|
||||
std::string::const_iterator message_end,
|
||||
@ -1018,7 +1001,7 @@ namespace ix
|
||||
appendToSendBuffer(header, message_begin, message_end, message_size, masking_key);
|
||||
|
||||
// Now actually send this data
|
||||
sendOnSocket();
|
||||
return sendOnSocket();
|
||||
}
|
||||
|
||||
WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
|
||||
@ -1051,19 +1034,17 @@ namespace ix
|
||||
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());
|
||||
}
|
||||
|
||||
void WebSocketTransport::sendOnSocket()
|
||||
bool WebSocketTransport::sendOnSocket()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||
|
||||
while (_txbuf.size())
|
||||
{
|
||||
ssize_t ret = send();
|
||||
ssize_t ret = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
ret = _socket->send((char*) &_txbuf[0], _txbuf.size());
|
||||
}
|
||||
|
||||
if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
@ -1073,13 +1054,43 @@ namespace ix
|
||||
{
|
||||
closeSocket();
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebSocketTransport::receiveFromSocket()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ssize_t ret = _socket->recv((char*) &_readbuf[0], _readbuf.size());
|
||||
|
||||
if (ret < 0 && Socket::isWaitNeeded())
|
||||
{
|
||||
break;
|
||||
}
|
||||
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)
|
||||
|
||||
closeSocket();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_rxbuf.insert(_rxbuf.end(), _readbuf.begin(), _readbuf.begin() + ret);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketTransport::sendCloseFrame(uint16_t code, const std::string& reason)
|
||||
@ -1168,4 +1179,30 @@ 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)
|
||||
{
|
||||
if (!sendOnSocket())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ix
|
||||
|
@ -61,7 +61,8 @@ namespace ix
|
||||
enum class PollResult
|
||||
{
|
||||
Succeeded,
|
||||
AbnormalClose
|
||||
AbnormalClose,
|
||||
CannotFlushSendBuffer
|
||||
};
|
||||
|
||||
using OnMessageCallback =
|
||||
@ -98,7 +99,6 @@ namespace ix
|
||||
bool remote = false);
|
||||
|
||||
void closeSocket();
|
||||
ssize_t send();
|
||||
|
||||
ReadyState getReadyState() const;
|
||||
void setReadyState(ReadyState readyState);
|
||||
@ -135,6 +135,10 @@ 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;
|
||||
|
||||
@ -238,13 +242,16 @@ namespace ix
|
||||
size_t closeWireSize,
|
||||
bool remote);
|
||||
|
||||
void sendOnSocket();
|
||||
bool flushSendBuffer();
|
||||
bool sendOnSocket();
|
||||
bool receiveFromSocket();
|
||||
|
||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
||||
const std::string& message,
|
||||
bool compress,
|
||||
const OnProgressCallback& onProgressCallback = nullptr);
|
||||
|
||||
void sendFragment(wsheader_type::opcode_type type,
|
||||
bool sendFragment(wsheader_type::opcode_type type,
|
||||
bool fin,
|
||||
std::string::const_iterator begin,
|
||||
std::string::const_iterator end,
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "7.3.1"
|
||||
#define IX_WEBSOCKET_VERSION "8.0.0"
|
||||
|
10
makefile
10
makefile
@ -30,7 +30,7 @@ uninstall:
|
||||
xargs rm -fv < build/install_manifest.txt
|
||||
|
||||
tag:
|
||||
git tag v"`cat DOCKER_VERSION`"
|
||||
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
|
||||
@ -40,12 +40,15 @@ xcode_openssl:
|
||||
|
||||
.PHONY: docker
|
||||
|
||||
NAME := bsergean/ws
|
||||
TAG := $(shell cat DOCKER_VERSION)
|
||||
NAME := ${DOCKER_REPO}/ws
|
||||
TAG := $(shell sh tools/extract_version.sh)
|
||||
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 .
|
||||
|
||||
@ -57,6 +60,7 @@ docker:
|
||||
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
|
||||
|
@ -23,13 +23,22 @@ include_directories(
|
||||
../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}
|
||||
|
||||
test_runner.cpp
|
||||
IXTest.cpp
|
||||
IXGetFreePort.cpp
|
||||
../third_party/msgpack11/msgpack11.cpp
|
||||
../third_party/jsoncpp/jsoncpp.cpp
|
||||
|
||||
IXSocketTest.cpp
|
||||
IXSocketConnectTest.cpp
|
||||
@ -46,12 +55,12 @@ set (SOURCES
|
||||
IXDNSLookupTest.cpp
|
||||
IXWebSocketSubProtocolTest.cpp
|
||||
IXSentryClientTest.cpp
|
||||
IXWebSocketChatTest.cpp
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
if (UNIX)
|
||||
list(APPEND SOURCES
|
||||
IXWebSocketChatTest.cpp
|
||||
IXWebSocketCloseTest.cpp
|
||||
)
|
||||
endif()
|
||||
@ -79,6 +88,11 @@ if (APPLE AND USE_TLS)
|
||||
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)
|
||||
@ -86,4 +100,6 @@ 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)
|
||||
|
@ -34,12 +34,10 @@ namespace
|
||||
});
|
||||
}
|
||||
|
||||
class SatoriChat
|
||||
class CobraChat
|
||||
{
|
||||
public:
|
||||
SatoriChat(const std::string& user,
|
||||
const std::string& session,
|
||||
const std::string& endpoint);
|
||||
CobraChat(const std::string& user, const std::string& session, const std::string& endpoint);
|
||||
|
||||
void subscribe(const std::string& channel);
|
||||
void start();
|
||||
@ -72,9 +70,9 @@ namespace
|
||||
std::mutex _logMutex;
|
||||
};
|
||||
|
||||
SatoriChat::SatoriChat(const std::string& user,
|
||||
const std::string& session,
|
||||
const std::string& endpoint)
|
||||
CobraChat::CobraChat(const std::string& user,
|
||||
const std::string& session,
|
||||
const std::string& endpoint)
|
||||
: _user(user)
|
||||
, _session(session)
|
||||
, _endpoint(endpoint)
|
||||
@ -83,34 +81,34 @@ namespace
|
||||
{
|
||||
}
|
||||
|
||||
void SatoriChat::start()
|
||||
void CobraChat::start()
|
||||
{
|
||||
_thread = std::thread(&SatoriChat::run, this);
|
||||
_thread = std::thread(&CobraChat::run, this);
|
||||
}
|
||||
|
||||
void SatoriChat::stop()
|
||||
void CobraChat::stop()
|
||||
{
|
||||
_stop = true;
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
bool SatoriChat::isReady() const
|
||||
bool CobraChat::isReady() const
|
||||
{
|
||||
return _connectedAndSubscribed;
|
||||
}
|
||||
|
||||
size_t SatoriChat::getReceivedMessagesCount() const
|
||||
size_t CobraChat::getReceivedMessagesCount() const
|
||||
{
|
||||
return _receivedQueue.size();
|
||||
}
|
||||
|
||||
bool SatoriChat::hasPendingMessages() const
|
||||
bool CobraChat::hasPendingMessages() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_queue_mutex);
|
||||
return !_publish_queue.empty();
|
||||
}
|
||||
|
||||
Json::Value SatoriChat::popMessage()
|
||||
Json::Value CobraChat::popMessage()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_queue_mutex);
|
||||
auto msg = _publish_queue.front();
|
||||
@ -121,7 +119,7 @@ namespace
|
||||
//
|
||||
// Callback to handle received messages, that are printed on the console
|
||||
//
|
||||
void SatoriChat::subscribe(const std::string& channel)
|
||||
void CobraChat::subscribe(const std::string& channel)
|
||||
{
|
||||
std::string filter;
|
||||
_conn.subscribe(channel, filter, [this](const Json::Value& msg) {
|
||||
@ -151,7 +149,7 @@ namespace
|
||||
});
|
||||
}
|
||||
|
||||
void SatoriChat::sendMessage(const std::string& text)
|
||||
void CobraChat::sendMessage(const std::string& text)
|
||||
{
|
||||
Json::Value msg;
|
||||
msg["user"] = _user;
|
||||
@ -166,16 +164,21 @@ namespace
|
||||
// Do satori communication on a background thread, where we can have
|
||||
// something like an event loop that publish, poll and receive data
|
||||
//
|
||||
void SatoriChat::run()
|
||||
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));
|
||||
_conn.configure(appkey,
|
||||
_endpoint,
|
||||
role,
|
||||
secret,
|
||||
ix::WebSocketPerMessageDeflateOptions(true),
|
||||
socketTLSOptions);
|
||||
_conn.connect();
|
||||
|
||||
_conn.setEventCallback([this, channel](ix::CobraConnectionEventType eventType,
|
||||
@ -280,8 +283,8 @@ TEST_CASE("Cobra_chat", "[cobra_chat]")
|
||||
ss << "ws://localhost:" << port;
|
||||
std::string endpoint = ss.str();
|
||||
|
||||
SatoriChat chatA("jean", session, endpoint);
|
||||
SatoriChat chatB("paul", session, endpoint);
|
||||
CobraChat chatA("jean", session, endpoint);
|
||||
CobraChat chatB("paul", session, endpoint);
|
||||
|
||||
chatA.start();
|
||||
chatB.start();
|
||||
|
@ -62,11 +62,14 @@ namespace
|
||||
gMessageCount = 0;
|
||||
|
||||
ix::CobraConnection conn;
|
||||
SocketTLSOptions socketTLSOptions;
|
||||
|
||||
conn.configure(APPKEY,
|
||||
endpoint,
|
||||
SUBSCRIBER_ROLE,
|
||||
SUBSCRIBER_SECRET,
|
||||
ix::WebSocketPerMessageDeflateOptions(true));
|
||||
ix::WebSocketPerMessageDeflateOptions(true),
|
||||
socketTLSOptions);
|
||||
conn.connect();
|
||||
|
||||
conn.setEventCallback([&conn](ix::CobraConnectionEventType eventType,
|
||||
@ -202,9 +205,15 @@ TEST_CASE("Cobra_Metrics_Publisher", "[cobra]")
|
||||
|
||||
ix::CobraMetricsPublisher cobraMetricsPublisher;
|
||||
|
||||
SocketTLSOptions socketTLSOptions;
|
||||
bool perMessageDeflate = true;
|
||||
cobraMetricsPublisher.configure(
|
||||
APPKEY, endpoint, CHANNEL, PUBLISHER_ROLE, PUBLISHER_SECRET, perMessageDeflate);
|
||||
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
|
||||
|
||||
|
26
test/compatibility/C/uWebSockets/.github/workflows/cpp.yml
vendored
Normal file
26
test/compatibility/C/uWebSockets/.github/workflows/cpp.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: C++ CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build_linux:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone source
|
||||
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
|
||||
- name: Build source
|
||||
run: make -C uWebSockets
|
||||
|
||||
|
||||
build_osx:
|
||||
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Clone source
|
||||
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
|
||||
- name: Build source
|
||||
run: make -C uWebSockets
|
||||
|
3
test/compatibility/C/uWebSockets/.gitmodules
vendored
Normal file
3
test/compatibility/C/uWebSockets/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "uSockets"]
|
||||
path = uSockets
|
||||
url = https://github.com/uNetworking/uSockets.git
|
201
test/compatibility/C/uWebSockets/LICENSE
Normal file
201
test/compatibility/C/uWebSockets/LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
40
test/compatibility/C/uWebSockets/Makefile
Normal file
40
test/compatibility/C/uWebSockets/Makefile
Normal file
@ -0,0 +1,40 @@
|
||||
EXAMPLE_FILES := HelloWorld EchoServer BroadcastingEchoServer
|
||||
THREADED_EXAMPLE_FILES := HelloWorldThreaded EchoServerThreaded
|
||||
override CXXFLAGS += -lpthread -Wconversion -std=c++17 -Isrc -IuSockets/src
|
||||
override LDFLAGS += uSockets/*.o -lz
|
||||
|
||||
# WITH_OPENSSL=1 enables OpenSSL 1.1+ support
|
||||
ifeq ($(WITH_OPENSSL),1)
|
||||
# With problems on macOS, make sure to pass needed LDFLAGS required to find these
|
||||
override LDFLAGS += -lssl -lcrypto
|
||||
else
|
||||
# WITH_WOLFSSL=1 enables WolfSSL 4.2.0 support (mutually exclusive with OpenSSL)
|
||||
ifeq ($(WITH_WOLFSSL),1)
|
||||
override LDFLAGS += -L/usr/local/lib -lwolfssl
|
||||
endif
|
||||
endif
|
||||
|
||||
# WITH_LIBUV=1 builds with libuv as event-loop
|
||||
ifeq ($(WITH_LIBUV),1)
|
||||
override LDFLAGS += -luv
|
||||
endif
|
||||
|
||||
# WITH_ASAN builds with sanitizers
|
||||
ifeq ($(WITH_ASAN),1)
|
||||
override CXXFLAGS += -fsanitize=address
|
||||
override LDFLAGS += -lasan
|
||||
endif
|
||||
|
||||
.PHONY: examples
|
||||
examples:
|
||||
cd uSockets && make
|
||||
$(foreach FILE,$(EXAMPLE_FILES),$(CXX) -flto -O3 $(CXXFLAGS) examples/$(FILE).cpp -o $(FILE) $(LDFLAGS);)
|
||||
$(foreach FILE,$(THREADED_EXAMPLE_FILES),$(CXX) -pthread -flto -O3 $(CXXFLAGS) examples/$(FILE).cpp -o $(FILE) $(LDFLAGS);)
|
||||
|
||||
all:
|
||||
make examples
|
||||
make -C fuzzing
|
||||
make -C benchmarks
|
||||
clean:
|
||||
rm -rf $(EXAMPLE_FILES) $(THREADED_EXAMPLE_FILES)
|
||||
rm -rf fuzzing/*.o benchmarks/*.o
|
65
test/compatibility/C/uWebSockets/README.md
Normal file
65
test/compatibility/C/uWebSockets/README.md
Normal file
@ -0,0 +1,65 @@
|
||||
<div align="center">
|
||||
<img src="misc/logo.svg" height="180" />
|
||||
|
||||
*µWebSockets™ (it's "[micro](https://en.wikipedia.org/wiki/Micro-)") is simple, secure*<sup>[[1]](fuzzing)</sup> *& standards compliant*<sup>[[2]](https://unetworking.github.io/uWebSockets.js/report.pdf)</sup> *web I/O for the most demanding*<sup>[[3]](benchmarks)</sup> *of applications.*
|
||||
|
||||
• [Read more](misc/READMORE.md) • [Read about uSockets](https://github.com/uNetworking/uSockets) • [See uWebSockets.js](https://github.com/uNetworking/uWebSockets.js)
|
||||
|
||||
*© 2016-2019, >39,632,272 downloads*
|
||||
|
||||
</div>
|
||||
|
||||
#### Express yourself briefly.
|
||||
```c++
|
||||
uWS::SSLApp({
|
||||
|
||||
/* There are tons of SSL options */
|
||||
.cert_file_name = "cert.pem",
|
||||
.key_file_name = "key.pem"
|
||||
|
||||
}).get("/hello", [](auto *res, auto *req) {
|
||||
|
||||
/* You can efficiently stream huge files too */
|
||||
res->writeHeader("Content-Type", "text/html; charset=utf-8")->end("Hello HTTP!");
|
||||
|
||||
}).ws<UserData>("/*", {
|
||||
|
||||
/* Just a few of the available handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
ws->subscribe("buzzword weekly");
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
ws->send(message, opCode);
|
||||
}
|
||||
|
||||
}).listen(9001, [](auto *token) {
|
||||
|
||||
if (token) {
|
||||
std::cout << "Listening on port " << 9001 << std::endl;
|
||||
}
|
||||
|
||||
}).run();
|
||||
```
|
||||
Don't miss the [user manual](https://github.com/uNetworking/uWebSockets/blob/master/misc/READMORE.md#user-manual), the [C++ examples](https://github.com/uNetworking/uWebSockets/tree/master/examples) or the [JavaScript examples](https://github.com/uNetworking/uWebSockets.js/tree/master/examples). JavaScript examples are very applicable to C++ developers, so go through them as well.
|
||||
|
||||
#### Pay what you want.
|
||||
A free & open source ([permissive](LICENSE)) project since 2016. Kindly sponsored by [BitMEX](https://bitmex.com), [Bitfinex](https://bitfinex.com) & [Coinbase](https://www.coinbase.com/) in 2018 and/or 2019. Individual donations are always accepted via [PayPal](https://paypal.me/uwebsockets).
|
||||
|
||||
<div align="center"><img src="misc/2018.png"/></div>
|
||||
|
||||
*Code is provided as-is, do not expect or demand **free** consulting services, personal tutoring, advice or debugging.*
|
||||
|
||||
#### Deploy like a boss.
|
||||
Commercial support is available via a per-hourly consulting plan or as otherwise negotiated. If you're stuck, worried about design or just in need of help don't hesitate throwing [me, the author](https://github.com/alexhultman) a mail and we'll figure out what's best for both parties. I want your business to have a proper understanding of the problem before rushing in to one of the many pitfalls.
|
||||
|
||||
#### Excel across the board.
|
||||
All that glitters is not gold. Especially so in a market driven by flashy logos, hype and pointless badges.
|
||||
|
||||
Http | WebSockets
|
||||
--- | ---
|
||||
 | 
|
||||
|
||||
#### Keep it legal.
|
||||
Intellectual property, all rights reserved.
|
||||
|
||||
*You are forbidden to use logos, product names, texts, names or otherwise perceived brand identity, of copyright holder, in any way that might state or imply that the copyright holder endorses your distribution or in any way that might state or imply that you created the original software. Modified distributions must carry, from the original distribution, significantly different names and must not be confused with the original distribution.*
|
4
test/compatibility/C/uWebSockets/benchmarks/Makefile
Normal file
4
test/compatibility/C/uWebSockets/benchmarks/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
default:
|
||||
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c broadcast_test.c -o broadcast_test -lssl -lcrypto
|
||||
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c load_test.c -o load_test -lssl -lcrypto
|
||||
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c scale_test.c -o scale_test -lssl -lcrypto
|
17
test/compatibility/C/uWebSockets/benchmarks/README.md
Normal file
17
test/compatibility/C/uWebSockets/benchmarks/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Benchmark-driven development
|
||||
|
||||
Just like testing code for correctness and stability, testing for performance is just as important if performance is a goal. You cannot really argue or reason about performance without having tests for it.
|
||||
|
||||
* Do not trust anyone who claims performance of any kind unless they provide benchmarks. Do not listen to people who talk about performance without having actual scientific data to back their claims up.
|
||||
* Never accept absolute numbers without a direct comparison with an alternative solution. Many projects can give you a number, X, which can be "50 billion messages per second". How much is this? What kind of worth does this number have? Impossible to know without a comparison. Absolute numbers mean nothing, relative comparisons are what you should look for.
|
||||
* Make sure to benchmark the correct thing. This is an extremely common mistake, done by many of the most well-known developers out there. If you measure for CPU-time efficiency (which you do) then normalizing for spent CPU-time is the difference between a completely invalid, botched and bogus test and something that might be valid.
|
||||
|
||||
Here are the current relative comparisons:
|
||||
|
||||
Http | WebSockets
|
||||
--- | ---
|
||||
 | 
|
||||
|
||||
Over the period of a few years I have never come across any web server which can score as high as µWebSockets do. This is not to say that µWebSockets is fastest, as "fastest" is a silly superlative nobody should *ever* use.
|
||||
|
||||
Never trust anyone using superlatives to describe their work; they are more often wrong than right.
|
196
test/compatibility/C/uWebSockets/benchmarks/broadcast_test.c
Normal file
196
test/compatibility/C/uWebSockets/benchmarks/broadcast_test.c
Normal file
@ -0,0 +1,196 @@
|
||||
/* This benchmark establishes _connections_ number of WebSocket
|
||||
clients, then iteratively performs the following:
|
||||
|
||||
1. Send one message for every client.
|
||||
2. Wait for the quadratic (_connections_^2) amount of responses from the server.
|
||||
3. Once received all expected bytes, repeat by going to step 1.
|
||||
|
||||
Every 4 seconds we print the current average "iterations per second".
|
||||
*/
|
||||
|
||||
#include <libusockets.h>
|
||||
int SSL;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
|
||||
|
||||
char request[] = "GET / HTTP/1.1\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
|
||||
"Host: server.example.com\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n";
|
||||
char *host;
|
||||
int port;
|
||||
int connections;
|
||||
|
||||
int satisfied_sockets;
|
||||
int iterations;
|
||||
|
||||
struct http_socket {
|
||||
/* How far we have streamed our websocket request */
|
||||
int offset;
|
||||
|
||||
/* How far we have streamed our upgrade request */
|
||||
int upgrade_offset;
|
||||
|
||||
/* Are we upgraded? */
|
||||
int is_upgraded;
|
||||
|
||||
/* Bytes received */
|
||||
int bytes_received;
|
||||
};
|
||||
|
||||
/* We track upgraded websockets */
|
||||
void **web_sockets;
|
||||
int num_web_sockets;
|
||||
|
||||
/* We don't need any of these */
|
||||
void noop(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
void start_iteration() {
|
||||
for (int i = 0; i < num_web_sockets; i++) {
|
||||
struct us_socket_t *s = (struct us_socket_t *) web_sockets[i];
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void next_connection(struct us_socket_t *s) {
|
||||
/* Add this connection to our array */
|
||||
web_sockets[num_web_sockets++] = s;
|
||||
|
||||
/* We could wait with this until properly upgraded */
|
||||
if (--connections) {
|
||||
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, 0, sizeof(struct http_socket));
|
||||
} else {
|
||||
printf("Running benchmark now...\n");
|
||||
start_iteration();
|
||||
|
||||
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
|
||||
}
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Are we still not upgraded yet? */
|
||||
if (http_socket->upgrade_offset < sizeof(request) - 1) {
|
||||
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
|
||||
} else {
|
||||
/* Stream whatever is remaining of the request */
|
||||
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
|
||||
|
||||
printf("Client was disconnected, exiting!\n");
|
||||
exit(-1);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
|
||||
return us_socket_close(SSL, s);
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
|
||||
/* Get socket extension and the socket's context's extension */
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Are we already upgraded? */
|
||||
if (http_socket->is_upgraded) {
|
||||
http_socket->bytes_received += length;
|
||||
|
||||
if (http_socket->bytes_received == (sizeof(web_socket_request) - 4) * num_web_sockets) {
|
||||
satisfied_sockets++;
|
||||
http_socket->bytes_received = 0;
|
||||
|
||||
if (satisfied_sockets == num_web_sockets) {
|
||||
iterations++;
|
||||
satisfied_sockets = 0;
|
||||
|
||||
start_iteration();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* We assume the server is not sending anything immediately following upgrade and that we get rnrn in one chunk */
|
||||
if (length >= 4 && data[length - 1] == '\n' && data[length - 2] == '\r' && data[length - 3] == '\n' && data[length - 4] == '\r') {
|
||||
http_socket->is_upgraded = 1;
|
||||
next_connection(s);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Reset offsets */
|
||||
http_socket->offset = 0;
|
||||
http_socket->is_upgraded = 0;
|
||||
http_socket->bytes_received = 0;
|
||||
|
||||
/* Send an upgrade request */
|
||||
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
|
||||
/* Print current statistics */
|
||||
printf("Iterations/second (%d clients): %f\n", num_web_sockets, ((float)iterations) / LIBUS_TIMEOUT_GRANULARITY);
|
||||
|
||||
iterations = 0;
|
||||
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
/* Parse host and port */
|
||||
if (argc != 5) {
|
||||
printf("Usage: connections host port ssl\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
port = atoi(argv[3]);
|
||||
host = malloc(strlen(argv[2]) + 1);
|
||||
memcpy(host, argv[2], strlen(argv[2]) + 1);
|
||||
connections = atoi(argv[1]);
|
||||
SSL = atoi(argv[4]);
|
||||
|
||||
/* Allocate room for every socket */
|
||||
web_sockets = (void **) malloc(sizeof(void *) * connections);
|
||||
|
||||
/* Create the event loop */
|
||||
struct us_loop_t *loop = us_create_loop(0, noop, noop, noop, 0);
|
||||
|
||||
/* Create a socket context for HTTP */
|
||||
struct us_socket_context_options_t options = {};
|
||||
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
|
||||
|
||||
/* Set up event handlers */
|
||||
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
|
||||
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
|
||||
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
|
||||
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
|
||||
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
|
||||
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
|
||||
|
||||
/* Start making HTTP connections */
|
||||
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
|
||||
|
||||
us_loop_run(loop);
|
||||
}
|
161
test/compatibility/C/uWebSockets/benchmarks/load_test.c
Normal file
161
test/compatibility/C/uWebSockets/benchmarks/load_test.c
Normal file
@ -0,0 +1,161 @@
|
||||
/* This is a simple yet efficient WebSocket server benchmark much like WRK */
|
||||
|
||||
#include <libusockets.h>
|
||||
int SSL;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// request eller upgradeRequest samt webSocketFrame
|
||||
|
||||
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
|
||||
|
||||
char request[] = "GET / HTTP/1.1\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
|
||||
"Host: server.example.com\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n";
|
||||
char *host;
|
||||
int port;
|
||||
int connections;
|
||||
|
||||
int responses;
|
||||
|
||||
struct http_socket {
|
||||
/* How far we have streamed our websocket request */
|
||||
int offset;
|
||||
|
||||
/* How far we have streamed our upgrade request */
|
||||
int upgrade_offset;
|
||||
};
|
||||
|
||||
/* We don't need any of these */
|
||||
void on_wakeup(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
void on_pre(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
|
||||
void on_post(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
void next_connection(struct us_socket_t *s) {
|
||||
/* We could wait with this until properly upgraded */
|
||||
if (--connections) {
|
||||
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, 0, sizeof(struct http_socket));
|
||||
} else {
|
||||
printf("Running benchmark now...\n");
|
||||
|
||||
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
|
||||
}
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Are we still not upgraded yet? */
|
||||
if (http_socket->upgrade_offset < sizeof(request) - 1) {
|
||||
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
|
||||
|
||||
/* Now we should be */
|
||||
if (http_socket->upgrade_offset == sizeof(request) - 1) {
|
||||
next_connection(s);
|
||||
}
|
||||
} else {
|
||||
/* Stream whatever is remaining of the request */
|
||||
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
|
||||
|
||||
printf("Closed!\n");
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
|
||||
return us_socket_close(SSL, s);
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
|
||||
/* Get socket extension and the socket's context's extension */
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
//struct http_context *http_context = (struct http_context *) us_socket_context_ext(SSL, us_socket_context(SSL, s));
|
||||
|
||||
/* We treat all data events as a response */
|
||||
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
|
||||
|
||||
/* */
|
||||
responses++;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Reset offsets */
|
||||
http_socket->offset = 0;
|
||||
|
||||
/* Send an upgrade request */
|
||||
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
|
||||
if (http_socket->upgrade_offset == sizeof(request) - 1) {
|
||||
next_connection(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
|
||||
/* Print current statistics */
|
||||
printf("Msg/sec: %f\n", ((float)responses) / LIBUS_TIMEOUT_GRANULARITY);
|
||||
|
||||
responses = 0;
|
||||
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
/* Parse host and port */
|
||||
if (argc != 5) {
|
||||
printf("Usage: connections host port ssl\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
port = atoi(argv[3]);
|
||||
host = malloc(strlen(argv[2]) + 1);
|
||||
memcpy(host, argv[2], strlen(argv[2]) + 1);
|
||||
connections = atoi(argv[1]);
|
||||
SSL = atoi(argv[4]);
|
||||
|
||||
/* Create the event loop */
|
||||
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
|
||||
|
||||
/* Create a socket context for HTTP */
|
||||
struct us_socket_context_options_t options = {};
|
||||
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
|
||||
|
||||
/* Set up event handlers */
|
||||
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
|
||||
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
|
||||
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
|
||||
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
|
||||
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
|
||||
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
|
||||
|
||||
/* Start making HTTP connections */
|
||||
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
|
||||
|
||||
us_loop_run(loop);
|
||||
}
|
185
test/compatibility/C/uWebSockets/benchmarks/scale_test.c
Normal file
185
test/compatibility/C/uWebSockets/benchmarks/scale_test.c
Normal file
@ -0,0 +1,185 @@
|
||||
/* This is a scalability test for testing million(s) of pinging connections */
|
||||
|
||||
#include <libusockets.h>
|
||||
int SSL;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
|
||||
|
||||
char request[] = "GET / HTTP/1.1\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
|
||||
"Host: server.example.com\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n";
|
||||
char *host;
|
||||
int port;
|
||||
int connections;
|
||||
|
||||
/* Send ping every 16 seconds */
|
||||
int WEBSOCKET_PING_INTERVAL = 16;
|
||||
|
||||
/* We only establish 20k connections per address */
|
||||
int CONNECTIONS_PER_ADDRESS = 20000;
|
||||
|
||||
/* How many connections a time */
|
||||
int BATCH_CONNECT = 1;
|
||||
|
||||
/* Currently open and alive connections */
|
||||
int opened_connections;
|
||||
/* Dead connections */
|
||||
int closed_connections;
|
||||
|
||||
struct http_socket {
|
||||
/* How far we have streamed our websocket request */
|
||||
int offset;
|
||||
|
||||
/* How far we have streamed our upgrade request */
|
||||
int upgrade_offset;
|
||||
};
|
||||
|
||||
/* We don't need any of these */
|
||||
void on_wakeup(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
void on_pre(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
|
||||
void on_post(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
void next_connection(struct us_socket_t *s) {
|
||||
/* We could wait with this until properly upgraded */
|
||||
if (--connections/* > BATCH_CONNECT*/) {
|
||||
/* Swap address */
|
||||
int address = opened_connections / CONNECTIONS_PER_ADDRESS + 1;
|
||||
char buf[16];
|
||||
sprintf(buf, "127.0.0.%d", address);
|
||||
|
||||
us_socket_context_connect(SSL, us_socket_context(SSL, s), buf, port, 0, sizeof(struct http_socket));
|
||||
}
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Are we still not upgraded yet? */
|
||||
if (http_socket->upgrade_offset < sizeof(request) - 1) {
|
||||
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
|
||||
|
||||
/* Now we should be */
|
||||
if (http_socket->upgrade_offset == sizeof(request) - 1) {
|
||||
next_connection(s);
|
||||
|
||||
/* Make sure to send ping */
|
||||
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
|
||||
}
|
||||
} else {
|
||||
/* Stream whatever is remaining of the request */
|
||||
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
|
||||
if (http_socket->offset == sizeof(web_socket_request)) {
|
||||
/* Reset timeout if we managed to */
|
||||
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_close(struct us_socket_t *s) {
|
||||
|
||||
closed_connections++;
|
||||
if (closed_connections % 1000 == 0) {
|
||||
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
|
||||
return us_socket_close(SSL, s);
|
||||
}
|
||||
|
||||
// should never get a response!
|
||||
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
|
||||
return s;
|
||||
}
|
||||
|
||||
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Display number of opened connections */
|
||||
opened_connections++;
|
||||
if (opened_connections % 1000 == 0) {
|
||||
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
|
||||
}
|
||||
|
||||
/* Send an upgrade request */
|
||||
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
|
||||
if (http_socket->upgrade_offset == sizeof(request) - 1) {
|
||||
next_connection(s);
|
||||
|
||||
/* Make sure to send ping */
|
||||
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// here we should send a message as ping (part of the test)
|
||||
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
|
||||
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
|
||||
|
||||
/* Send ping here */
|
||||
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
|
||||
if (http_socket->offset == sizeof(web_socket_request)) {
|
||||
/* Reset timeout if we managed to */
|
||||
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
/* Parse host and port */
|
||||
if (argc != 5) {
|
||||
printf("Usage: connections host port ssl\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
port = atoi(argv[3]);
|
||||
host = malloc(strlen(argv[2]) + 1);
|
||||
memcpy(host, argv[2], strlen(argv[2]) + 1);
|
||||
connections = atoi(argv[1]);
|
||||
SSL = atoi(argv[4]);
|
||||
|
||||
/* Create the event loop */
|
||||
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
|
||||
|
||||
/* Create a socket context for HTTP */
|
||||
struct us_socket_context_options_t options = {};
|
||||
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
|
||||
|
||||
/* Set up event handlers */
|
||||
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
|
||||
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
|
||||
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
|
||||
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
|
||||
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
|
||||
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
|
||||
|
||||
/* Start making HTTP connections */
|
||||
for (int i = 0; i < BATCH_CONNECT; i++) {
|
||||
us_socket_context_connect(SSL, http_context, host, port, 0, sizeof(struct http_socket));
|
||||
}
|
||||
|
||||
us_loop_run(loop);
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
#include "App.h"
|
||||
|
||||
struct us_listen_socket_t *listen_socket;
|
||||
|
||||
int main() {
|
||||
/* ws->getUserData returns one of these */
|
||||
struct PerSocketData {
|
||||
|
||||
};
|
||||
|
||||
/* Very simple WebSocket broadcasting echo server */
|
||||
uWS::App().ws<PerSocketData>("/*", {
|
||||
/* Settings */
|
||||
.compression = uWS::SHARED_COMPRESSOR,
|
||||
.maxPayloadLength = 16 * 1024 * 1024,
|
||||
.idleTimeout = 10,
|
||||
.maxBackpressure = 1 * 1024 * 1204,
|
||||
/* Handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
/* Let's make every connection subscribe to the "broadcast" topic */
|
||||
ws->subscribe("broadcast");
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
/* Exit gracefully if we get a closedown message (ASAN debug) */
|
||||
if (message == "closedown") {
|
||||
/* Bye bye */
|
||||
us_listen_socket_close(0, listen_socket);
|
||||
ws->close();
|
||||
}
|
||||
|
||||
/* Simply broadcast every single message we get */
|
||||
ws->publish("broadcast", message, opCode);
|
||||
},
|
||||
.drain = [](auto *ws) {
|
||||
/* Check getBufferedAmount here */
|
||||
},
|
||||
.ping = [](auto *ws) {
|
||||
|
||||
},
|
||||
.pong = [](auto *ws) {
|
||||
|
||||
},
|
||||
.close = [](auto *ws, int code, std::string_view message) {
|
||||
/* We automatically unsubscribe from any topic here */
|
||||
}
|
||||
}).listen(9001, [](auto *token) {
|
||||
listen_socket = token;
|
||||
if (token) {
|
||||
std::cout << "Listening on port " << 9001 << std::endl;
|
||||
}
|
||||
}).run();
|
||||
}
|
50
test/compatibility/C/uWebSockets/examples/EchoServer.cpp
Normal file
50
test/compatibility/C/uWebSockets/examples/EchoServer.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
/* We simply call the root header file "App.h", giving you uWS::App and uWS::SSLApp */
|
||||
#include "App.h"
|
||||
|
||||
/* This is a simple WebSocket echo server example.
|
||||
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
|
||||
|
||||
int main() {
|
||||
/* ws->getUserData returns one of these */
|
||||
struct PerSocketData {
|
||||
/* Fill with user data */
|
||||
};
|
||||
|
||||
/* Keep in mind that uWS::SSLApp({options}) is the same as uWS::App() when compiled without SSL support.
|
||||
* You may swap to using uWS:App() if you don't need SSL */
|
||||
uWS::SSLApp({
|
||||
/* There are example certificates in uWebSockets.js repo */
|
||||
.key_file_name = "../misc/key.pem",
|
||||
.cert_file_name = "../misc/cert.pem",
|
||||
.passphrase = "1234"
|
||||
}).ws<PerSocketData>("/*", {
|
||||
/* Settings */
|
||||
.compression = uWS::SHARED_COMPRESSOR,
|
||||
.maxPayloadLength = 16 * 1024,
|
||||
.idleTimeout = 10,
|
||||
.maxBackpressure = 1 * 1024 * 1204,
|
||||
/* Handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
/* Open event here, you may access ws->getUserData() which points to a PerSocketData struct */
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
ws->send(message, opCode);
|
||||
},
|
||||
.drain = [](auto *ws) {
|
||||
/* Check ws->getBufferedAmount() here */
|
||||
},
|
||||
.ping = [](auto *ws) {
|
||||
/* Not implemented yet */
|
||||
},
|
||||
.pong = [](auto *ws) {
|
||||
/* Not implemented yet */
|
||||
},
|
||||
.close = [](auto *ws, int code, std::string_view message) {
|
||||
/* You may access ws->getUserData() here */
|
||||
}
|
||||
}).listen(9001, [](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Listening on port " << 9001 << std::endl;
|
||||
}
|
||||
}).run();
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
#include "App.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
|
||||
int main() {
|
||||
/* ws->getUserData returns one of these */
|
||||
struct PerSocketData {
|
||||
|
||||
};
|
||||
|
||||
/* Simple echo websocket server, using multiple threads */
|
||||
std::vector<std::thread *> threads(std::thread::hardware_concurrency());
|
||||
|
||||
std::transform(threads.begin(), threads.end(), threads.begin(), [](std::thread *t) {
|
||||
return new std::thread([]() {
|
||||
|
||||
/* Very simple WebSocket echo server */
|
||||
uWS::App().ws<PerSocketData>("/*", {
|
||||
/* Settings */
|
||||
.compression = uWS::SHARED_COMPRESSOR,
|
||||
.maxPayloadLength = 16 * 1024,
|
||||
.idleTimeout = 10,
|
||||
.maxBackpressure = 1 * 1024 * 1204,
|
||||
/* Handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
ws->send(message, opCode);
|
||||
},
|
||||
.drain = [](auto *ws) {
|
||||
/* Check getBufferedAmount here */
|
||||
},
|
||||
.ping = [](auto *ws) {
|
||||
|
||||
},
|
||||
.pong = [](auto *ws) {
|
||||
|
||||
},
|
||||
.close = [](auto *ws, int code, std::string_view message) {
|
||||
|
||||
}
|
||||
}).listen(9001, [](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Thread " << std::this_thread::get_id() << " listening on port " << 9001 << std::endl;
|
||||
} else {
|
||||
std::cout << "Thread " << std::this_thread::get_id() << " failed to listen on port 9001" << std::endl;
|
||||
}
|
||||
}).run();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
std::for_each(threads.begin(), threads.end(), [](std::thread *t) {
|
||||
t->join();
|
||||
});
|
||||
}
|
20
test/compatibility/C/uWebSockets/examples/HelloWorld.cpp
Normal file
20
test/compatibility/C/uWebSockets/examples/HelloWorld.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "App.h"
|
||||
|
||||
/* Note that uWS::SSLApp({options}) is the same as uWS::App() when compiled without SSL support */
|
||||
|
||||
int main() {
|
||||
/* Overly simple hello world app */
|
||||
uWS::SSLApp({
|
||||
.key_file_name = "../misc/key.pem",
|
||||
.cert_file_name = "../misc/cert.pem",
|
||||
.passphrase = "1234"
|
||||
}).get("/*", [](auto *res, auto *req) {
|
||||
res->end("Hello world!");
|
||||
}).listen(3000, [](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Listening on port " << 3000 << std::endl;
|
||||
}
|
||||
}).run();
|
||||
|
||||
std::cout << "Failed to listen on port 3000" << std::endl;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
#include "App.h"
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
|
||||
int main() {
|
||||
/* Overly simple hello world app, using multiple threads */
|
||||
std::vector<std::thread *> threads(std::thread::hardware_concurrency());
|
||||
|
||||
std::transform(threads.begin(), threads.end(), threads.begin(), [](std::thread *t) {
|
||||
return new std::thread([]() {
|
||||
|
||||
uWS::App().get("/*", [](auto *res, auto *req) {
|
||||
res->end("Hello world!");
|
||||
}).listen(3000, [](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Thread " << std::this_thread::get_id() << " listening on port " << 3000 << std::endl;
|
||||
} else {
|
||||
std::cout << "Thread " << std::this_thread::get_id() << " failed to listen on port 3000" << std::endl;
|
||||
}
|
||||
}).run();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
std::for_each(threads.begin(), threads.end(), [](std::thread *t) {
|
||||
t->join();
|
||||
});
|
||||
}
|
90
test/compatibility/C/uWebSockets/examples/HttpServer.cpp
Normal file
90
test/compatibility/C/uWebSockets/examples/HttpServer.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
/* This is a simple HTTP(S) web server much like Python's SimpleHTTPServer */
|
||||
|
||||
#include <App.h>
|
||||
|
||||
/* Helpers for this example */
|
||||
#include "helpers/AsyncFileReader.h"
|
||||
#include "helpers/AsyncFileStreamer.h"
|
||||
#include "helpers/Middleware.h"
|
||||
|
||||
/* optparse */
|
||||
#define OPTPARSE_IMPLEMENTATION
|
||||
#include "helpers/optparse.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
int option;
|
||||
struct optparse options;
|
||||
optparse_init(&options, argv);
|
||||
|
||||
struct optparse_long longopts[] = {
|
||||
{"port", 'p', OPTPARSE_REQUIRED},
|
||||
{"help", 'h', OPTPARSE_NONE},
|
||||
{"passphrase", 'a', OPTPARSE_REQUIRED},
|
||||
{"key", 'k', OPTPARSE_REQUIRED},
|
||||
{"cert", 'c', OPTPARSE_REQUIRED},
|
||||
{"dh_params", 'd', OPTPARSE_REQUIRED},
|
||||
{0}
|
||||
};
|
||||
|
||||
int port = 3000;
|
||||
struct us_socket_context_options_t ssl_options = {};
|
||||
|
||||
while ((option = optparse_long(&options, longopts, nullptr)) != -1) {
|
||||
switch (option) {
|
||||
case 'p':
|
||||
port = atoi(options.optarg);
|
||||
break;
|
||||
case 'a':
|
||||
ssl_options.passphrase = options.optarg;
|
||||
break;
|
||||
case 'c':
|
||||
ssl_options.cert_file_name = options.optarg;
|
||||
break;
|
||||
case 'k':
|
||||
ssl_options.key_file_name = options.optarg;
|
||||
break;
|
||||
case 'd':
|
||||
ssl_options.dh_params_file_name = options.optarg;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
fail:
|
||||
std::cout << "Usage: " << argv[0] << " [--help] [--port <port>] [--key <ssl key>] [--cert <ssl cert>] [--passphrase <ssl key passphrase>] [--dh_params <ssl dh params file>] <public root>" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
char *root = optparse_arg(&options);
|
||||
if (!root) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
AsyncFileStreamer asyncFileStreamer(root);
|
||||
|
||||
/* Either serve over HTTP or HTTPS */
|
||||
struct us_socket_context_options_t empty_ssl_options = {};
|
||||
if (memcmp(&ssl_options, &empty_ssl_options, sizeof(empty_ssl_options))) {
|
||||
/* HTTPS */
|
||||
uWS::SSLApp(ssl_options).get("/*", [&asyncFileStreamer](auto *res, auto *req) {
|
||||
serveFile(res, req);
|
||||
asyncFileStreamer.streamFile(res, req->getUrl());
|
||||
}).listen(port, [port, root](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Serving " << root << " over HTTPS a " << port << std::endl;
|
||||
}
|
||||
}).run();
|
||||
} else {
|
||||
/* HTTP */
|
||||
uWS::App().get("/*", [&asyncFileStreamer](auto *res, auto *req) {
|
||||
serveFile(res, req);
|
||||
asyncFileStreamer.streamFile(res, req->getUrl());
|
||||
}).listen(port, [port, root](auto *token) {
|
||||
if (token) {
|
||||
std::cout << "Serving " << root << " over HTTP a " << port << std::endl;
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
|
||||
std::cout << "Failed to listen to port " << port << std::endl;
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <future>
|
||||
|
||||
/* This is just a very simple and inefficient demo of async responses,
|
||||
* please do roll your own variant or use a database or Node.js's async
|
||||
* features instead of this really bad demo */
|
||||
struct AsyncFileReader {
|
||||
private:
|
||||
/* The cache we have in memory for this file */
|
||||
std::string cache;
|
||||
int cacheOffset;
|
||||
bool hasCache;
|
||||
|
||||
/* The pending async file read (yes we only support one pending read) */
|
||||
std::function<void(std::string_view)> pendingReadCb;
|
||||
|
||||
int fileSize;
|
||||
std::string fileName;
|
||||
std::ifstream fin;
|
||||
uWS::Loop *loop;
|
||||
|
||||
public:
|
||||
/* Construct a demo async. file reader for fileName */
|
||||
AsyncFileReader(std::string fileName) : fileName(fileName) {
|
||||
fin.open(fileName, std::ios::binary);
|
||||
|
||||
// get fileSize
|
||||
fin.seekg(0, fin.end);
|
||||
fileSize = fin.tellg();
|
||||
|
||||
//std::cout << "File size is: " << fileSize << std::endl;
|
||||
|
||||
// cache up 1 mb!
|
||||
cache.resize(1024 * 1024);
|
||||
|
||||
//std::cout << "Caching 1 MB at offset = " << 0 << std::endl;
|
||||
fin.seekg(0, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
cacheOffset = 0;
|
||||
hasCache = true;
|
||||
|
||||
// get loop for thread
|
||||
|
||||
loop = uWS::Loop::get();
|
||||
}
|
||||
|
||||
/* Returns any data already cached for this offset */
|
||||
std::string_view peek(int offset) {
|
||||
/* Did we hit the cache? */
|
||||
if (hasCache && offset >= cacheOffset && ((offset - cacheOffset) < cache.length())) {
|
||||
/* Cache hit */
|
||||
//std::cout << "Cache hit!" << std::endl;
|
||||
|
||||
/*if (fileSize - offset < cache.length()) {
|
||||
std::cout << "LESS THAN WHAT WE HAVE!" << std::endl;
|
||||
}*/
|
||||
|
||||
int chunkSize = std::min<int>(fileSize - offset, cache.length() - offset + cacheOffset);
|
||||
|
||||
return std::string_view(cache.data() + offset - cacheOffset, chunkSize);
|
||||
} else {
|
||||
/* Cache miss */
|
||||
//std::cout << "Cache miss!" << std::endl;
|
||||
return std::string_view(nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Asynchronously request more data at offset */
|
||||
void request(int offset, std::function<void(std::string_view)> cb) {
|
||||
|
||||
// in this case, what do we do?
|
||||
// we need to queue up this chunk request and callback!
|
||||
// if queue is full, either block or close the connection via abort!
|
||||
if (!hasCache) {
|
||||
// already requesting a chunk!
|
||||
std::cout << "ERROR: already requesting a chunk!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// disable cache
|
||||
hasCache = false;
|
||||
|
||||
std::async(std::launch::async, [this, cb, offset]() {
|
||||
//std::cout << "ASYNC Caching 1 MB at offset = " << offset << std::endl;
|
||||
|
||||
|
||||
|
||||
// den har stängts! öppna igen!
|
||||
if (!fin.good()) {
|
||||
fin.close();
|
||||
//std::cout << "Reopening fin!" << std::endl;
|
||||
fin.open(fileName, std::ios::binary);
|
||||
}
|
||||
fin.seekg(offset, fin.beg);
|
||||
fin.read(cache.data(), cache.length());
|
||||
|
||||
cacheOffset = offset;
|
||||
|
||||
loop->defer([this, cb, offset]() {
|
||||
|
||||
int chunkSize = std::min<int>(cache.length(), fileSize - offset);
|
||||
|
||||
// båda dessa sker, wtf?
|
||||
if (chunkSize == 0) {
|
||||
std::cout << "Zero size!?" << std::endl;
|
||||
}
|
||||
|
||||
if (chunkSize != cache.length()) {
|
||||
std::cout << "LESS THAN A CACHE 1 MB!" << std::endl;
|
||||
}
|
||||
|
||||
hasCache = true;
|
||||
cb(std::string_view(cache.data(), chunkSize));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* Abort any pending async. request */
|
||||
void abort() {
|
||||
|
||||
}
|
||||
|
||||
int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
};
|
@ -0,0 +1,84 @@
|
||||
#include <filesystem>
|
||||
|
||||
struct AsyncFileStreamer {
|
||||
|
||||
std::map<std::string_view, AsyncFileReader *> asyncFileReaders;
|
||||
std::string root;
|
||||
|
||||
AsyncFileStreamer(std::string root) : root(root) {
|
||||
// for all files in this path, init the map of AsyncFileReaders
|
||||
updateRootCache();
|
||||
}
|
||||
|
||||
void updateRootCache() {
|
||||
// todo: if the root folder changes, we want to reload the cache
|
||||
for(auto &p : std::filesystem::recursive_directory_iterator(root)) {
|
||||
std::string url = p.path().string().substr(root.length());
|
||||
if (url == "/index.html") {
|
||||
url = "/";
|
||||
}
|
||||
|
||||
char *key = new char[url.length()];
|
||||
memcpy(key, url.data(), url.length());
|
||||
asyncFileReaders[std::string_view(key, url.length())] = new AsyncFileReader(p.path().string());
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SSL>
|
||||
void streamFile(uWS::HttpResponse<SSL> *res, std::string_view url) {
|
||||
auto it = asyncFileReaders.find(url);
|
||||
if (it == asyncFileReaders.end()) {
|
||||
std::cout << "Did not find file: " << url << std::endl;
|
||||
} else {
|
||||
streamFile(res, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SSL>
|
||||
static void streamFile(uWS::HttpResponse<SSL> *res, AsyncFileReader *asyncFileReader) {
|
||||
/* Peek from cache */
|
||||
std::string_view chunk = asyncFileReader->peek(res->getWriteOffset());
|
||||
if (!chunk.length() || res->tryEnd(chunk, asyncFileReader->getFileSize()).first) {
|
||||
/* Request new chunk */
|
||||
// todo: we need to abort this callback if peer closed!
|
||||
// this also means Loop::defer needs to support aborting (functions should embedd an atomic boolean abort or something)
|
||||
|
||||
// Loop::defer(f) -> integer
|
||||
// Loop::abort(integer)
|
||||
|
||||
// hmm? no?
|
||||
|
||||
// us_socket_up_ref eftersom vi delar ägandeskapet
|
||||
|
||||
if (chunk.length() < asyncFileReader->getFileSize()) {
|
||||
asyncFileReader->request(res->getWriteOffset(), [res, asyncFileReader](std::string_view chunk) {
|
||||
// check if we were closed in the mean time
|
||||
//if (us_socket_is_closed()) {
|
||||
// free it here
|
||||
//return;
|
||||
//}
|
||||
|
||||
/* We were aborted for some reason */
|
||||
if (!chunk.length()) {
|
||||
// todo: make sure to check for is_closed internally after all callbacks!
|
||||
res->close();
|
||||
} else {
|
||||
AsyncFileStreamer::streamFile(res, asyncFileReader);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
/* We failed writing everything, so let's continue when we can */
|
||||
res->onWritable([res, asyncFileReader](int offset) {
|
||||
|
||||
// här kan skiten avbrytas!
|
||||
|
||||
AsyncFileStreamer::streamFile(res, asyncFileReader);
|
||||
// todo: I don't really know what this is supposed to mean?
|
||||
return false;
|
||||
})->onAborted([]() {
|
||||
std::cout << "ABORTED!" << std::endl;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
/* Middleware to fill out content-type */
|
||||
inline bool hasExt(std::string_view file, std::string_view ext) {
|
||||
if (ext.size() > file.size()) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(ext.rbegin(), ext.rend(), file.rbegin());
|
||||
}
|
||||
|
||||
/* This should be a filter / middleware like app.use(handler) */
|
||||
template <bool SSL>
|
||||
uWS::HttpResponse<SSL> *serveFile(uWS::HttpResponse<SSL> *res, uWS::HttpRequest *req) {
|
||||
res->writeStatus(uWS::HTTP_200_OK);
|
||||
|
||||
if (hasExt(req->getUrl(), ".svg")) {
|
||||
res->writeHeader("Content-Type", "image/svg+xml");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
407
test/compatibility/C/uWebSockets/examples/helpers/optparse.h
Normal file
407
test/compatibility/C/uWebSockets/examples/helpers/optparse.h
Normal file
@ -0,0 +1,407 @@
|
||||
/* Nicked from third-party https://github.com/skeeto/optparse 2018-09-24 */
|
||||
/* µWebSockets is not the origin of this software file */
|
||||
/* ------------------------------------------------------ */
|
||||
|
||||
/* Optparse --- portable, reentrant, embeddable, getopt-like option parser
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*
|
||||
* To get the implementation, define OPTPARSE_IMPLEMENTATION.
|
||||
* Optionally define OPTPARSE_API to control the API's visibility
|
||||
* and/or linkage (static, __attribute__, __declspec).
|
||||
*
|
||||
* The POSIX getopt() option parser has three fatal flaws. These flaws
|
||||
* are solved by Optparse.
|
||||
*
|
||||
* 1) Parser state is stored entirely in global variables, some of
|
||||
* which are static and inaccessible. This means only one thread can
|
||||
* use getopt(). It also means it's not possible to recursively parse
|
||||
* nested sub-arguments while in the middle of argument parsing.
|
||||
* Optparse fixes this by storing all state on a local struct.
|
||||
*
|
||||
* 2) The POSIX standard provides no way to properly reset the parser.
|
||||
* This means for portable code that getopt() is only good for one
|
||||
* run, over one argv with one option string. It also means subcommand
|
||||
* options cannot be processed with getopt(). Most implementations
|
||||
* provide a method to reset the parser, but it's not portable.
|
||||
* Optparse provides an optparse_arg() function for stepping over
|
||||
* subcommands and continuing parsing of options with another option
|
||||
* string. The Optparse struct itself can be passed around to
|
||||
* subcommand handlers for additional subcommand option parsing. A
|
||||
* full reset can be achieved by with an additional optparse_init().
|
||||
*
|
||||
* 3) Error messages are printed to stderr. This can be disabled with
|
||||
* opterr, but the messages themselves are still inaccessible.
|
||||
* Optparse solves this by writing an error message in its errmsg
|
||||
* field. The downside to Optparse is that this error message will
|
||||
* always be in English rather than the current locale.
|
||||
*
|
||||
* Optparse should be familiar with anyone accustomed to getopt(), and
|
||||
* it could be a nearly drop-in replacement. The option string is the
|
||||
* same and the fields have the same names as the getopt() global
|
||||
* variables (optarg, optind, optopt).
|
||||
*
|
||||
* Optparse also supports GNU-style long options with optparse_long().
|
||||
* The interface is slightly different and simpler than getopt_long().
|
||||
*
|
||||
* By default, argv is permuted as it is parsed, moving non-option
|
||||
* arguments to the end. This can be disabled by setting the `permute`
|
||||
* field to 0 after initialization.
|
||||
*/
|
||||
#ifndef OPTPARSE_H
|
||||
#define OPTPARSE_H
|
||||
|
||||
#ifndef OPTPARSE_API
|
||||
# define OPTPARSE_API
|
||||
#endif
|
||||
|
||||
struct optparse {
|
||||
char **argv;
|
||||
int permute;
|
||||
int optind;
|
||||
int optopt;
|
||||
char *optarg;
|
||||
char errmsg[64];
|
||||
int subopt;
|
||||
};
|
||||
|
||||
enum optparse_argtype {
|
||||
OPTPARSE_NONE,
|
||||
OPTPARSE_REQUIRED,
|
||||
OPTPARSE_OPTIONAL
|
||||
};
|
||||
|
||||
struct optparse_long {
|
||||
const char *longname;
|
||||
int shortname;
|
||||
enum optparse_argtype argtype;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the parser state.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
void optparse_init(struct optparse *options, char **argv);
|
||||
|
||||
/**
|
||||
* Read the next option in the argv array.
|
||||
* @param optstring a getopt()-formatted option string.
|
||||
* @return the next option character, -1 for done, or '?' for error
|
||||
*
|
||||
* Just like getopt(), a character followed by no colons means no
|
||||
* argument. One colon means the option has a required argument. Two
|
||||
* colons means the option takes an optional argument.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
int optparse(struct optparse *options, const char *optstring);
|
||||
|
||||
/**
|
||||
* Handles GNU-style long options in addition to getopt() options.
|
||||
* This works a lot like GNU's getopt_long(). The last option in
|
||||
* longopts must be all zeros, marking the end of the array. The
|
||||
* longindex argument may be NULL.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
int optparse_long(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex);
|
||||
|
||||
/**
|
||||
* Used for stepping over non-option arguments.
|
||||
* @return the next non-option argument, or NULL for no more arguments
|
||||
*
|
||||
* Argument parsing can continue with optparse() after using this
|
||||
* function. That would be used to parse the options for the
|
||||
* subcommand returned by optparse_arg(). This function allows you to
|
||||
* ignore the value of optind.
|
||||
*/
|
||||
OPTPARSE_API
|
||||
char *optparse_arg(struct optparse *options);
|
||||
|
||||
/* Implementation */
|
||||
#ifdef OPTPARSE_IMPLEMENTATION
|
||||
|
||||
#define OPTPARSE_MSG_INVALID "invalid option"
|
||||
#define OPTPARSE_MSG_MISSING "option requires an argument"
|
||||
#define OPTPARSE_MSG_TOOMANY "option takes no arguments"
|
||||
|
||||
static int
|
||||
optparse_error(struct optparse *options, const char *msg, const char *data)
|
||||
{
|
||||
unsigned p = 0;
|
||||
const char *sep = " -- '";
|
||||
while (*msg)
|
||||
options->errmsg[p++] = *msg++;
|
||||
while (*sep)
|
||||
options->errmsg[p++] = *sep++;
|
||||
while (p < sizeof(options->errmsg) - 2 && *data)
|
||||
options->errmsg[p++] = *data++;
|
||||
options->errmsg[p++] = '\'';
|
||||
options->errmsg[p++] = '\0';
|
||||
return '?';
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
void
|
||||
optparse_init(struct optparse *options, char **argv)
|
||||
{
|
||||
options->argv = argv;
|
||||
options->permute = 1;
|
||||
options->optind = 1;
|
||||
options->subopt = 0;
|
||||
options->optarg = 0;
|
||||
options->errmsg[0] = '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_dashdash(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_shortopt(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_is_longopt(const char *arg)
|
||||
{
|
||||
return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
optparse_permute(struct optparse *options, int index)
|
||||
{
|
||||
char *nonoption = options->argv[index];
|
||||
int i;
|
||||
for (i = index; i < options->optind - 1; i++)
|
||||
options->argv[i] = options->argv[i + 1];
|
||||
options->argv[options->optind - 1] = nonoption;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_argtype(const char *optstring, char c)
|
||||
{
|
||||
int count = OPTPARSE_NONE;
|
||||
if (c == ':')
|
||||
return -1;
|
||||
for (; *optstring && c != *optstring; optstring++);
|
||||
if (!*optstring)
|
||||
return -1;
|
||||
if (optstring[1] == ':')
|
||||
count += optstring[2] == ':' ? 2 : 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
int
|
||||
optparse(struct optparse *options, const char *optstring)
|
||||
{
|
||||
int type;
|
||||
char *next;
|
||||
char *option = options->argv[options->optind];
|
||||
options->errmsg[0] = '\0';
|
||||
options->optopt = 0;
|
||||
options->optarg = 0;
|
||||
if (option == 0) {
|
||||
return -1;
|
||||
} else if (optparse_is_dashdash(option)) {
|
||||
options->optind++; /* consume "--" */
|
||||
return -1;
|
||||
} else if (!optparse_is_shortopt(option)) {
|
||||
if (options->permute) {
|
||||
int index = options->optind++;
|
||||
int r = optparse(options, optstring);
|
||||
optparse_permute(options, index);
|
||||
options->optind--;
|
||||
return r;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
option += options->subopt + 1;
|
||||
options->optopt = option[0];
|
||||
type = optparse_argtype(optstring, option[0]);
|
||||
next = options->argv[options->optind + 1];
|
||||
switch (type) {
|
||||
case -1: {
|
||||
char str[2] = {0, 0};
|
||||
str[0] = option[0];
|
||||
options->optind++;
|
||||
return optparse_error(options, OPTPARSE_MSG_INVALID, str);
|
||||
}
|
||||
case OPTPARSE_NONE:
|
||||
if (option[1]) {
|
||||
options->subopt++;
|
||||
} else {
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
}
|
||||
return option[0];
|
||||
case OPTPARSE_REQUIRED:
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
if (option[1]) {
|
||||
options->optarg = option + 1;
|
||||
} else if (next != 0) {
|
||||
options->optarg = next;
|
||||
options->optind++;
|
||||
} else {
|
||||
char str[2] = {0, 0};
|
||||
str[0] = option[0];
|
||||
options->optarg = 0;
|
||||
return optparse_error(options, OPTPARSE_MSG_MISSING, str);
|
||||
}
|
||||
return option[0];
|
||||
case OPTPARSE_OPTIONAL:
|
||||
options->subopt = 0;
|
||||
options->optind++;
|
||||
if (option[1])
|
||||
options->optarg = option + 1;
|
||||
else
|
||||
options->optarg = 0;
|
||||
return option[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
char *
|
||||
optparse_arg(struct optparse *options)
|
||||
{
|
||||
char *option = options->argv[options->optind];
|
||||
options->subopt = 0;
|
||||
if (option != 0)
|
||||
options->optind++;
|
||||
return option;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_longopts_end(const struct optparse_long *longopts, int i)
|
||||
{
|
||||
return !longopts[i].longname && !longopts[i].shortname;
|
||||
}
|
||||
|
||||
static void
|
||||
optparse_from_long(const struct optparse_long *longopts, char *optstring)
|
||||
{
|
||||
char *p = optstring;
|
||||
int i;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
|
||||
if (longopts[i].shortname) {
|
||||
int a;
|
||||
*p++ = longopts[i].shortname;
|
||||
for (a = 0; a < (int)longopts[i].argtype; a++)
|
||||
*p++ = ':';
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
/* Unlike strcmp(), handles options containing "=". */
|
||||
static int
|
||||
optparse_longopts_match(const char *longname, const char *option)
|
||||
{
|
||||
const char *a = option, *n = longname;
|
||||
if (longname == 0)
|
||||
return 0;
|
||||
for (; *a && *n && *a != '='; a++, n++)
|
||||
if (*a != *n)
|
||||
return 0;
|
||||
return *n == '\0' && (*a == '\0' || *a == '=');
|
||||
}
|
||||
|
||||
/* Return the part after "=", or NULL. */
|
||||
static char *
|
||||
optparse_longopts_arg(char *option)
|
||||
{
|
||||
for (; *option && *option != '='; option++);
|
||||
if (*option == '=')
|
||||
return option + 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
optparse_long_fallback(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex)
|
||||
{
|
||||
int result;
|
||||
char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
|
||||
optparse_from_long(longopts, optstring);
|
||||
result = optparse(options, optstring);
|
||||
if (longindex != 0) {
|
||||
*longindex = -1;
|
||||
if (result != -1) {
|
||||
int i;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++)
|
||||
if (longopts[i].shortname == options->optopt)
|
||||
*longindex = i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OPTPARSE_API
|
||||
int
|
||||
optparse_long(struct optparse *options,
|
||||
const struct optparse_long *longopts,
|
||||
int *longindex)
|
||||
{
|
||||
int i;
|
||||
char *option = options->argv[options->optind];
|
||||
if (option == 0) {
|
||||
return -1;
|
||||
} else if (optparse_is_dashdash(option)) {
|
||||
options->optind++; /* consume "--" */
|
||||
return -1;
|
||||
} else if (optparse_is_shortopt(option)) {
|
||||
return optparse_long_fallback(options, longopts, longindex);
|
||||
} else if (!optparse_is_longopt(option)) {
|
||||
if (options->permute) {
|
||||
int index = options->optind++;
|
||||
int r = optparse_long(options, longopts, longindex);
|
||||
optparse_permute(options, index);
|
||||
options->optind--;
|
||||
return r;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse as long option. */
|
||||
options->errmsg[0] = '\0';
|
||||
options->optopt = 0;
|
||||
options->optarg = 0;
|
||||
option += 2; /* skip "--" */
|
||||
options->optind++;
|
||||
for (i = 0; !optparse_longopts_end(longopts, i); i++) {
|
||||
const char *name = longopts[i].longname;
|
||||
if (optparse_longopts_match(name, option)) {
|
||||
char *arg;
|
||||
if (longindex)
|
||||
*longindex = i;
|
||||
options->optopt = longopts[i].shortname;
|
||||
arg = optparse_longopts_arg(option);
|
||||
if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
|
||||
return optparse_error(options, OPTPARSE_MSG_TOOMANY, name);
|
||||
} if (arg != 0) {
|
||||
options->optarg = arg;
|
||||
} else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
|
||||
options->optarg = options->argv[options->optind];
|
||||
if (options->optarg == 0)
|
||||
return optparse_error(options, OPTPARSE_MSG_MISSING, name);
|
||||
else
|
||||
options->optind++;
|
||||
}
|
||||
return options->optopt;
|
||||
}
|
||||
}
|
||||
return optparse_error(options, OPTPARSE_MSG_INVALID, option);
|
||||
}
|
||||
|
||||
#endif /* OPTPARSE_IMPLEMENTATION */
|
||||
#endif /* OPTPARSE_H */
|
39
test/compatibility/C/uWebSockets/fuzzing/Extensions.cpp
Normal file
39
test/compatibility/C/uWebSockets/fuzzing/Extensions.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
/* This is a fuzz test of the websocket extensions parser */
|
||||
|
||||
#define WIN32_EXPORT
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/* We test the websocket extensions parser */
|
||||
#include "../src/WebSocketExtensions.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
{
|
||||
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::PERMESSAGE_DEFLATE);
|
||||
extensionsNegotiator.readOffer({(char *) data, size});
|
||||
|
||||
extensionsNegotiator.generateOffer();
|
||||
extensionsNegotiator.getNegotiatedOptions();
|
||||
}
|
||||
|
||||
{
|
||||
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::NO_OPTIONS);
|
||||
extensionsNegotiator.readOffer({(char *) data, size});
|
||||
|
||||
extensionsNegotiator.generateOffer();
|
||||
extensionsNegotiator.getNegotiatedOptions();
|
||||
}
|
||||
|
||||
{
|
||||
uWS::ExtensionsNegotiator<true> extensionsNegotiator(uWS::Options::CLIENT_NO_CONTEXT_TAKEOVER);
|
||||
extensionsNegotiator.readOffer({(char *) data, size});
|
||||
|
||||
extensionsNegotiator.generateOffer();
|
||||
extensionsNegotiator.getNegotiatedOptions();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
20
test/compatibility/C/uWebSockets/fuzzing/Handshake.cpp
Normal file
20
test/compatibility/C/uWebSockets/fuzzing/Handshake.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
/* This is a fuzz test of the websocket handshake generator */
|
||||
|
||||
#define WIN32_EXPORT
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/* We test the websocket handshake generator */
|
||||
#include "../src/WebSocketHandshake.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
char output[28];
|
||||
if (size >= 24) {
|
||||
uWS::WebSocketHandshake::generate((char *) data, output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
124
test/compatibility/C/uWebSockets/fuzzing/Http.cpp
Normal file
124
test/compatibility/C/uWebSockets/fuzzing/Http.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
/* This is a fuzz test of the http parser */
|
||||
|
||||
#define WIN32_EXPORT
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
/* We test the websocket parser */
|
||||
#include "../src/HttpParser.h"
|
||||
|
||||
/* And the router */
|
||||
#include "../src/HttpRouter.h"
|
||||
|
||||
struct StaticData {
|
||||
|
||||
struct RouterData {
|
||||
|
||||
};
|
||||
|
||||
uWS::HttpRouter<RouterData> router;
|
||||
|
||||
StaticData() {
|
||||
|
||||
router.add({"get"}, "/:hello/:hi", [](auto *h) mutable {
|
||||
auto [paramsTop, params] = h->getParameters();
|
||||
|
||||
/* Something is horribly wrong */
|
||||
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* This route did handle it */
|
||||
return true;
|
||||
});
|
||||
|
||||
router.add({"post"}, "/:hello/:hi/*", [](auto *h) mutable {
|
||||
auto [paramsTop, params] = h->getParameters();
|
||||
|
||||
/* Something is horribly wrong */
|
||||
if (paramsTop != 1 || !params[0].length() || !params[1].length()) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* This route did handle it */
|
||||
return true;
|
||||
});
|
||||
|
||||
router.add({"get"}, "/*", [](auto *h) mutable {
|
||||
auto [paramsTop, params] = h->getParameters();
|
||||
|
||||
/* Something is horribly wrong */
|
||||
if (paramsTop != -1) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* This route did not handle it */
|
||||
return false;
|
||||
});
|
||||
|
||||
router.add({"get"}, "/hi", [](auto *h) mutable {
|
||||
auto [paramsTop, params] = h->getParameters();
|
||||
|
||||
/* Something is horribly wrong */
|
||||
if (paramsTop != -1) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/* This route did handle it */
|
||||
return true;
|
||||
});
|
||||
}
|
||||
} staticData;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
/* Create parser */
|
||||
uWS::HttpParser httpParser;
|
||||
/* User data */
|
||||
void *user = (void *) 13;
|
||||
|
||||
/* Iterate the padded fuzz as chunks */
|
||||
makeChunked(makePadded(data, size), size, [&httpParser, user](const uint8_t *data, size_t size) {
|
||||
/* We need at least 1 byte post padding */
|
||||
if (size) {
|
||||
size--;
|
||||
} else {
|
||||
/* We might be given zero length chunks */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse it */
|
||||
httpParser.consumePostPadded((char *) data, size, user, [](void *s, uWS::HttpRequest *httpRequest) -> void * {
|
||||
|
||||
readBytes(httpRequest->getHeader(httpRequest->getUrl()));
|
||||
readBytes(httpRequest->getMethod());
|
||||
readBytes(httpRequest->getQuery());
|
||||
|
||||
/* Route the method and URL in two passes */
|
||||
staticData.router.getUserData() = {};
|
||||
if (!staticData.router.route(httpRequest->getMethod(), httpRequest->getUrl())) {
|
||||
/* It was not handled */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (auto p : *httpRequest) {
|
||||
|
||||
}
|
||||
|
||||
/* Return ok */
|
||||
return s;
|
||||
|
||||
}, [](void *user, std::string_view data, bool fin) -> void * {
|
||||
|
||||
/* Return ok */
|
||||
return user;
|
||||
|
||||
}, [](void *user) {
|
||||
|
||||
/* Return break */
|
||||
return nullptr;
|
||||
});
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
21
test/compatibility/C/uWebSockets/fuzzing/Makefile
Normal file
21
test/compatibility/C/uWebSockets/fuzzing/Makefile
Normal file
@ -0,0 +1,21 @@
|
||||
# You can select which sanitizer to use by setting this
|
||||
SANITIZER ?= address
|
||||
# These are set by OSS-Fuzz, we default to AddressSanitizer
|
||||
CXXFLAGS ?= -DLIBUS_NO_SSL -fsanitize=$(SANITIZER),fuzzer
|
||||
CFLAGS ?= -DLIBUS_NO_SSL
|
||||
OUT ?= .
|
||||
|
||||
oss-fuzz:
|
||||
# "Unit tests"
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 WebSocket.cpp -o $(OUT)/WebSocket $(LIB_FUZZING_ENGINE)
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Http.cpp -o $(OUT)/Http $(LIB_FUZZING_ENGINE)
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 PerMessageDeflate.cpp -o $(OUT)/PerMessageDeflate $(LIB_FUZZING_ENGINE) -lz
|
||||
# "Integration tests"
|
||||
$(CC) $(CFLAGS) -DLIBUS_NO_SSL -c -O3 uSocketsMock.c
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedHelloWorld.cpp uSocketsMock.o -lz -o $(OUT)/MockedHelloWorld $(LIB_FUZZING_ENGINE)
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 -DLIBUS_NO_SSL -I../src -I../uSockets/src MockedEchoServer.cpp uSocketsMock.o -lz -o $(OUT)/MockedEchoServer $(LIB_FUZZING_ENGINE)
|
||||
|
||||
broken:
|
||||
# Too small tests, failing coverage test
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Extensions.cpp -o $(OUT)/Extensions $(LIB_FUZZING_ENGINE)
|
||||
$(CXX) $(CXXFLAGS) -std=c++17 -O3 Handshake.cpp -o $(OUT)/Handshake $(LIB_FUZZING_ENGINE)
|
@ -0,0 +1,75 @@
|
||||
#include "App.h"
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
/* This function pushes data to the uSockets mock */
|
||||
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
|
||||
|
||||
uWS::TemplatedApp<false> *app;
|
||||
us_listen_socket_t *listenSocket;
|
||||
|
||||
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
|
||||
/* ws->getUserData returns one of these */
|
||||
struct PerSocketData {
|
||||
int nothing;
|
||||
};
|
||||
|
||||
/* Very simple WebSocket echo server */
|
||||
app = new uWS::TemplatedApp<false>(uWS::App().ws<PerSocketData>("/*", {
|
||||
/* Settings */
|
||||
.compression = uWS::SHARED_COMPRESSOR,
|
||||
/* We want this to be low so that we can hit it, yet bigger than 256 */
|
||||
.maxPayloadLength = 300,
|
||||
.idleTimeout = 10,
|
||||
/* Handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
if (req->getHeader("close_me").length()) {
|
||||
ws->close();
|
||||
} else if (req->getHeader("end_me").length()) {
|
||||
ws->end(1006);
|
||||
}
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
if (message.length() > 300) {
|
||||
/* Inform the sanitizer of the fault */
|
||||
fprintf(stderr, "Too long message passed\n");
|
||||
free((void *) -1);
|
||||
}
|
||||
|
||||
if (message.length() && message[0] == 'C') {
|
||||
ws->close();
|
||||
} else if (message.length() && message[0] == 'E') {
|
||||
ws->end(1006);
|
||||
} else {
|
||||
ws->send(message, opCode, true);
|
||||
}
|
||||
},
|
||||
.drain = [](auto *ws) {
|
||||
/* Check getBufferedAmount here */
|
||||
},
|
||||
.ping = [](auto *ws) {
|
||||
|
||||
},
|
||||
.pong = [](auto *ws) {
|
||||
|
||||
},
|
||||
.close = [](auto *ws, int code, std::string_view message) {
|
||||
|
||||
}
|
||||
}).listen(9001, [](us_listen_socket_t *listenSocket) {
|
||||
if (listenSocket) {
|
||||
std::cout << "Listening on port " << 9001 << std::endl;
|
||||
::listenSocket = listenSocket;
|
||||
}
|
||||
}));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
#include "App.h"
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
/* This function pushes data to the uSockets mock */
|
||||
extern "C" void us_loop_read_mocked_data(struct us_loop *loop, char *data, unsigned int size);
|
||||
|
||||
uWS::TemplatedApp<false> *app;
|
||||
us_listen_socket_t *listenSocket;
|
||||
|
||||
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
|
||||
|
||||
app = new uWS::TemplatedApp<false>(uWS::App().get("/*", [](auto *res, auto *req) {
|
||||
if (req->getHeader("use_write").length()) {
|
||||
res->writeStatus("200 OK")->writeHeader("write", "true")->write("Hello");
|
||||
res->write(" world!");
|
||||
res->end();
|
||||
} else if (req->getQuery().length()) {
|
||||
res->close();
|
||||
} else {
|
||||
res->end("Hello world!");
|
||||
}
|
||||
})/*.post("/*", [](auto *res, auto *req) {
|
||||
res->onAborted([]() {
|
||||
|
||||
});
|
||||
res->onData([res](std::string_view chunk, bool isEnd) {
|
||||
if (isEnd) {
|
||||
res->end(chunk);
|
||||
}
|
||||
});
|
||||
})*/.listen(9001, [](us_listen_socket_t *listenSocket) {
|
||||
if (listenSocket) {
|
||||
std::cout << "Listening on port " << 9001 << std::endl;
|
||||
::listenSocket = listenSocket;
|
||||
}
|
||||
}));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
us_loop_read_mocked_data((struct us_loop *) uWS::Loop::get(), (char *) makePadded(data, size), size);
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* This is a fuzz test of the permessage-deflate module */
|
||||
|
||||
#define WIN32_EXPORT
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/* We test the permessage deflate module */
|
||||
#include "../src/PerMessageDeflate.h"
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
struct StaticData {
|
||||
uWS::ZlibContext zlibContext;
|
||||
|
||||
uWS::InflationStream inflationStream;
|
||||
uWS::DeflationStream deflationStream;
|
||||
} staticData;
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
/* Why is this padded? */
|
||||
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
|
||||
std::string_view inflation = staticData.inflationStream.inflate(&staticData.zlibContext, std::string_view((char *) data, size), 256);
|
||||
if (inflation.length() > 256) {
|
||||
/* Cause ASAN to freak out */
|
||||
delete (int *) (void *) 1;
|
||||
}
|
||||
});
|
||||
|
||||
makeChunked(makePadded(data, size), size, [](const uint8_t *data, size_t size) {
|
||||
/* Always reset */
|
||||
staticData.deflationStream.deflate(&staticData.zlibContext, std::string_view((char *) data, size), true);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
29
test/compatibility/C/uWebSockets/fuzzing/README.md
Normal file
29
test/compatibility/C/uWebSockets/fuzzing/README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Fuzz-testing of various parsers and mocked examples
|
||||
|
||||
A secure web server must be capable of receiving mass amount of malicious input without misbehaving or performing illegal actions, such as stepping outside of a memory block or otherwise spilling the beans.
|
||||
|
||||
### Continuous fuzzing under various sanitizers is done as part of the [Google OSS-Fuzz](https://github.com/google/oss-fuzz#oss-fuzz---continuous-fuzzing-for-open-source-software) project:
|
||||
* UndefinedBehaviorSanitizer
|
||||
* AddressSanitizer
|
||||
* MemorySanitizer
|
||||
|
||||
### Currently the following parts are individually fuzzed:
|
||||
|
||||
* WebSocket handshake generator
|
||||
* WebSocket message parser
|
||||
* WebSocket extensions parser & negotiator
|
||||
* WebSocket permessage-deflate compression/inflation helper
|
||||
* Http parser
|
||||
* Http method/url router
|
||||
|
||||
### While entire (mocked) examples are fuzzed:
|
||||
|
||||
* HelloWorld
|
||||
* EchoServer
|
||||
|
||||
No defects or issues are left unfixed, covered up or otherwise neglected. In fact we **cannot** cover up security issues as OSS-Fuzz automatically and publicly reports security issues as they happen.
|
||||
|
||||
Currently we are at ~80% total fuzz coverage and OSS-Fuzz is reporting **zero** issues whatsoever. The goal is to approach 90% total coverage.
|
||||
|
||||
### Security awards
|
||||
Google have sent us thousands of USD for the integration with OSS-Fuzz - we continue working on bettering the testing with every new release.
|
59
test/compatibility/C/uWebSockets/fuzzing/WebSocket.cpp
Normal file
59
test/compatibility/C/uWebSockets/fuzzing/WebSocket.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
/* This is a fuzz test of the websocket parser */
|
||||
|
||||
#define WIN32_EXPORT
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
/* We test the websocket parser */
|
||||
#include "../src/WebSocketProtocol.h"
|
||||
|
||||
struct Impl {
|
||||
static bool refusePayloadLength(uint64_t length, uWS::WebSocketState<true> *wState, void *s) {
|
||||
|
||||
/* We need a limit */
|
||||
if (length > 16000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return ok */
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool setCompressed(uWS::WebSocketState<true> *wState, void *s) {
|
||||
/* We support it */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void forceClose(uWS::WebSocketState<true> *wState, void *s) {
|
||||
|
||||
}
|
||||
|
||||
static bool handleFragment(char *data, size_t length, unsigned int remainingBytes, int opCode, bool fin, uWS::WebSocketState<true> *webSocketState, void *s) {
|
||||
|
||||
if (opCode == uWS::TEXT) {
|
||||
if (!uWS::protocol::isValidUtf8((unsigned char *)data, length)) {
|
||||
/* Return break */
|
||||
return true;
|
||||
}
|
||||
} else if (opCode == uWS::CLOSE) {
|
||||
uWS::protocol::parseClosePayload((char *)data, length);
|
||||
}
|
||||
|
||||
/* Return ok */
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
|
||||
/* Create the parser state */
|
||||
uWS::WebSocketState<true> state;
|
||||
|
||||
makeChunked(makePadded(data, size), size, [&state](const uint8_t *data, size_t size) {
|
||||
/* Parse it */
|
||||
uWS::WebSocketProtocol<true, Impl>::consume((char *) data, size, &state, nullptr);
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
51
test/compatibility/C/uWebSockets/fuzzing/helpers.h
Normal file
51
test/compatibility/C/uWebSockets/fuzzing/helpers.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef HELPERS_H
|
||||
#define HELPERS_H
|
||||
|
||||
/* Common helpers for fuzzing */
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <cstring>
|
||||
|
||||
/* We use this to pad the fuzz */
|
||||
static inline const uint8_t *makePadded(const uint8_t *data, size_t size) {
|
||||
static int paddedLength = 512 * 1024;
|
||||
static char *padded = new char[128 + paddedLength + 128];
|
||||
|
||||
/* Increase landing area if required */
|
||||
if (paddedLength < size) {
|
||||
delete [] padded;
|
||||
paddedLength = size;
|
||||
padded = new char [128 + paddedLength + 128];
|
||||
}
|
||||
|
||||
memcpy(padded + 128, data, size);
|
||||
|
||||
return (uint8_t *) padded + 128;
|
||||
}
|
||||
|
||||
/* Splits the fuzz data in one or many chunks */
|
||||
static inline void makeChunked(const uint8_t *data, size_t size, std::function<void(const uint8_t *data, size_t size)> cb) {
|
||||
/* First byte determines chunk size; 0 is all that remains, 1-255 is small chunk */
|
||||
for (int i = 0; i < size; ) {
|
||||
unsigned int chunkSize = data[i++];
|
||||
if (!chunkSize) {
|
||||
chunkSize = size - i;
|
||||
} else {
|
||||
chunkSize = std::min<int>(chunkSize, size - i);
|
||||
}
|
||||
|
||||
cb(data + i, chunkSize);
|
||||
i += chunkSize;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reads all bytes to trigger invalid reads */
|
||||
static inline void readBytes(std::string_view s) {
|
||||
volatile int sum = 0;
|
||||
for (int i = 0; i < s.size(); i++) {
|
||||
sum += s[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
285
test/compatibility/C/uWebSockets/fuzzing/uSocketsMock.c
Normal file
285
test/compatibility/C/uWebSockets/fuzzing/uSocketsMock.c
Normal file
@ -0,0 +1,285 @@
|
||||
/* uSockets is entierly opaque so we can use the real header straight up */
|
||||
#include "../uSockets/src/libusockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdalign.h>
|
||||
#include <string.h>
|
||||
|
||||
struct us_loop_t {
|
||||
|
||||
/* We only support one listen socket */
|
||||
alignas(16) struct us_listen_socket_t *listen_socket;
|
||||
|
||||
/* The list of closed sockets */
|
||||
struct us_socket_t *close_list;
|
||||
};
|
||||
|
||||
struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size) {
|
||||
struct us_loop_t *loop = (struct us_loop_t *) malloc(sizeof(struct us_loop_t) + ext_size);
|
||||
|
||||
loop->listen_socket = 0;
|
||||
loop->close_list = 0;
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
void us_loop_free(struct us_loop_t *loop) {
|
||||
free(loop);
|
||||
}
|
||||
|
||||
void *us_loop_ext(struct us_loop_t *loop) {
|
||||
return loop + 1;
|
||||
}
|
||||
|
||||
void us_loop_run(struct us_loop_t *loop) {
|
||||
|
||||
}
|
||||
|
||||
struct us_socket_context_t {
|
||||
alignas(16) struct us_loop_t *loop;
|
||||
|
||||
struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length);
|
||||
struct us_socket_t *(*on_close)(struct us_socket_t *s);
|
||||
struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length);
|
||||
struct us_socket_t *(*on_writable)(struct us_socket_t *s);
|
||||
struct us_socket_t *(*on_timeout)(struct us_socket_t *s);
|
||||
struct us_socket_t *(*on_end)(struct us_socket_t *s);
|
||||
};
|
||||
|
||||
struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options) {
|
||||
struct us_socket_context_t *socket_context = (struct us_socket_context_t *) malloc(sizeof(struct us_socket_context_t) + ext_size);
|
||||
|
||||
socket_context->loop = loop;
|
||||
|
||||
//printf("us_create_socket_context: %p\n", socket_context);
|
||||
|
||||
return socket_context;
|
||||
}
|
||||
|
||||
void us_socket_context_free(int ssl, struct us_socket_context_t *context) {
|
||||
//printf("us_socket_context_free: %p\n", context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void us_socket_context_on_open(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length)) {
|
||||
context->on_open = on_open;
|
||||
}
|
||||
|
||||
void us_socket_context_on_close(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_close)(struct us_socket_t *s)) {
|
||||
context->on_close = on_close;
|
||||
}
|
||||
|
||||
void us_socket_context_on_data(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length)) {
|
||||
context->on_data = on_data;
|
||||
}
|
||||
|
||||
void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_writable)(struct us_socket_t *s)) {
|
||||
context->on_writable = on_writable;
|
||||
}
|
||||
|
||||
void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_timeout)(struct us_socket_t *s)) {
|
||||
context->on_timeout = on_timeout;
|
||||
}
|
||||
|
||||
void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *s)) {
|
||||
context->on_end = on_end;
|
||||
}
|
||||
|
||||
void *us_socket_context_ext(int ssl, struct us_socket_context_t *context) {
|
||||
return context + 1;
|
||||
}
|
||||
|
||||
struct us_listen_socket_t {
|
||||
int socket_ext_size;
|
||||
struct us_socket_context_t *context;
|
||||
};
|
||||
|
||||
struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
|
||||
struct us_listen_socket_t *listen_socket = (struct us_listen_socket_t *) malloc(sizeof(struct us_listen_socket_t));
|
||||
|
||||
listen_socket->socket_ext_size = socket_ext_size;
|
||||
listen_socket->context = context;
|
||||
|
||||
context->loop->listen_socket = listen_socket;
|
||||
|
||||
return listen_socket;
|
||||
}
|
||||
|
||||
void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
|
||||
free(ls);
|
||||
}
|
||||
|
||||
struct us_socket_t {
|
||||
alignas(16) struct us_socket_context_t *context;
|
||||
|
||||
int closed;
|
||||
int shutdown;
|
||||
int wants_writable;
|
||||
|
||||
//struct us_socket_t *next;
|
||||
};
|
||||
|
||||
struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
|
||||
//printf("us_socket_context_connect\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {
|
||||
return context->loop;
|
||||
}
|
||||
|
||||
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
|
||||
struct us_socket_t *new_s = (struct us_socket_t *) realloc(s, sizeof(struct us_socket_t) + ext_size);
|
||||
new_s->context = context;
|
||||
|
||||
return new_s;
|
||||
}
|
||||
|
||||
struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size) {
|
||||
/* We simply create a new context in this mock */
|
||||
struct us_socket_context_options_t options = {};
|
||||
struct us_socket_context_t *child_context = us_create_socket_context(ssl, context->loop, context_ext_size, options);
|
||||
|
||||
return child_context;
|
||||
}
|
||||
|
||||
int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
|
||||
|
||||
if (!length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Last byte determines if we send everything or not, to stress the buffering mechanism */
|
||||
if (data[length - 1] % 2 == 0) {
|
||||
/* Send only half, but first set our outgoing flag */
|
||||
s->wants_writable = 1;
|
||||
return length / 2;
|
||||
}
|
||||
|
||||
/* Send everything */
|
||||
return length;
|
||||
}
|
||||
|
||||
void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds) {
|
||||
|
||||
}
|
||||
|
||||
void *us_socket_ext(int ssl, struct us_socket_t *s) {
|
||||
return s + 1;
|
||||
}
|
||||
|
||||
struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s) {
|
||||
return s->context;
|
||||
}
|
||||
|
||||
void us_socket_flush(int ssl, struct us_socket_t *s) {
|
||||
|
||||
}
|
||||
|
||||
void us_socket_shutdown(int ssl, struct us_socket_t *s) {
|
||||
s->shutdown = 1;
|
||||
}
|
||||
|
||||
int us_socket_is_shut_down(int ssl, struct us_socket_t *s) {
|
||||
return s->shutdown;
|
||||
}
|
||||
|
||||
int us_socket_is_closed(int ssl, struct us_socket_t *s) {
|
||||
return s->closed;
|
||||
}
|
||||
|
||||
struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s) {
|
||||
|
||||
if (!us_socket_is_closed(0, s)) {
|
||||
/* Emit close event */
|
||||
s = s->context->on_close(s);
|
||||
}
|
||||
|
||||
/* We are now closed */
|
||||
s->closed = 1;
|
||||
|
||||
/* Add us to the close list */
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length) {
|
||||
printf("us_socket_remote_address\n");
|
||||
}
|
||||
|
||||
/* We expose this function to let fuzz targets push data to uSockets */
|
||||
void us_loop_read_mocked_data(struct us_loop_t *loop, char *data, unsigned int size) {
|
||||
|
||||
/* We are unwound so let's free all closed polls here */
|
||||
|
||||
|
||||
/* We have one listen socket */
|
||||
int socket_ext_size = loop->listen_socket->socket_ext_size;
|
||||
|
||||
/* Create a socket with information from the listen socket */
|
||||
struct us_socket_t *s = (struct us_socket_t *) malloc(sizeof(struct us_socket_t) + socket_ext_size);
|
||||
s->context = loop->listen_socket->context;
|
||||
s->closed = 0;
|
||||
s->shutdown = 0;
|
||||
s->wants_writable = 0;
|
||||
|
||||
/* Emit open event */
|
||||
s = s->context->on_open(s, 0, 0, 0);
|
||||
if (!us_socket_is_closed(0, s) && !us_socket_is_shut_down(0, s)) {
|
||||
|
||||
/* Trigger writable event if we want it */
|
||||
if (s->wants_writable) {
|
||||
s->wants_writable = 0;
|
||||
s = s->context->on_writable(s);
|
||||
/* Check if we closed inside of writable */
|
||||
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop over the data, emitting it in chunks of 0-255 bytes */
|
||||
for (int i = 0; i < size; ) {
|
||||
unsigned char chunkLength = data[i++];
|
||||
if (i + chunkLength > size) {
|
||||
chunkLength = size - i;
|
||||
}
|
||||
|
||||
/* Copy the data chunk to a properly padded buffer */
|
||||
static char *paddedBuffer;
|
||||
if (!paddedBuffer) {
|
||||
paddedBuffer = malloc(128 + 255 + 128);
|
||||
memset(paddedBuffer, 0, 128 + 255 + 128);
|
||||
}
|
||||
memcpy(paddedBuffer + 128, data + i, chunkLength);
|
||||
|
||||
/* Emit a bunch of data events here */
|
||||
s = s->context->on_data(s, paddedBuffer + 128, chunkLength);
|
||||
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Also trigger it here */
|
||||
if (s->wants_writable) {
|
||||
s->wants_writable = 0;
|
||||
s = s->context->on_writable(s);
|
||||
/* Check if we closed inside of writable */
|
||||
if (us_socket_is_closed(0, s) || us_socket_is_shut_down(0, s)) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
i += chunkLength;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (!us_socket_is_closed(0, s)) {
|
||||
/* Emit close event */
|
||||
s = s->context->on_close(s);
|
||||
}
|
||||
|
||||
/* Free the socket */
|
||||
free(s);
|
||||
}
|
41
test/compatibility/C/uWebSockets/misc/15.pro
Normal file
41
test/compatibility/C/uWebSockets/misc/15.pro
Normal file
@ -0,0 +1,41 @@
|
||||
TEMPLATE = app
|
||||
CONFIG += console c++1z
|
||||
CONFIG -= app_bundle
|
||||
CONFIG -= qt
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
../uSockets/src/eventing/epoll.c \
|
||||
../uSockets/src/context.c \
|
||||
../uSockets/src/socket.c \
|
||||
../uSockets/src/eventing/libuv.c \
|
||||
../uSockets/src/ssl.c \
|
||||
../uSockets/src/loop.c
|
||||
|
||||
HEADERS += \
|
||||
../src/HttpRouter.h \
|
||||
../src/HttpParser.h \
|
||||
../src/libwshandshake.hpp \
|
||||
../src/WebSocketProtocol.h \
|
||||
../src/HttpContext.h \
|
||||
../src/HttpContextData.h \
|
||||
../src/HttpResponseData.h \
|
||||
../src/HttpResponse.h \
|
||||
../src/LoopData.h \
|
||||
../src/AsyncSocket.h \
|
||||
../src/AsyncSocketData.h \
|
||||
../src/Loop.h \
|
||||
../src/App.h \
|
||||
../src/Utilities.h \
|
||||
../src/WebSocket.h \
|
||||
../src/WebSocketData.h \
|
||||
../src/WebSocketContext.h \
|
||||
../src/WebSocketContextData.h \
|
||||
../src/WebSocketExtensions.h \
|
||||
../src/PerMessageDeflate.h \
|
||||
../src/TopicTree.h
|
||||
|
||||
INCLUDEPATH += ../uSockets/src ../src
|
||||
QMAKE_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter -Wno-unused-but-set-parameter -fsanitize=address
|
||||
QMAKE_CFLAGS += -Wno-unused-parameter -Wno-unused-variable
|
||||
LIBS += -lasan -pthread -lssl -lcrypto -lz -lstdc++fs
|
BIN
test/compatibility/C/uWebSockets/misc/2018.png
Normal file
BIN
test/compatibility/C/uWebSockets/misc/2018.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
19
test/compatibility/C/uWebSockets/misc/FAQ.md
Normal file
19
test/compatibility/C/uWebSockets/misc/FAQ.md
Normal file
@ -0,0 +1,19 @@
|
||||
## Build systems
|
||||
Q:
|
||||
```
|
||||
Hi Alex,
|
||||
I've used your library and I like it, however, to integrate it in my process it needs to use cmake and conan. I have written those for my own project, would you like me to submit a pull request?
|
||||
|
||||
Regards,
|
||||
Ioannis
|
||||
```
|
||||
A:
|
||||
```
|
||||
Hi Ioannis,
|
||||
|
||||
This is a very common request. I don't accept any specific build systems because I know from experience that doing so opens up hell. People simply cannot agree on which build system to use, or even how to use one particular build system.
|
||||
|
||||
That's why I no longer accept any such PRs. I've had too many CMake PRs dragging in completely different directions to know this is the only solution.
|
||||
|
||||
You'll have to clone the repo and create a new project in whatever build system you want to use.
|
||||
```
|
205
test/compatibility/C/uWebSockets/misc/READMORE.md
Normal file
205
test/compatibility/C/uWebSockets/misc/READMORE.md
Normal file
@ -0,0 +1,205 @@
|
||||
# µWebSockets v0.17 user manual
|
||||
|
||||
For a list of frequently asked questions you may filter the GitHub issue tracker by label FAQ. Please don't misuse the issue tracker as your personal Q&A.
|
||||
|
||||
## Motivation and goals
|
||||
µWebSockets is a simple to use yet thoroughly optimized implementation of HTTP and WebSockets.
|
||||
It comes with built-in pub/sub support, HTTP routing, TLS 1.3, IPv6, permessage-deflate and is battle tested as one of the most popular implementations, reaching many end-users daily.
|
||||
Unlike other "pub/sub brokers", µWS does not assume or push any particular protocol but only operates over standard WebSockets.
|
||||
|
||||
The implementation is header-only C++17, cross-platform and compiles down to a tiny binary of a handful kilobytes.
|
||||
It depends on µSockets, which is a standard C project for Linux, macOS & Windows.
|
||||
|
||||
Performance wise you can expect to outperform, or equal, just about anything similar out there, that's the fundamental goal of the project.
|
||||
I can show certain cases where µWS with SSL significantly outperforms Golang servers running non-SSL. You get the SSL for free in a sense, well, in this particular case at least.
|
||||
|
||||
Another goal of the project is minimalism, simplicity and elegance.
|
||||
Design wise it follows an ExpressJS-like interface where you attach callbacks to different URL routes.
|
||||
This way you can easily build complete REST/WebSocket services in a few lines of code.
|
||||
|
||||
The project is async only and runs local to one thread. You scale it as individual threads much like Node.js scales as individual processes. That is, the implementation only sees a single thread and is not thread-safe. There are simple ways to do threading via async delegates though, if you really need to.
|
||||
|
||||
## Compiling
|
||||
µWebSockets is 100% standard header-only C++17 - it compiles on any platform. However, it depends on µSockets in all cases, which is platform-specific C code that runs on Linux, Windows and macOS.
|
||||
|
||||
There are a few compilation flags for µSockets (see its documentation), but common between µSockets and µWebSockets flags are as follows:
|
||||
|
||||
* LIBUS_NO_SSL - disable OpenSSL dependency/functionality for uSockets and uWebSockets builds
|
||||
* UWS_NO_ZLIB - disable Zlib dependency/functionality for uWebSockets
|
||||
|
||||
## Node.js
|
||||
V8 addon is developed over at https://github.com/uNetworking/uWebSockets.js.
|
||||
|
||||
## User manual
|
||||
|
||||
### uWS::App & uWS::SSLApp
|
||||
You begin your journey by constructing an "App". Either an SSL-app or a regular TCP-only App. The uWS::SSLApp constructor takes a struct holding SSL options such as cert and key. Interfaces for both apps are identical, so let's call them both "App" from now on.
|
||||
|
||||
Apps follow the builder pattern, member functions return the App so that you can chain calls.
|
||||
|
||||
### App.get, post, put, [...] and any routes
|
||||
You attach behavior to "URL routes". A lambda is paired with a "method" (Http method that is) and a pattern (the URL matching pattern).
|
||||
|
||||
Methods are many, but most common are probably get & post. They all have the same signature, let's look at one example:
|
||||
|
||||
```c++
|
||||
uWS::App().get("/hello", [](auto *res, auto *req) {
|
||||
res->end("Hello World!");
|
||||
});
|
||||
```
|
||||
|
||||
Important for all routes is that "req", the `uWS::HttpRequest *` dies with return. In other words, req is stack allocated so don't keep it in your pocket.
|
||||
|
||||
res, the `uWS::HttpResponse<SSL> *` will be alive and accessible until either its .onAborted callback emits, or you've responded to the request via res.end or res.tryEnd.
|
||||
|
||||
In other words, you either respond to the request immediately and return, or you attach lambdas to the res (which may hold captured data), and respond later on in some other async callback.
|
||||
|
||||
Data that you capture in a res follows RAII and is move-only so you can properly move-in for instance std::string buffers that you may use to, for instance, buffer upp streaming POST data. It's pretty cool, check out mutable lambdas with move-only captures.
|
||||
|
||||
The "any" route will match any method.
|
||||
|
||||
#### Pattern matching
|
||||
Routes are matched in **order of specificity**, not by the order you register them:
|
||||
|
||||
* Highest priority - static routes, think "/hello/this/is/static".
|
||||
* Middle priority - parameter routes, think "/candy/:kind", where value of :kind is retrieved by req.getParameter(0).
|
||||
* Lowest priority - wildcard routes, think "/hello/*".
|
||||
|
||||
In other words, the more specific a route is, the earlier it will match. This allows you to define wildcard routes that match a wide range of URLs and then "carve" out more specific behavior from that.
|
||||
|
||||
"Any" routes, those who match any HTTP method, will match with lower priority than routes which specify their specific HTTP method (such as GET) if and only if the two routes otherwise are equally specific.
|
||||
|
||||
#### Streaming data
|
||||
You should never call res.end(huge buffer). res.end guarantees sending so backpressure will probably spike. Instead you should use res.tryEnd to stream huge data part by part. Use in combination with res.onWritable and res.onAborted callbacks.
|
||||
|
||||
Tip: Check out the JavaScript project, it has many useful examples of async streaming of huge data.
|
||||
|
||||
#### Corking
|
||||
It is very important to understand the corking mechanism, as that is responsible for efficiently formatting, packing and sending data. Without corking your app will still work reliably, but can perform very bad and use excessive networking. In some cases the performance can be dreadful without proper corking.
|
||||
|
||||
That's why your sockets will be corked by default in most simple cases, including all of the examples provided. However there are cases where default corking cannot happen automatically.
|
||||
|
||||
* Whenever your registered business logic (your callbacks) are called from the library, such as when receiving a message or when a socket opens, you'll be corked by default. Whatever you do with the socket inside of that callback will be efficient and properly corked.
|
||||
|
||||
* If you have callbacks registered to some other library, say libhiredis, those callbacks will not be called with corked sockets (how could **we** know when to cork the socket if we don't control the third-party library!?).
|
||||
|
||||
* Only one single socket can be corked at any point in time (isolated per thread, of course). It is efficient to cork-and-uncork.
|
||||
|
||||
* Whenever your callback is a coroutine, such as the JavaScript async/await, automatic corking can only happen in the very first portion of the coroutine (consider await a separator which essentially cuts the coroutine into smaller segments). Only the first "segment" of the coroutine will be called from µWS, the following async segments will be called by the JavaScript runtime at a later point in time and will thus not be under our control with default corking enabled.
|
||||
|
||||
* Corking is important even for calls which seem to be "atomic" and only send one chunk. res->end, res->tryEnd, res->writeStatus, res->writeHeader will most likely send multiple chunks of data and is very important to properly cork.
|
||||
|
||||
You can make sure corking is enabled, even for cases where default corking would be enabled, by wrapping whatever sending function calls in a lambda like so:
|
||||
|
||||
```c++
|
||||
res->cork([]() {
|
||||
res->end("This Http response will be properly corked and efficient in all cases");
|
||||
});
|
||||
```
|
||||
|
||||
The above res->end call will actually call three separate send functions; res->writeStatus, res->writeHeader and whatever it does itself. By wrapping the call in res->cork you make sure these three send functions are efficient and only result in one single send syscall and one single SSL block if using SSL.
|
||||
|
||||
Keep this in mind, corking is by far the single most important performance trick to use. Even when streaming huge amounts of data it can be useful to cork. At least in the very tip of the response, as that holds the headers and status.
|
||||
|
||||
### The App.ws route
|
||||
WebSocket "routes" are registered similarly, but not identically.
|
||||
|
||||
Every websocket route has the same pattern and pattern matching as for Http, but instead of one single callback you have a whole set of them, here's an example:
|
||||
|
||||
```c++
|
||||
uWS::App().ws<PerSocketData>("/*", {
|
||||
/* Settings */
|
||||
.compression = uWS::SHARED_COMPRESSOR,
|
||||
.maxPayloadLength = 16 * 1024,
|
||||
.idleTimeout = 10,
|
||||
/* Handlers */
|
||||
.open = [](auto *ws, auto *req) {
|
||||
/* Here you can use req just like as for Http */
|
||||
},
|
||||
.message = [](auto *ws, std::string_view message, uWS::OpCode opCode) {
|
||||
ws->send(message, opCode);
|
||||
},
|
||||
.drain = [](auto *ws) {
|
||||
/* Check getBufferedAmount here */
|
||||
},
|
||||
.ping = [](auto *ws) {
|
||||
|
||||
},
|
||||
.pong = [](auto *ws) {
|
||||
|
||||
},
|
||||
.close = [](auto *ws, int code, std::string_view message) {
|
||||
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
WebSocket routes specify a user data type that should be used to keep per-websocket data. Many times people tend to attach user data
|
||||
which should belong to the websocket by putting the pointer and the user data in a std::map. That's wrong! Don't do that!
|
||||
|
||||
#### Use the WebSocket.getUserData() feature
|
||||
You should use the provided user data feature to store and attach any per-socket user data. Going from user data to WebSocket is possible if you make your user data hold a pointer to WebSocket, and hook things up in the WebSocket open handler. Your user data memory is valid while your WebSocket is.
|
||||
|
||||
If you want to create something more elaborate you could have the user data hold a pointer to some dynamically allocated memory block that keeps a boolean whether the WebSocket is still valid or not. Sky is the limit here, you should never need any std::map for this.
|
||||
|
||||
#### WebSockets are valid from open to close
|
||||
All given WebSocket pointers are guaranteed to live from open event (where you got your WebSocket) until close event is called. So is the user data memory. One open event will always end in exactly one close event, they are 1-to-1 and will always be balanced no matter what. Use them to drive your RAII data types, they can be seen as constructor and destructor.
|
||||
|
||||
Message events will never emit outside of open/close. Calling WebSocket.close or WebSocket.end will immediately call the close handler.
|
||||
|
||||
#### Backpressure in websockets
|
||||
Similarly to for Http, methods such as ws.send(...) can cause backpressure. Make sure to check ws.getBufferedAmount() before sending, and check the return value of ws.send before sending any more data. WebSockets do not have .onWritable, but instead make use of the .drain handler of the websocket route handler.
|
||||
|
||||
Inside of .drain event you should check ws.getBufferedAmount(), it might have drained, or even increased. Most likely drained but don't assume that it has, .drain event is only a hint that it has changed.
|
||||
|
||||
#### Settings
|
||||
Compression (permessage-deflate) has three options, uWS::DISABLED, uWS::SHARED_COMPRESSOR and uWS::DEDICATED_COMPRESSOR. Disabled and shared options require no memory, while dedicated compressor requires somewhere close to 300kb per socket, a very significant cost.
|
||||
|
||||
Compressing using shared means that every WebSocket message is an isolated compression stream, it does not have a sliding compression window, kept between multiple send calls.
|
||||
|
||||
Shared compression is my personal favorite, since it doesn't change memory usage while still provide decent compression, especially for larger messages.
|
||||
|
||||
* idleTimeout is roughly the amount of seconds that may pass between messages. Being idle for more than this, and the connection is severed. This means you should make your clients send small ping messages every now and then, to keep the connection alive. You can also make the server send ping messages but I would definitely put that labor on the client side.
|
||||
|
||||
### Listening on a port
|
||||
Once you have defined your routes and their behavior, it is time to start listening for new connections. You do this by calling
|
||||
|
||||
```c++
|
||||
App.listen(port, [](auto *listenSocket) {
|
||||
/* listenSocket is either nullptr or us_listen_socket */
|
||||
})
|
||||
```
|
||||
|
||||
Cancelling listenning is done with the uSockets function call `us_listen_socket_close`.
|
||||
|
||||
### App.run and fallthrough
|
||||
When you are done and want to enter the event loop, you call, once and only once, App.run.
|
||||
This will block the calling thread until "fallthrough". The event loop will block until no more async work is scheduled, just like for Node.js.
|
||||
|
||||
Many users ask how they should stop the event loop. That's not how it is done, you never stop it, you let it fall through. By closing all sockets, stopping the listen socket, removing any timers, etc, the loop will automatically cause App.run to return gracefully, with no memory leaks.
|
||||
|
||||
Because the App itself is under RAII control, once the blocking .run call returns and the App goes out of scope, all memory will gracefully be deleted.
|
||||
|
||||
### Putting it all toghether
|
||||
|
||||
```c++
|
||||
int main() {
|
||||
uWS::App().get("/*", [](auto *res, auto *req) {
|
||||
res->end("Hello World!");
|
||||
}).listen(9001, [](auto *listenSocket) {
|
||||
if (listenSocket) {
|
||||
std::cout << "Listening for connections..." << std::endl;
|
||||
}
|
||||
}).run();
|
||||
|
||||
std::cout << "Shoot! We failed to listen and the App fell through, exiting now!" << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
### Scaling up
|
||||
|
||||
One event-loop per thread, isolated and without shared data. That's the design here. Just like Node.js, but instead of per-process, it's per thread (well, obviously you can do it per-process also).
|
||||
|
||||
If you want to, you can simply take the previous example, put it inside of a few `std::thread` and listen to separate ports, or share the same port (works on Linux). More features like these will probably come, such as master/slave set-ups but it really isn't that hard to understand the concept - keep things isolated and spawn multiple instances of whatever code you have.
|
||||
|
||||
Recent Node.js versions may scale using multiple threads, via the new Worker threads support. Scaling using that feature is identical to scaling using multiple threads in C++.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user