Compare commits
58 Commits
bsergean-p
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
9884c325dd | ||
|
c27f5a94bd | ||
|
2d47af89cf | ||
|
c106e6cb24 | ||
|
1d210c0139 | ||
|
dc8807ec9d | ||
|
03e5a6970f | ||
|
38d6da7755 | ||
|
9ef61bf224 | ||
|
93e673da9f | ||
|
92beef8348 | ||
|
755d98d918 | ||
|
98b4828e93 | ||
|
39e085bebc | ||
|
70602c4e6b | ||
|
c5a02f1066 | ||
|
e03c0be8a4 | ||
|
3b66efbb6a | ||
|
f29906c72f | ||
|
872f516ede | ||
|
014d43eb13 | ||
|
d77067e50f | ||
|
ed5b1a0895 | ||
|
ef57e3a2b1 | ||
|
28832f8732 | ||
|
0dd284267a | ||
|
a7019631b7 | ||
|
632ee31509 | ||
|
688af99747 | ||
|
397bb5d18a | ||
|
f79c64ae97 | ||
|
bc765e73a3 | ||
|
dfa10df5ae | ||
|
eb9a7bed76 | ||
|
d20864d7d1 | ||
|
f184a7adef | ||
|
dc7b986e10 | ||
|
1e3560014f | ||
|
9157873f5b | ||
|
aa2ca19895 | ||
|
6cc21f3658 | ||
|
679ce519dd | ||
|
a5d4911a16 | ||
|
b0fd119d14 | ||
|
472cf68c31 | ||
|
1e46466114 | ||
|
0b8b5608dc | ||
|
20a028e2ae | ||
|
8d7b557be6 | ||
|
e417e63605 | ||
|
7b1524d7ec | ||
|
e8048ad826 | ||
|
2b40a30c8f | ||
|
d7bfe89e43 | ||
|
84aa652846 | ||
|
edb6ded99f | ||
|
2f560ff4c0 | ||
|
002d9c8985 |
19
.github/workflows/stale.yml
vendored
19
.github/workflows/stale.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
name: Mark stale issues and pull requests
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v1
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
stale-issue-message: 'Stale issue message'
|
|
||||||
stale-pr-message: 'Stale pull request message'
|
|
||||||
stale-issue-label: 'no-issue-activity'
|
|
||||||
stale-pr-label: 'no-pr-activity'
|
|
27
.github/workflows/unittest_windows.yml
vendored
27
.github/workflows/unittest_windows.yml
vendored
@ -1,27 +0,0 @@
|
|||||||
name: windows
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
windows:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: seanmiddleditch/gha-setup-vsdevenv@master
|
|
||||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
|
||||||
- run: |
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
cmake -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe -DUSE_WS=1 -DUSE_TEST=1 -DUSE_ZLIB=OFF -DBUILD_SHARED_LIBS=OFF ..
|
|
||||||
- run: |
|
|
||||||
cd build
|
|
||||||
ninja
|
|
||||||
- run: |
|
|
||||||
cd build
|
|
||||||
ninja test
|
|
||||||
|
|
||||||
#- run: ../build/test/ixwebsocket_unittest.exe
|
|
||||||
# working-directory: test
|
|
2
.github/workflows/unittest_windows_gcc.yml
vendored
2
.github/workflows/unittest_windows_gcc.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||||
- uses: egor-tensin/setup-mingw@v2
|
- uses: bsergean/setup-mingw@d79ce405bac9edef3a1726ef00554a56f0bafe66
|
||||||
- run: |
|
- run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ ws/.srl
|
|||||||
ixhttpd
|
ixhttpd
|
||||||
makefile
|
makefile
|
||||||
a.out
|
a.out
|
||||||
|
.idea/
|
||||||
|
cmake-build-debug/
|
||||||
|
@ -6,11 +6,12 @@
|
|||||||
cmake_minimum_required(VERSION 3.4.1...3.17.2)
|
cmake_minimum_required(VERSION 3.4.1...3.17.2)
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
||||||
|
|
||||||
project(ixwebsocket C CXX)
|
project(ixwebsocket LANGUAGES C CXX VERSION 11.4.6)
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
set (CMAKE_CXX_STANDARD 11)
|
||||||
set (CXX_STANDARD_REQUIRED ON)
|
set (CXX_STANDARD_REQUIRED ON)
|
||||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
|
||||||
|
|
||||||
option (BUILD_DEMO OFF)
|
option (BUILD_DEMO OFF)
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
|
ixwebsocket/IXBase64.h
|
||||||
ixwebsocket/IXBench.h
|
ixwebsocket/IXBench.h
|
||||||
ixwebsocket/IXCancellationRequest.h
|
ixwebsocket/IXCancellationRequest.h
|
||||||
ixwebsocket/IXConnectionState.h
|
ixwebsocket/IXConnectionState.h
|
||||||
@ -134,6 +136,7 @@ if (USE_TLS)
|
|||||||
else() # default to OpenSSL on all other platforms
|
else() # default to OpenSSL on all other platforms
|
||||||
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||||
set(USE_OPEN_SSL ON)
|
set(USE_OPEN_SSL ON)
|
||||||
|
set(requires "openssl")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -165,8 +168,7 @@ if(BUILD_SHARED_LIBS)
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Set library version
|
# Set library version
|
||||||
set_target_properties(ixwebsocket PROPERTIES VERSION 11.3.2)
|
set_target_properties(ixwebsocket PROPERTIES VERSION ${PROJECT_VERSION})
|
||||||
|
|
||||||
else()
|
else()
|
||||||
# Static library
|
# Static library
|
||||||
add_library( ixwebsocket
|
add_library( ixwebsocket
|
||||||
@ -249,7 +251,7 @@ if (WIN32)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX AND NOT APPLE)
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
|
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
|
||||||
@ -285,8 +287,12 @@ if (IXWEBSOCKET_INSTALL)
|
|||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
|
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
|
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
|
||||||
install(FILES "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
|
||||||
|
|
||||||
|
set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||||
|
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" @ONLY)
|
||||||
|
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||||
|
|
||||||
install(EXPORT ixwebsocket
|
install(EXPORT ixwebsocket
|
||||||
FILE ixwebsocket-targets.cmake
|
FILE ixwebsocket-targets.cmake
|
||||||
|
14
README.md
14
README.md
@ -1,5 +1,7 @@
|
|||||||
## Hello world
|
## Hello world
|
||||||
|
|
||||||
|
(note from the main developer, sadly I don't have too much time to devote to this library anymore, maybe it's time to pass the maintenance to someone else more motivated ?)
|
||||||
|
|
||||||
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.
|
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.
|
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.
|
||||||
@ -35,6 +37,7 @@ int main()
|
|||||||
|
|
||||||
// Connect to a server with encryption
|
// Connect to a server with encryption
|
||||||
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
|
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
|
||||||
|
// https://github.com/machinezone/IXWebSocket/issues/386#issuecomment-1105235227 (self signed certificates)
|
||||||
std::string url("wss://echo.websocket.org");
|
std::string url("wss://echo.websocket.org");
|
||||||
webSocket.setUrl(url);
|
webSocket.setUrl(url);
|
||||||
|
|
||||||
@ -98,14 +101,16 @@ Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
|
|||||||
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
If your company or project is using this library, feel free to open an issue or PR to amend this list.
|
||||||
|
|
||||||
- [Machine Zone](https://www.mz.com)
|
- [Machine Zone](https://www.mz.com)
|
||||||
- [Tokio](https://gitlab.com/HCInk/tokio), a discord library focused on audio playback with node bindings.
|
- [Tokio](https://github.com/liz3/tokio), a discord library focused on audio playback with node bindings.
|
||||||
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
|
- [libDiscordBot](https://github.com/tostc/libDiscordBot/tree/master), an easy to use Discord-bot framework.
|
||||||
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
|
- [gwebsocket](https://github.com/norrbotten/gwebsocket), a websocket (lua) module for Garry's Mod
|
||||||
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper
|
- [DisCPP](https://github.com/DisCPP/DisCPP), a simple but feature rich Discord API wrapper (archived as of Oct 8, 2021)
|
||||||
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
|
- [discord.cpp](https://github.com/luccanunes/discord.cpp), a discord library for making bots
|
||||||
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
|
- [Teleport](http://teleportconnect.com/), Teleport is your own personal remote robot avatar
|
||||||
- [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm
|
- [Abaddon](https://github.com/uowuo/abaddon), An alternative Discord client made with C++/gtkmm
|
||||||
- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency.
|
- [NovaCoin](https://github.com/novacoin-project/novacoin), a hybrid scrypt PoW + PoS based cryptocurrency.
|
||||||
|
- [Candy](https://github.com/lanthora/candy), A WebSocket and TUN based VPN for Linux
|
||||||
|
- [ITGmania](https://github.com/itgmania/itgmania), a cross platform Dance Dance Revolution-like emulator.
|
||||||
|
|
||||||
## Alternative libraries
|
## Alternative libraries
|
||||||
|
|
||||||
@ -113,8 +118,8 @@ There are plenty of great websocket libraries out there, which might work for yo
|
|||||||
|
|
||||||
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++
|
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++
|
||||||
* [beast](https://github.com/boostorg/beast) - C++
|
* [beast](https://github.com/boostorg/beast) - C++
|
||||||
|
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C++
|
||||||
* [libwebsockets](https://libwebsockets.org/) - C
|
* [libwebsockets](https://libwebsockets.org/) - C
|
||||||
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C
|
|
||||||
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
|
* [wslay](https://github.com/tatsuhiro-t/wslay) - C
|
||||||
|
|
||||||
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.
|
[uvweb](https://github.com/bsergean/uvweb) is a library written by the IXWebSocket author which is built on top of [uvw](https://github.com/skypjack/uvw), which is a C++ wrapper for [libuv](https://libuv.org/). It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.
|
||||||
@ -132,9 +137,7 @@ To check the performance of a websocket library, you can look at the [autoroute]
|
|||||||
| Windows | Disabled | None | [![Build2][5]][0] |
|
| Windows | Disabled | None | [![Build2][5]][0] |
|
||||||
| UWP | Disabled | None | [![Build2][6]][0] |
|
| UWP | Disabled | None | [![Build2][6]][0] |
|
||||||
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
|
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
|
||||||
| Mingw | Disabled | None | [![Build2][8]][0] |
|
|
||||||
|
|
||||||
* ASAN fails on Linux because of a known problem, we need a
|
|
||||||
* Some tests are disabled on Windows/UWP because of a pathing problem
|
* Some tests are disabled on Windows/UWP because of a pathing problem
|
||||||
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
|
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
|
||||||
|
|
||||||
@ -146,5 +149,4 @@ To check the performance of a websocket library, you can look at the [autoroute]
|
|||||||
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
|
[5]: https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg
|
||||||
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
|
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg
|
||||||
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
|
[7]: https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg
|
||||||
[8]: https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg
|
|
||||||
|
|
||||||
|
@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
All changes to this project will be documented in this file.
|
All changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [11.4.5] - 2024-06-05
|
||||||
|
|
||||||
|
New changes are documented in the Release page in the GitHub repository.
|
||||||
|
|
||||||
|
## [11.4.4] - 2023-06-05
|
||||||
|
|
||||||
|
## [11.4.3] - 2022-05-13
|
||||||
|
|
||||||
|
Set shorter thread names
|
||||||
|
BoringSSL fix with SNI
|
||||||
|
Websocket computed header is valid Base64
|
||||||
|
|
||||||
## [11.4.1] - 2022-04-23
|
## [11.4.1] - 2022-04-23
|
||||||
|
|
||||||
vckpg + cmake fix, to handle zlib as a dependency better
|
vckpg + cmake fix, to handle zlib as a dependency better
|
||||||
|
@ -54,7 +54,7 @@ To use the installed package within a cmake project, use the following:
|
|||||||
# include headers
|
# include headers
|
||||||
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
|
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
|
||||||
# ...
|
# ...
|
||||||
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUNS
|
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUND
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -301,7 +301,9 @@ This api was actually changed to take a weak_ptr<WebSocket> as the first argumen
|
|||||||
|
|
||||||
// Run a server on localhost at a given port.
|
// Run a server on localhost at a given port.
|
||||||
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
||||||
ix::WebSocketServer server(port);
|
int port = 8008;
|
||||||
|
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
|
||||||
|
ix::WebSocketServer server(port, host);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::weak_ptr<WebSocket> webSocket,
|
[&server](std::weak_ptr<WebSocket> webSocket,
|
||||||
@ -384,7 +386,9 @@ The webSocket reference is guaranteed to be always valid ; by design the callbac
|
|||||||
|
|
||||||
// Run a server on localhost at a given port.
|
// Run a server on localhost at a given port.
|
||||||
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
// Bound host name, max connections and listen backlog can also be passed in as parameters.
|
||||||
ix::WebSocketServer server(port);
|
int port = 8008;
|
||||||
|
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
|
||||||
|
ix::WebSocketServer server(port, host);
|
||||||
|
|
||||||
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
|
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
|
||||||
// The ConnectionState object contains information about the connection,
|
// The ConnectionState object contains information about the connection,
|
||||||
@ -441,6 +445,17 @@ server.wait();
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Heartbeat
|
||||||
|
|
||||||
|
You can configure an optional heartbeat / keep-alive for the WebSocket server. The heartbeat interval can be adjusted or disabled when constructing the `WebSocketServer`. Setting the interval to `-1` disables the heartbeat feature; this is the default setting. The parameter you set will be applied to every `WebSocket` object that the server creates.
|
||||||
|
|
||||||
|
To enable a 45 second heartbeat on a `WebSocketServer`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int pingIntervalSeconds = 45;
|
||||||
|
ix::WebSocketServer server(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily, pingIntervalSeconds);
|
||||||
|
```
|
||||||
|
|
||||||
## HTTP client API
|
## HTTP client API
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@ -624,3 +639,5 @@ For a client, specifying `caFile` can be used if connecting to a server that use
|
|||||||
For a server, specifying `caFile` implies that:
|
For a server, specifying `caFile` implies that:
|
||||||
1. You require clients to present a certificate
|
1. You require clients to present a certificate
|
||||||
1. It must be signed by one of the trusted roots in the file
|
1. It must be signed by one of the trusted roots in the file
|
||||||
|
|
||||||
|
By default, a destination's hostname is always validated against the certificate that it presents. To accept certificates with any hostname, set `ix::SocketTLSOptions::disable_hostname_validation` to `true`.
|
||||||
|
11
ixwebsocket.pc.in
Normal file
11
ixwebsocket.pc.in
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
prefix=@prefix@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${exec_prefix}/lib
|
||||||
|
includedir=${prefix}/include
|
||||||
|
|
||||||
|
Name: ixwebsocket
|
||||||
|
Description: websocket and http client and server library, with TLS support and very few dependencies
|
||||||
|
Version: @CMAKE_PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lixwebsocket
|
||||||
|
Cflags: -I${includedir}
|
||||||
|
Requires: @requires@
|
125
ixwebsocket/IXBase64.h
Normal file
125
ixwebsocket/IXBase64.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#ifndef _MACARON_BASE64_H_
|
||||||
|
#define _MACARON_BASE64_H_
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MIT License (MIT)
|
||||||
|
* Copyright (c) 2016 tomykaira
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace macaron {
|
||||||
|
|
||||||
|
class Base64 {
|
||||||
|
public:
|
||||||
|
|
||||||
|
static std::string Encode(const std::string data) {
|
||||||
|
static constexpr char sEncodingTable[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||||
|
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||||
|
'4', '5', '6', '7', '8', '9', '+', '/'
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t in_len = data.size();
|
||||||
|
size_t out_len = 4 * ((in_len + 2) / 3);
|
||||||
|
std::string ret(out_len, '\0');
|
||||||
|
size_t i;
|
||||||
|
char *p = const_cast<char*>(ret.c_str());
|
||||||
|
|
||||||
|
for (i = 0; i < in_len - 2; i += 3) {
|
||||||
|
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
|
||||||
|
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
|
||||||
|
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
|
||||||
|
*p++ = sEncodingTable[data[i + 2] & 0x3F];
|
||||||
|
}
|
||||||
|
if (i < in_len) {
|
||||||
|
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
|
||||||
|
if (i == (in_len - 1)) {
|
||||||
|
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
|
||||||
|
*p++ = '=';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
|
||||||
|
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
|
||||||
|
}
|
||||||
|
*p++ = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string Decode(const std::string& input, std::string& out) {
|
||||||
|
static constexpr unsigned char kDecodingTable[] = {
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
||||||
|
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
||||||
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t in_len = input.size();
|
||||||
|
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
|
||||||
|
|
||||||
|
size_t out_len = in_len / 4 * 3;
|
||||||
|
if (input[in_len - 1] == '=') out_len--;
|
||||||
|
if (input[in_len - 2] == '=') out_len--;
|
||||||
|
|
||||||
|
out.resize(out_len);
|
||||||
|
|
||||||
|
for (size_t i = 0, j = 0; i < in_len;) {
|
||||||
|
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||||
|
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||||
|
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||||
|
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
|
||||||
|
|
||||||
|
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
|
||||||
|
|
||||||
|
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
|
||||||
|
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
|
||||||
|
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _MACARON_BASE64_H_ */
|
@ -6,7 +6,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <stdint.h>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
|
@ -23,12 +23,23 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
// mingw build quirks
|
// mingw build quirks
|
||||||
#if defined(_WIN32) && defined(__GNUC__)
|
#if defined(_WIN32) && defined(__GNUC__)
|
||||||
|
#ifndef AI_NUMERICSERV
|
||||||
#define AI_NUMERICSERV NI_NUMERICSERV
|
#define AI_NUMERICSERV NI_NUMERICSERV
|
||||||
|
#endif
|
||||||
|
#ifndef AI_ADDRCONFIG
|
||||||
#define AI_ADDRCONFIG LUP_ADDRCONFIG
|
#define AI_ADDRCONFIG LUP_ADDRCONFIG
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#ifndef AI_NUMERICSERV
|
||||||
|
#define AI_NUMERICSERV 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -44,7 +55,7 @@ namespace ix
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
@ -63,10 +74,10 @@ namespace ix
|
|||||||
errMsg = gai_strerror(getaddrinfo_result);
|
errMsg = gai_strerror(getaddrinfo_result);
|
||||||
res = nullptr;
|
res = nullptr;
|
||||||
}
|
}
|
||||||
return res;
|
return AddrInfoPtr{ res, freeaddrinfo };
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
|
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested,
|
const CancellationRequest& isCancellationRequested,
|
||||||
bool cancellable)
|
bool cancellable)
|
||||||
{
|
{
|
||||||
@ -74,12 +85,7 @@ namespace ix
|
|||||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::release(struct addrinfo* addr)
|
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable(
|
||||||
{
|
|
||||||
freeaddrinfo(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveUnCancellable(
|
|
||||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||||
{
|
{
|
||||||
errMsg = "no error";
|
errMsg = "no error";
|
||||||
@ -94,7 +100,7 @@ namespace ix
|
|||||||
return getAddrInfo(_hostname, _port, errMsg);
|
return getAddrInfo(_hostname, _port, errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveCancellable(
|
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable(
|
||||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||||
{
|
{
|
||||||
errMsg = "no error";
|
errMsg = "no error";
|
||||||
@ -157,7 +163,7 @@ namespace ix
|
|||||||
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
||||||
// getAddrInfo needs to work.
|
// getAddrInfo needs to work.
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
auto res = getAddrInfo(hostname, port, errMsg);
|
||||||
|
|
||||||
if (auto lock = self.lock())
|
if (auto lock = self.lock())
|
||||||
{
|
{
|
||||||
@ -181,13 +187,13 @@ namespace ix
|
|||||||
return _errMsg;
|
return _errMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::setRes(struct addrinfo* addr)
|
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
std::lock_guard<std::mutex> lock(_resMutex);
|
||||||
_res = addr;
|
_res = std::move(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getRes()
|
DNSLookup::AddrInfoPtr DNSLookup::getRes()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
std::lock_guard<std::mutex> lock(_resMutex);
|
||||||
return _res;
|
return _res;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <set>
|
||||||
@ -24,22 +25,21 @@ namespace ix
|
|||||||
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
|
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using AddrInfoPtr = std::shared_ptr<addrinfo>;
|
||||||
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
||||||
~DNSLookup() = default;
|
~DNSLookup() = default;
|
||||||
|
|
||||||
struct addrinfo* resolve(std::string& errMsg,
|
AddrInfoPtr resolve(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested,
|
const CancellationRequest& isCancellationRequested,
|
||||||
bool cancellable = true);
|
bool cancellable = true);
|
||||||
|
|
||||||
void release(struct addrinfo* addr);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct addrinfo* resolveCancellable(std::string& errMsg,
|
AddrInfoPtr resolveCancellable(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
struct addrinfo* resolveUnCancellable(std::string& errMsg,
|
AddrInfoPtr resolveUnCancellable(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
|
|
||||||
static struct addrinfo* getAddrInfo(const std::string& hostname,
|
AddrInfoPtr getAddrInfo(const std::string& hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg);
|
std::string& errMsg);
|
||||||
|
|
||||||
@ -48,15 +48,15 @@ namespace ix
|
|||||||
void setErrMsg(const std::string& errMsg);
|
void setErrMsg(const std::string& errMsg);
|
||||||
const std::string& getErrMsg();
|
const std::string& getErrMsg();
|
||||||
|
|
||||||
void setRes(struct addrinfo* addr);
|
void setRes(AddrInfoPtr addr);
|
||||||
struct addrinfo* getRes();
|
AddrInfoPtr getRes();
|
||||||
|
|
||||||
std::string _hostname;
|
std::string _hostname;
|
||||||
int _port;
|
int _port;
|
||||||
int64_t _wait;
|
int64_t _wait;
|
||||||
const static int64_t kDefaultWait;
|
const static int64_t kDefaultWait;
|
||||||
|
|
||||||
struct addrinfo* _res;
|
AddrInfoPtr _res;
|
||||||
std::mutex _resMutex;
|
std::mutex _resMutex;
|
||||||
|
|
||||||
std::string _errMsg;
|
std::string _errMsg;
|
||||||
|
@ -133,16 +133,20 @@ namespace ix
|
|||||||
if (headers.find("Content-Length") != headers.end())
|
if (headers.find("Content-Length") != headers.end())
|
||||||
{
|
{
|
||||||
int contentLength = 0;
|
int contentLength = 0;
|
||||||
try
|
|
||||||
{
|
|
||||||
contentLength = std::stoi(headers["Content-Length"]);
|
|
||||||
}
|
|
||||||
catch (const std::exception&)
|
|
||||||
{
|
{
|
||||||
|
const char* p = headers["Content-Length"].c_str();
|
||||||
|
char* p_end{};
|
||||||
|
errno = 0;
|
||||||
|
long val = std::strtol(p, &p_end, 10);
|
||||||
|
if (p_end == p // invalid argument
|
||||||
|
|| errno == ERANGE // out of range
|
||||||
|
|| val < std::numeric_limits<int>::min()
|
||||||
|
|| val > std::numeric_limits<int>::max()) {
|
||||||
return std::make_tuple(
|
return std::make_tuple(
|
||||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
||||||
}
|
}
|
||||||
|
contentLength = val;
|
||||||
|
}
|
||||||
if (contentLength < 0)
|
if (contentLength < 0)
|
||||||
{
|
{
|
||||||
return std::make_tuple(
|
return std::make_tuple(
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "IXProgressCallback.h"
|
#include "IXProgressCallback.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "IXUserAgent.h"
|
#include "IXUserAgent.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
@ -139,8 +140,9 @@ namespace ix
|
|||||||
|
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
|
bool isProtocolDefaultPort;
|
||||||
|
|
||||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
if (!UrlParser::parse(url, protocol, host, path, query, port, isProtocolDefaultPort))
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot parse url: " << url;
|
ss << "Cannot parse url: " << url;
|
||||||
@ -173,7 +175,12 @@ namespace ix
|
|||||||
// Build request string
|
// Build request string
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
ss << verb << " " << path << " HTTP/1.1\r\n";
|
||||||
ss << "Host: " << host << "\r\n";
|
ss << "Host: " << host;
|
||||||
|
if (!isProtocolDefaultPort)
|
||||||
|
{
|
||||||
|
ss << ":" << port;
|
||||||
|
}
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_ZLIB
|
#ifdef IXWEBSOCKET_USE_ZLIB
|
||||||
if (args->compress && !args->onChunkCallback)
|
if (args->compress && !args->onChunkCallback)
|
||||||
@ -202,6 +209,12 @@ namespace ix
|
|||||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set an origin header if missing
|
||||||
|
if (args->extraHeaders.find("Origin") == args->extraHeaders.end())
|
||||||
|
{
|
||||||
|
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
|
||||||
{
|
{
|
||||||
// Set request compression header
|
// Set request compression header
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXUserAgent.h"
|
#include "IXUserAgent.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -40,6 +41,29 @@ namespace
|
|||||||
auto vec = res.second;
|
auto vec = res.second;
|
||||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string response_head_file(const std::string& file_name){
|
||||||
|
|
||||||
|
if (std::string::npos != file_name.find(".html") || std::string::npos != file_name.find(".htm"))
|
||||||
|
return "text/html";
|
||||||
|
else if (std::string::npos != file_name.find(".css"))
|
||||||
|
return "text/css";
|
||||||
|
else if (std::string::npos != file_name.find(".js") || std::string::npos != file_name.find(".mjs"))
|
||||||
|
return "application/x-javascript";
|
||||||
|
else if (std::string::npos != file_name.find(".ico"))
|
||||||
|
return "image/x-icon";
|
||||||
|
else if (std::string::npos != file_name.find(".png"))
|
||||||
|
return "image/png";
|
||||||
|
else if (std::string::npos != file_name.find(".jpg") || std::string::npos != file_name.find(".jpeg"))
|
||||||
|
return "image/jpeg";
|
||||||
|
else if (std::string::npos != file_name.find(".gif"))
|
||||||
|
return "image/gif";
|
||||||
|
else if (std::string::npos != file_name.find(".svg"))
|
||||||
|
return "image/svg+xml";
|
||||||
|
else
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -51,28 +75,14 @@ namespace ix
|
|||||||
int backlog,
|
int backlog,
|
||||||
size_t maxConnections,
|
size_t maxConnections,
|
||||||
int addressFamily,
|
int addressFamily,
|
||||||
int timeoutSecs)
|
int timeoutSecs,
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
int handshakeTimeoutSecs)
|
||||||
, _connectedClientsCount(0)
|
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily)
|
||||||
, _timeoutSecs(timeoutSecs)
|
, _timeoutSecs(timeoutSecs)
|
||||||
{
|
{
|
||||||
setDefaultConnectionCallback();
|
setDefaultConnectionCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpServer::~HttpServer()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpServer::stop()
|
|
||||||
{
|
|
||||||
stopAcceptingConnections();
|
|
||||||
|
|
||||||
// FIXME: cancelling / closing active clients ...
|
|
||||||
|
|
||||||
SocketServer::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
||||||
{
|
{
|
||||||
_onConnectionCallback = callback;
|
_onConnectionCallback = callback;
|
||||||
@ -81,34 +91,35 @@ namespace ix
|
|||||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState)
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
_connectedClientsCount++;
|
|
||||||
|
|
||||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
||||||
// FIXME: handle errors in parseRequest
|
// FIXME: handle errors in parseRequest
|
||||||
|
|
||||||
if (std::get<0>(ret))
|
if (std::get<0>(ret))
|
||||||
{
|
{
|
||||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
auto request = std::get<2>(ret);
|
||||||
|
std::shared_ptr<ix::HttpResponse> response;
|
||||||
|
if (request->headers["Upgrade"] == "websocket")
|
||||||
|
{
|
||||||
|
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto response = _onConnectionCallback(request, connectionState);
|
||||||
if (!Http::sendResponse(response, socket))
|
if (!Http::sendResponse(response, socket))
|
||||||
{
|
{
|
||||||
logError("Cannot send response");
|
logError("Cannot send response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connectionState->setTerminated();
|
|
||||||
|
|
||||||
_connectedClientsCount--;
|
|
||||||
}
|
}
|
||||||
|
connectionState->setTerminated();
|
||||||
size_t HttpServer::getConnectedClientsCount()
|
|
||||||
{
|
|
||||||
return _connectedClientsCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpServer::setDefaultConnectionCallback()
|
void HttpServer::setDefaultConnectionCallback()
|
||||||
{
|
{
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this](HttpRequestPtr request,
|
[this](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||||
|
{
|
||||||
std::string uri(request->uri);
|
std::string uri(request->uri);
|
||||||
if (uri.empty() || uri == "/")
|
if (uri.empty() || uri == "/")
|
||||||
{
|
{
|
||||||
@ -117,6 +128,7 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Server"] = userAgent();
|
headers["Server"] = userAgent();
|
||||||
|
headers["Content-Type"] = response_head_file(uri);
|
||||||
|
|
||||||
std::string path("." + uri);
|
std::string path("." + uri);
|
||||||
auto res = readAsString(path);
|
auto res = readAsString(path);
|
||||||
@ -136,6 +148,7 @@ namespace ix
|
|||||||
content = gzipCompress(content);
|
content = gzipCompress(content);
|
||||||
headers["Content-Encoding"] = "gzip";
|
headers["Content-Encoding"] = "gzip";
|
||||||
}
|
}
|
||||||
|
headers["Accept-Encoding"] = "gzip";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Log request
|
// Log request
|
||||||
@ -149,11 +162,6 @@ namespace ix
|
|||||||
// headers["Content-Type"] = "application/octet-stream";
|
// headers["Content-Type"] = "application/octet-stream";
|
||||||
headers["Accept-Ranges"] = "none";
|
headers["Accept-Ranges"] = "none";
|
||||||
|
|
||||||
for (auto&& it : request->headers)
|
|
||||||
{
|
|
||||||
headers[it.first] = it.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(
|
return std::make_shared<HttpResponse>(
|
||||||
200, "OK", HttpErrorCode::Ok, headers, content);
|
200, "OK", HttpErrorCode::Ok, headers, content);
|
||||||
});
|
});
|
||||||
@ -165,9 +173,9 @@ namespace ix
|
|||||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||||
//
|
//
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this,
|
[this, redirectUrl](HttpRequestPtr request,
|
||||||
redirectUrl](HttpRequestPtr request,
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
{
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Server"] = userAgent();
|
headers["Server"] = userAgent();
|
||||||
|
|
||||||
@ -198,7 +206,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
setOnConnectionCallback(
|
setOnConnectionCallback(
|
||||||
[this](HttpRequestPtr request,
|
[this](HttpRequestPtr request,
|
||||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||||
|
{
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Server"] = userAgent();
|
headers["Server"] = userAgent();
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXHttp.h"
|
#include "IXHttp.h"
|
||||||
#include "IXSocketServer.h"
|
|
||||||
#include "IXWebSocket.h"
|
#include "IXWebSocket.h"
|
||||||
|
#include "IXWebSocketServer.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class HttpServer final : public SocketServer
|
class HttpServer final : public WebSocketServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using OnConnectionCallback =
|
using OnConnectionCallback =
|
||||||
@ -30,9 +30,8 @@ namespace ix
|
|||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||||
int addressFamily = SocketServer::kDefaultAddressFamily,
|
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||||
int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
|
int timeoutSecs = HttpServer::kDefaultTimeoutSecs,
|
||||||
virtual ~HttpServer();
|
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
|
||||||
virtual void stop() final;
|
|
||||||
|
|
||||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||||
|
|
||||||
@ -41,10 +40,10 @@ namespace ix
|
|||||||
void makeDebugServer();
|
void makeDebugServer();
|
||||||
|
|
||||||
int getTimeoutSecs();
|
int getTimeoutSecs();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Member variables
|
// Member variables
|
||||||
OnConnectionCallback _onConnectionCallback;
|
OnConnectionCallback _onConnectionCallback;
|
||||||
std::atomic<int> _connectedClientsCount;
|
|
||||||
|
|
||||||
const static int kDefaultTimeoutSecs;
|
const static int kDefaultTimeoutSecs;
|
||||||
int _timeoutSecs;
|
int _timeoutSecs;
|
||||||
@ -52,7 +51,6 @@ namespace ix
|
|||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||||
std::shared_ptr<ConnectionState> connectionState) final;
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
virtual size_t getConnectedClientsCount() final;
|
|
||||||
|
|
||||||
void setDefaultConnectionCallback();
|
void setDefaultConnectionCallback();
|
||||||
};
|
};
|
||||||
|
@ -69,7 +69,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// We must deselect the networkevents from the socket event. Otherwise the
|
// We must deselect the networkevents from the socket event. Otherwise the
|
||||||
// socket will report states that aren't there.
|
// socket will report states that aren't there.
|
||||||
if (_fd != nullptr && _fd->fd != -1)
|
if (_fd != nullptr && (int)_fd->fd != -1)
|
||||||
WSAEventSelect(_fd->fd, _event, 0);
|
WSAEventSelect(_fd->fd, _event, 0);
|
||||||
WSACloseEvent(_event);
|
WSACloseEvent(_event);
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ namespace ix
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
|
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
|
||||||
// all the signaled events must be processed.
|
// all the signaled events must be processed.
|
||||||
while (socketIndex < socketEvents.size())
|
while (socketIndex < (int)socketEvents.size())
|
||||||
{
|
{
|
||||||
struct pollfd* fd = socketEvents[socketIndex];
|
struct pollfd* fd = socketEvents[socketIndex];
|
||||||
|
|
||||||
@ -345,7 +345,7 @@ namespace ix
|
|||||||
buf[best] = buf[best + 1] = ':';
|
buf[best] = buf[best + 1] = ':';
|
||||||
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
|
memmove(buf + best + 2, buf + best + max, i - best - max + 1);
|
||||||
}
|
}
|
||||||
if (strlen(buf) < l)
|
if (strlen(buf) < (size_t)l)
|
||||||
{
|
{
|
||||||
strcpy(s, buf);
|
strcpy(s, buf);
|
||||||
return s;
|
return s;
|
||||||
|
@ -6,6 +6,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
#include "IXSelectInterrupt.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -34,8 +34,12 @@ namespace ix
|
|||||||
|
|
||||||
SelectInterruptPipe::~SelectInterruptPipe()
|
SelectInterruptPipe::~SelectInterruptPipe()
|
||||||
{
|
{
|
||||||
|
if (-1 != _fildes[kPipeReadIndex]) {
|
||||||
::close(_fildes[kPipeReadIndex]);
|
::close(_fildes[kPipeReadIndex]);
|
||||||
|
}
|
||||||
|
if (-1 != _fildes[kPipeWriteIndex]) {
|
||||||
::close(_fildes[kPipeWriteIndex]);
|
::close(_fildes[kPipeWriteIndex]);
|
||||||
|
}
|
||||||
_fildes[kPipeReadIndex] = -1;
|
_fildes[kPipeReadIndex] = -1;
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
_fildes[kPipeWriteIndex] = -1;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
#include "IXSelectInterrupt.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
#include <pthread_np.h>
|
#include <pthread_np.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <AvailabilityMacros.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Windows
|
// Windows
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@ -58,7 +62,7 @@ namespace ix
|
|||||||
|
|
||||||
void setThreadName(const std::string& name)
|
void setThreadName(const std::string& name)
|
||||||
{
|
{
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
|
||||||
//
|
//
|
||||||
// Apple reserves 16 bytes for its thread names
|
// Apple reserves 16 bytes for its thread names
|
||||||
// Notice that the Apple version of pthread_setname_np
|
// Notice that the Apple version of pthread_setname_np
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
@ -7,11 +7,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <basetsd.h>
|
#include <basetsd.h>
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
@ -205,6 +205,8 @@ namespace ix
|
|||||||
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
||||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
||||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||||
|
|
||||||
|
if (!_tlsOptions.disable_hostname_validation)
|
||||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||||
|
|
||||||
if (_tlsOptions.isPeerVerifyDisabled())
|
if (_tlsOptions.isPeerVerifyDisabled())
|
||||||
|
@ -102,7 +102,7 @@ namespace ix
|
|||||||
// First do DNS resolution
|
// First do DNS resolution
|
||||||
//
|
//
|
||||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||||
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
auto res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||||
if (res == nullptr)
|
if (res == nullptr)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
@ -112,7 +112,7 @@ namespace ix
|
|||||||
|
|
||||||
// iterate through the records to find a working peer
|
// iterate through the records to find a working peer
|
||||||
struct addrinfo* address;
|
struct addrinfo* address;
|
||||||
for (address = res; address != nullptr; address = address->ai_next)
|
for (address = res.get(); address != nullptr; address = address->ai_next)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Second try to connect to the remote host
|
// Second try to connect to the remote host
|
||||||
@ -124,7 +124,6 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
freeaddrinfo(res);
|
|
||||||
return sockfd;
|
return sockfd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -46,6 +47,13 @@ namespace ix
|
|||||||
mbedtls_x509_crt_init(&_cacert);
|
mbedtls_x509_crt_init(&_cacert);
|
||||||
mbedtls_x509_crt_init(&_cert);
|
mbedtls_x509_crt_init(&_cert);
|
||||||
mbedtls_pk_init(&_pkey);
|
mbedtls_pk_init(&_pkey);
|
||||||
|
// Initialize the PSA Crypto API if required by the version of Mbed TLS (3.6.0).
|
||||||
|
// This allows the X.509/TLS libraries to use PSA for crypto operations.
|
||||||
|
// See: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/use-psa-crypto.md
|
||||||
|
if (MBEDTLS_VERSION_MAJOR >= 3 && MBEDTLS_VERSION_MINOR >= 6 && MBEDTLS_VERSION_PATCH >= 0)
|
||||||
|
{
|
||||||
|
psa_crypto_init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
|
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
|
||||||
@ -195,11 +203,14 @@ namespace ix
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_tlsOptions.disable_hostname_validation)
|
||||||
|
{
|
||||||
if (!host.empty() && 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";
|
errMsg = "SNI setup failed";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -348,6 +359,11 @@ namespace ix
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
errno = ECONNRESET;
|
||||||
|
}
|
||||||
|
|
||||||
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||||
{
|
{
|
||||||
errno = EWOULDBLOCK;
|
errno = EWOULDBLOCK;
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// For manipulating the certificate store
|
// For manipulating the certificate store
|
||||||
|
#include <windows.h>
|
||||||
#include <wincrypt.h>
|
#include <wincrypt.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ namespace
|
|||||||
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
|
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
|
||||||
|
|
||||||
int certificateCount = 0;
|
int certificateCount = 0;
|
||||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
while ((certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator)))
|
||||||
{
|
{
|
||||||
X509* x509 = d2i_X509(NULL,
|
X509* x509 = d2i_X509(NULL,
|
||||||
(const unsigned char**) &certificateIterator->pbCertEncoded,
|
(const unsigned char**) &certificateIterator->pbCertEncoded,
|
||||||
@ -293,15 +294,25 @@ namespace ix
|
|||||||
*/
|
*/
|
||||||
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
||||||
{
|
{
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return PathMatchSpecA(host.c_str(), pattern);
|
return PathMatchSpecA(host.c_str(), pattern);
|
||||||
#else
|
#else
|
||||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
|
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
const std::string& hostname,
|
const std::string& hostname,
|
||||||
|
#else
|
||||||
|
const std::string& /* hostname */,
|
||||||
|
#endif
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
{
|
{
|
||||||
X509* server_cert = SSL_get_peer_certificate(ssl);
|
X509* server_cert = SSL_get_peer_certificate(ssl);
|
||||||
@ -390,6 +401,11 @@ namespace ix
|
|||||||
int connect_result = SSL_connect(_ssl_connection);
|
int connect_result = SSL_connect(_ssl_connection);
|
||||||
if (connect_result == 1)
|
if (connect_result == 1)
|
||||||
{
|
{
|
||||||
|
if (_tlsOptions.disable_hostname_validation)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
|
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
|
||||||
}
|
}
|
||||||
int reason = SSL_get_error(_ssl_connection, connect_result);
|
int reason = SSL_get_error(_ssl_connection, connect_result);
|
||||||
@ -754,8 +770,11 @@ namespace ix
|
|||||||
// (The docs say that this should work from 1.0.2, and is the default from
|
// (The docs say that this should work from 1.0.2, and is the default from
|
||||||
// 1.1.0, but it does not. To be on the safe side, the manual test
|
// 1.1.0, but it does not. To be on the safe side, the manual test
|
||||||
// below is enabled for all versions prior to 1.1.0.)
|
// below is enabled for all versions prior to 1.1.0.)
|
||||||
|
if (!_tlsOptions.disable_hostname_validation)
|
||||||
|
{
|
||||||
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
|
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
|
||||||
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
|
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
|
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
|
||||||
}
|
}
|
||||||
|
@ -219,6 +219,10 @@ namespace ix
|
|||||||
if (_gcThread.joinable())
|
if (_gcThread.joinable())
|
||||||
{
|
{
|
||||||
_stopGc = true;
|
_stopGc = true;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
|
||||||
|
_canContinueGC = true;
|
||||||
|
}
|
||||||
_conditionVariableGC.notify_one();
|
_conditionVariableGC.notify_one();
|
||||||
_gcThread.join();
|
_gcThread.join();
|
||||||
_stopGc = false;
|
_stopGc = false;
|
||||||
@ -268,7 +272,10 @@ namespace ix
|
|||||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||||
SocketConnect::configure(_serverFd);
|
SocketConnect::configure(_serverFd);
|
||||||
|
|
||||||
setThreadName("SocketServer::accept");
|
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
|
||||||
|
// $ echo Srv:gc:64000 | wc -c
|
||||||
|
// 13
|
||||||
|
setThreadName("Srv:ac:" + std::to_string(_port));
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@ -425,7 +432,10 @@ namespace ix
|
|||||||
|
|
||||||
void SocketServer::runGC()
|
void SocketServer::runGC()
|
||||||
{
|
{
|
||||||
setThreadName("SocketServer::GC");
|
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
|
||||||
|
// $ echo Srv:gc:64000 | wc -c
|
||||||
|
// 13
|
||||||
|
setThreadName("Srv:gc:" + std::to_string(_port));
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@ -445,7 +455,10 @@ namespace ix
|
|||||||
if (!_stopGc)
|
if (!_stopGc)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
|
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
|
||||||
_conditionVariableGC.wait(lock);
|
if(!_canContinueGC) {
|
||||||
|
_conditionVariableGC.wait(lock, [this]{ return _canContinueGC; });
|
||||||
|
}
|
||||||
|
_canContinueGC = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -459,6 +472,10 @@ namespace ix
|
|||||||
{
|
{
|
||||||
// a connection got terminated, we can run the connection thread GC,
|
// a connection got terminated, we can run the connection thread GC,
|
||||||
// so wake up the thread responsible for that
|
// so wake up the thread responsible for that
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
|
||||||
|
_canContinueGC = true;
|
||||||
|
}
|
||||||
_conditionVariableGC.notify_one();
|
_conditionVariableGC.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,5 +126,6 @@ namespace ix
|
|||||||
// as a connection
|
// as a connection
|
||||||
std::condition_variable _conditionVariableGC;
|
std::condition_variable _conditionVariableGC;
|
||||||
std::mutex _conditionVariableMutexGC;
|
std::mutex _conditionVariableMutexGC;
|
||||||
|
bool _canContinueGC{ false };
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -33,6 +33,9 @@ namespace ix
|
|||||||
// whether tls is enabled, used for server code
|
// whether tls is enabled, used for server code
|
||||||
bool tls = false;
|
bool tls = false;
|
||||||
|
|
||||||
|
// whether to skip validating the peer's hostname against the certificate presented
|
||||||
|
bool disable_hostname_validation = false;
|
||||||
|
|
||||||
bool hasCertAndKey() const;
|
bool hasCertAndKey() const;
|
||||||
|
|
||||||
bool isUsingSystemDefaults() const;
|
bool isUsingSystemDefaults() const;
|
||||||
|
@ -180,7 +180,7 @@ namespace
|
|||||||
bHasUserName = true;
|
bHasUserName = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (*LocalString == '/')
|
else if (*LocalString == '/' || *LocalString == '?')
|
||||||
{
|
{
|
||||||
// end of <host>:<port> specification
|
// end of <host>:<port> specification
|
||||||
bHasUserName = false;
|
bHasUserName = false;
|
||||||
@ -242,7 +242,7 @@ namespace
|
|||||||
LocalString++;
|
LocalString++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/' || *LocalString == '?'))
|
||||||
{
|
{
|
||||||
// port number is specified
|
// port number is specified
|
||||||
break;
|
break;
|
||||||
@ -280,12 +280,14 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip '/'
|
// skip '/'
|
||||||
if (*CurrentString != '/')
|
if (*CurrentString != '/' && *CurrentString != '?')
|
||||||
{
|
{
|
||||||
return clParseURL(LUrlParserError_NoSlash);
|
return clParseURL(LUrlParserError_NoSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (*CurrentString != '?') {
|
||||||
CurrentString++;
|
CurrentString++;
|
||||||
|
}
|
||||||
|
|
||||||
// parse the path
|
// parse the path
|
||||||
LocalString = CurrentString;
|
LocalString = CurrentString;
|
||||||
@ -333,6 +335,19 @@ namespace
|
|||||||
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getProtocolPort(const std::string& protocol)
|
||||||
|
{
|
||||||
|
if (protocol == "ws" || protocol == "http")
|
||||||
|
{
|
||||||
|
return 80;
|
||||||
|
}
|
||||||
|
else if (protocol == "wss" || protocol == "https")
|
||||||
|
{
|
||||||
|
return 443;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
@ -343,6 +358,18 @@ namespace ix
|
|||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port)
|
int& port)
|
||||||
|
{
|
||||||
|
bool isProtocolDefaultPort;
|
||||||
|
return parse(url, protocol, host, path, query, port, isProtocolDefaultPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UrlParser::parse(const std::string& url,
|
||||||
|
std::string& protocol,
|
||||||
|
std::string& host,
|
||||||
|
std::string& path,
|
||||||
|
std::string& query,
|
||||||
|
int& port,
|
||||||
|
bool& isProtocolDefaultPort)
|
||||||
{
|
{
|
||||||
clParseURL res = clParseURL::ParseURL(url);
|
clParseURL res = clParseURL::ParseURL(url);
|
||||||
|
|
||||||
@ -356,23 +383,12 @@ namespace ix
|
|||||||
path = res.m_Path;
|
path = res.m_Path;
|
||||||
query = res.m_Query;
|
query = res.m_Query;
|
||||||
|
|
||||||
|
const auto protocolPort = getProtocolPort(protocol);
|
||||||
if (!res.GetPort(&port))
|
if (!res.GetPort(&port))
|
||||||
{
|
{
|
||||||
if (protocol == "ws" || protocol == "http")
|
port = protocolPort;
|
||||||
{
|
|
||||||
port = 80;
|
|
||||||
}
|
|
||||||
else if (protocol == "wss" || protocol == "https")
|
|
||||||
{
|
|
||||||
port = 443;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Invalid protocol. Should be caught by regex check
|
|
||||||
// but this missing branch trigger cpplint linter.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
isProtocolDefaultPort = port == protocolPort;
|
||||||
|
|
||||||
if (path.empty())
|
if (path.empty())
|
||||||
{
|
{
|
||||||
|
@ -19,5 +19,13 @@ namespace ix
|
|||||||
std::string& path,
|
std::string& path,
|
||||||
std::string& query,
|
std::string& query,
|
||||||
int& port);
|
int& port);
|
||||||
|
|
||||||
|
static bool parse(const std::string& url,
|
||||||
|
std::string& protocol,
|
||||||
|
std::string& host,
|
||||||
|
std::string& path,
|
||||||
|
std::string& query,
|
||||||
|
int& port,
|
||||||
|
bool& isProtocolDefaultPort);
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "IXUuid.h"
|
#include "IXUuid.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@ -39,9 +40,12 @@ namespace ix
|
|||||||
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
|
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
|
||||||
, _enablePong(kDefaultEnablePong)
|
, _enablePong(kDefaultEnablePong)
|
||||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||||
|
, _pingType(SendMessageKind::Ping)
|
||||||
|
, _autoThreadName(true)
|
||||||
{
|
{
|
||||||
_ws.setOnCloseCallback(
|
_ws.setOnCloseCallback(
|
||||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
|
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
|
||||||
|
{
|
||||||
_onMessageCallback(
|
_onMessageCallback(
|
||||||
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||||
emptyMsg,
|
emptyMsg,
|
||||||
@ -100,6 +104,17 @@ namespace ix
|
|||||||
return _perMessageDeflateOptions;
|
return _perMessageDeflateOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocket::setPingMessage(const std::string& sendMessage, SendMessageKind pingType)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
|
_pingMessage = sendMessage;
|
||||||
|
_ws.setPingMessage(_pingMessage, pingType);
|
||||||
|
}
|
||||||
|
const std::string WebSocket::getPingMessage() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
|
return _pingMessage;
|
||||||
|
}
|
||||||
void WebSocket::setPingInterval(int pingIntervalSecs)
|
void WebSocket::setPingInterval(int pingIntervalSecs)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
@ -194,7 +209,7 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketHttpHeaders headers(_extraHeaders);
|
WebSocketHttpHeaders headers(_extraHeaders);
|
||||||
std::string subProtocolsHeader;
|
std::string subProtocolsHeader;
|
||||||
auto subProtocols = getSubProtocols();
|
const auto &subProtocols = getSubProtocols();
|
||||||
if (!subProtocols.empty())
|
if (!subProtocols.empty())
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@ -204,7 +219,7 @@ namespace ix
|
|||||||
// 'json,msgpack'
|
// 'json,msgpack'
|
||||||
//
|
//
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (auto subProtocol : subProtocols)
|
for (const auto & subProtocol : subProtocols)
|
||||||
{
|
{
|
||||||
if (i++ != 0)
|
if (i++ != 0)
|
||||||
{
|
{
|
||||||
@ -232,7 +247,7 @@ namespace ix
|
|||||||
if (_pingIntervalSecs > 0)
|
if (_pingIntervalSecs > 0)
|
||||||
{
|
{
|
||||||
// Send a heart beat right away
|
// Send a heart beat right away
|
||||||
_ws.sendHeartBeat();
|
_ws.sendHeartBeat(_pingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -240,7 +255,8 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
|
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
|
||||||
int timeoutSecs,
|
int timeoutSecs,
|
||||||
bool enablePerMessageDeflate)
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
@ -249,7 +265,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketInitResult status =
|
WebSocketInitResult status =
|
||||||
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate);
|
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request);
|
||||||
if (!status.success)
|
if (!status.success)
|
||||||
{
|
{
|
||||||
return status;
|
return status;
|
||||||
@ -266,7 +282,7 @@ namespace ix
|
|||||||
if (_pingIntervalSecs > 0)
|
if (_pingIntervalSecs > 0)
|
||||||
{
|
{
|
||||||
// Send a heart beat right away
|
// Send a heart beat right away
|
||||||
_ws.sendHeartBeat();
|
_ws.sendHeartBeat(_pingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
@ -354,8 +370,11 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::run()
|
void WebSocket::run()
|
||||||
|
{
|
||||||
|
if (_autoThreadName)
|
||||||
{
|
{
|
||||||
setThreadName(getUrl());
|
setThreadName(getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
bool firstConnectionAttempt = true;
|
bool firstConnectionAttempt = true;
|
||||||
|
|
||||||
@ -384,7 +403,8 @@ namespace ix
|
|||||||
[this](const std::string& msg,
|
[this](const std::string& msg,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
bool decompressionError,
|
bool decompressionError,
|
||||||
WebSocketTransport::MessageKind messageKind) {
|
WebSocketTransport::MessageKind messageKind)
|
||||||
|
{
|
||||||
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
|
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
|
||||||
switch (messageKind)
|
switch (messageKind)
|
||||||
{
|
{
|
||||||
@ -503,13 +523,13 @@ namespace ix
|
|||||||
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
|
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
WebSocketSendInfo WebSocket::ping(const std::string& text, SendMessageKind pingType)
|
||||||
{
|
{
|
||||||
// Standard limit ping message size
|
// Standard limit ping message size
|
||||||
constexpr size_t pingMaxPayloadSize = 125;
|
constexpr size_t pingMaxPayloadSize = 125;
|
||||||
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
||||||
|
|
||||||
return sendMessage(text, SendMessageKind::Ping);
|
return sendMessage(text, pingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
|
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
|
||||||
@ -611,4 +631,9 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
return _subProtocols;
|
return _subProtocols;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebSocket::setAutoThreadName(bool enabled)
|
||||||
|
{
|
||||||
|
_autoThreadName = enabled;
|
||||||
|
}
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -16,11 +16,12 @@
|
|||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXWebSocketMessage.h"
|
#include "IXWebSocketMessage.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include "IXWebSocketSendData.h"
|
#include "IXWebSocketSendData.h"
|
||||||
|
#include "IXWebSocketSendInfo.h"
|
||||||
#include "IXWebSocketTransport.h"
|
#include "IXWebSocketTransport.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <cstdint>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@ -53,6 +54,8 @@ namespace ix
|
|||||||
void setPerMessageDeflateOptions(
|
void setPerMessageDeflateOptions(
|
||||||
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||||
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
|
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
|
||||||
|
void setPingMessage(const std::string& sendMessage,
|
||||||
|
SendMessageKind pingType = SendMessageKind::Ping);
|
||||||
void setPingInterval(int pingIntervalSecs);
|
void setPingInterval(int pingIntervalSecs);
|
||||||
void enablePong();
|
void enablePong();
|
||||||
void disablePong();
|
void disablePong();
|
||||||
@ -88,7 +91,7 @@ namespace ix
|
|||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo sendText(const std::string& text,
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
WebSocketSendInfo ping(const std::string& text,SendMessageKind pingType = SendMessageKind::Ping);
|
||||||
|
|
||||||
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
||||||
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
||||||
@ -103,6 +106,7 @@ namespace ix
|
|||||||
|
|
||||||
const std::string getUrl() const;
|
const std::string getUrl() const;
|
||||||
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
|
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
|
||||||
|
const std::string getPingMessage() const;
|
||||||
int getPingInterval() const;
|
int getPingInterval() const;
|
||||||
size_t bufferedAmount() const;
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
@ -115,6 +119,8 @@ namespace ix
|
|||||||
uint32_t getMinWaitBetweenReconnectionRetries() const;
|
uint32_t getMinWaitBetweenReconnectionRetries() const;
|
||||||
const std::vector<std::string>& getSubProtocols();
|
const std::vector<std::string>& getSubProtocols();
|
||||||
|
|
||||||
|
void setAutoThreadName(bool enabled);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message,
|
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message,
|
||||||
SendMessageKind sendMessageKind,
|
SendMessageKind sendMessageKind,
|
||||||
@ -128,7 +134,8 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
|
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
|
||||||
int timeoutSecs,
|
int timeoutSecs,
|
||||||
bool enablePerMessageDeflate);
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request = nullptr);
|
||||||
|
|
||||||
WebSocketTransport _ws;
|
WebSocketTransport _ws;
|
||||||
|
|
||||||
@ -169,12 +176,17 @@ namespace ix
|
|||||||
// Optional ping and pong timeout
|
// Optional ping and pong timeout
|
||||||
int _pingIntervalSecs;
|
int _pingIntervalSecs;
|
||||||
int _pingTimeoutSecs;
|
int _pingTimeoutSecs;
|
||||||
|
std::string _pingMessage;
|
||||||
|
SendMessageKind _pingType;
|
||||||
static const int kDefaultPingIntervalSecs;
|
static const int kDefaultPingIntervalSecs;
|
||||||
static const int kDefaultPingTimeoutSecs;
|
static const int kDefaultPingTimeoutSecs;
|
||||||
|
|
||||||
// Subprotocols
|
// Subprotocols
|
||||||
std::vector<std::string> _subProtocols;
|
std::vector<std::string> _subProtocols;
|
||||||
|
|
||||||
|
// enable or disable auto set thread name
|
||||||
|
bool _autoThreadName;
|
||||||
|
|
||||||
friend class WebSocketServer;
|
friend class WebSocketServer;
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
|
|
||||||
|
#include "IXBase64.h"
|
||||||
#include "IXHttp.h"
|
#include "IXHttp.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXStrCaseCompare.h"
|
#include "IXStrCaseCompare.h"
|
||||||
@ -17,7 +18,6 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
WebSocketHandshake::WebSocketHandshake(
|
WebSocketHandshake::WebSocketHandshake(
|
||||||
@ -87,6 +87,7 @@ namespace ix
|
|||||||
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
||||||
const std::string& url,
|
const std::string& url,
|
||||||
const WebSocketHttpHeaders& extraHeaders,
|
const WebSocketHttpHeaders& extraHeaders,
|
||||||
|
const std::string& protocol,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& path,
|
const std::string& path,
|
||||||
int port,
|
int port,
|
||||||
@ -106,15 +107,10 @@ namespace ix
|
|||||||
return WebSocketInitResult(false, 0, ss.str());
|
return WebSocketInitResult(false, 0, ss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// Generate a random 16 bytes string and base64 encode it.
|
||||||
// Generate a random 24 bytes string which looks like it is base64 encoded
|
|
||||||
// y3JJHMbDL1EzLkh9GBhXDw==
|
|
||||||
// 0cb3Vd9HkbpVVumoS3Noka==
|
|
||||||
//
|
//
|
||||||
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
||||||
//
|
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
|
||||||
std::string secWebSocketKey = genRandomString(22);
|
|
||||||
secWebSocketKey += "==";
|
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
ss << "GET " << path << " HTTP/1.1\r\n";
|
||||||
@ -130,6 +126,12 @@ namespace ix
|
|||||||
ss << "User-Agent: " << userAgent() << "\r\n";
|
ss << "User-Agent: " << userAgent() << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set an origin header if missing
|
||||||
|
if (extraHeaders.find("Origin") == extraHeaders.end())
|
||||||
|
{
|
||||||
|
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& it : extraHeaders)
|
for (auto& it : extraHeaders)
|
||||||
{
|
{
|
||||||
ss << it.first << ": " << it.second << "\r\n";
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
@ -245,13 +247,26 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
|
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
|
||||||
bool enablePerMessageDeflate)
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request)
|
||||||
{
|
{
|
||||||
_requestInitCancellation = false;
|
_requestInitCancellation = false;
|
||||||
|
|
||||||
auto isCancellationRequested =
|
auto isCancellationRequested =
|
||||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
||||||
|
|
||||||
|
std::string method;
|
||||||
|
std::string uri;
|
||||||
|
std::string httpVersion;
|
||||||
|
|
||||||
|
if (request)
|
||||||
|
{
|
||||||
|
method = request->method;
|
||||||
|
uri = request->uri;
|
||||||
|
httpVersion = request->version;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Read first line
|
// Read first line
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
@ -264,9 +279,10 @@ namespace ix
|
|||||||
|
|
||||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||||
auto requestLine = Http::parseRequestLine(line);
|
auto requestLine = Http::parseRequestLine(line);
|
||||||
auto method = std::get<0>(requestLine);
|
method = std::get<0>(requestLine);
|
||||||
auto uri = std::get<1>(requestLine);
|
uri = std::get<1>(requestLine);
|
||||||
auto httpVersion = std::get<2>(requestLine);
|
httpVersion = std::get<2>(requestLine);
|
||||||
|
}
|
||||||
|
|
||||||
if (method != "GET")
|
if (method != "GET")
|
||||||
{
|
{
|
||||||
@ -279,15 +295,23 @@ namespace ix
|
|||||||
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
if (request)
|
||||||
|
{
|
||||||
|
headers = request->headers;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Retrieve and validate HTTP headers
|
// Retrieve and validate HTTP headers
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||||
auto headersValid = result.first;
|
auto headersValid = result.first;
|
||||||
auto headers = result.second;
|
headers = result.second;
|
||||||
|
|
||||||
if (!headersValid)
|
if (!headersValid)
|
||||||
{
|
{
|
||||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
return sendErrorResponse(400, "Error parsing HTTP headers");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (headers.find("sec-websocket-key") == headers.end())
|
if (headers.find("sec-websocket-key") == headers.end())
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXHttp.h"
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXWebSocketInitResult.h"
|
#include "IXWebSocketInitResult.h"
|
||||||
@ -30,12 +31,15 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketInitResult clientHandshake(const std::string& url,
|
WebSocketInitResult clientHandshake(const std::string& url,
|
||||||
const WebSocketHttpHeaders& extraHeaders,
|
const WebSocketHttpHeaders& extraHeaders,
|
||||||
|
const std::string& protocol,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& path,
|
const std::string& path,
|
||||||
int port,
|
int port,
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
|
|
||||||
WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate);
|
WebSocketInitResult serverHandshake(int timeoutSecs,
|
||||||
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string genRandomString(const int len);
|
std::string genRandomString(const int len);
|
||||||
|
@ -46,6 +46,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
#include "IXWebSocketPerMessageDeflate.h"
|
||||||
|
|
||||||
#include "IXUniquePtr.h"
|
#include "IXUniquePtr.h"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
#endif
|
#endif
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "IXWebSocketSendData.h"
|
#include "IXWebSocketSendData.h"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
|
@ -57,7 +57,7 @@ namespace ix
|
|||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
||||||
std::shared_ptr<ConnectionState> connectionState) {
|
std::shared_ptr<ConnectionState> connectionState) {
|
||||||
auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
|
auto state = std::static_pointer_cast<ProxyConnectionState>(connectionState);
|
||||||
auto remoteIp = connectionState->getRemoteIp();
|
auto remoteIp = connectionState->getRemoteIp();
|
||||||
|
|
||||||
// Server connection
|
// Server connection
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
@ -19,17 +19,20 @@ namespace ix
|
|||||||
{
|
{
|
||||||
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
||||||
const bool WebSocketServer::kDefaultEnablePong(true);
|
const bool WebSocketServer::kDefaultEnablePong(true);
|
||||||
|
const int WebSocketServer::kPingIntervalSeconds(-1); // disable heartbeat
|
||||||
|
|
||||||
WebSocketServer::WebSocketServer(int port,
|
WebSocketServer::WebSocketServer(int port,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
int backlog,
|
int backlog,
|
||||||
size_t maxConnections,
|
size_t maxConnections,
|
||||||
int handshakeTimeoutSecs,
|
int handshakeTimeoutSecs,
|
||||||
int addressFamily)
|
int addressFamily,
|
||||||
|
int pingIntervalSeconds)
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||||
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
||||||
, _enablePong(kDefaultEnablePong)
|
, _enablePong(kDefaultEnablePong)
|
||||||
, _enablePerMessageDeflate(true)
|
, _enablePerMessageDeflate(true)
|
||||||
|
, _pingIntervalSeconds(pingIntervalSeconds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,9 +82,22 @@ namespace ix
|
|||||||
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState)
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
setThreadName("WebSocketServer::" + connectionState->getId());
|
handleUpgrade(std::move(socket), connectionState);
|
||||||
|
|
||||||
|
connectionState->setTerminated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebSocketServer::handleUpgrade(std::unique_ptr<Socket> socket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
|
HttpRequestPtr request)
|
||||||
|
{
|
||||||
|
setThreadName("Srv:ws:" + connectionState->getId());
|
||||||
|
|
||||||
auto webSocket = std::make_shared<WebSocket>();
|
auto webSocket = std::make_shared<WebSocket>();
|
||||||
|
|
||||||
|
webSocket->setAutoThreadName(false);
|
||||||
|
webSocket->setPingInterval(_pingIntervalSeconds);
|
||||||
|
|
||||||
if (_onConnectionCallback)
|
if (_onConnectionCallback)
|
||||||
{
|
{
|
||||||
_onConnectionCallback(webSocket, connectionState);
|
_onConnectionCallback(webSocket, connectionState);
|
||||||
@ -89,7 +105,7 @@ namespace ix
|
|||||||
if (!webSocket->isOnMessageCallbackRegistered())
|
if (!webSocket->isOnMessageCallbackRegistered())
|
||||||
{
|
{
|
||||||
logError("WebSocketServer Application developer error: Server callback improperly "
|
logError("WebSocketServer Application developer error: Server callback improperly "
|
||||||
"registerered.");
|
"registered.");
|
||||||
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
|
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
|
||||||
connectionState->setTerminated();
|
connectionState->setTerminated();
|
||||||
return;
|
return;
|
||||||
@ -99,9 +115,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
WebSocket* webSocketRawPtr = webSocket.get();
|
WebSocket* webSocketRawPtr = webSocket.get();
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
|
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg)
|
||||||
_onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
|
{ _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -130,7 +145,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto status = webSocket->connectToSocket(
|
auto status = webSocket->connectToSocket(
|
||||||
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate);
|
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate, request);
|
||||||
if (status.success)
|
if (status.success)
|
||||||
{
|
{
|
||||||
// Process incoming messages and execute callbacks
|
// Process incoming messages and execute callbacks
|
||||||
@ -155,8 +170,6 @@ namespace ix
|
|||||||
logError("Cannot delete client");
|
logError("Cannot delete client");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState->setTerminated();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
||||||
@ -176,9 +189,11 @@ namespace ix
|
|||||||
//
|
//
|
||||||
void WebSocketServer::makeBroadcastServer()
|
void WebSocketServer::makeBroadcastServer()
|
||||||
{
|
{
|
||||||
setOnClientMessageCallback([this](std::shared_ptr<ConnectionState> connectionState,
|
setOnClientMessageCallback(
|
||||||
|
[this](std::shared_ptr<ConnectionState> connectionState,
|
||||||
WebSocket& webSocket,
|
WebSocket& webSocket,
|
||||||
const WebSocketMessagePtr& msg) {
|
const WebSocketMessagePtr& msg)
|
||||||
|
{
|
||||||
auto remoteIp = connectionState->getRemoteIp();
|
auto remoteIp = connectionState->getRemoteIp();
|
||||||
if (msg->type == ix::WebSocketMessageType::Message)
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,8 @@ namespace ix
|
|||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
||||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||||
|
int pingIntervalSeconds = WebSocketServer::kPingIntervalSeconds);
|
||||||
virtual ~WebSocketServer();
|
virtual ~WebSocketServer();
|
||||||
virtual void stop() final;
|
virtual void stop() final;
|
||||||
|
|
||||||
@ -55,11 +56,13 @@ namespace ix
|
|||||||
int getHandshakeTimeoutSecs();
|
int getHandshakeTimeoutSecs();
|
||||||
bool isPongEnabled();
|
bool isPongEnabled();
|
||||||
bool isPerMessageDeflateEnabled();
|
bool isPerMessageDeflateEnabled();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Member variables
|
// Member variables
|
||||||
int _handshakeTimeoutSecs;
|
int _handshakeTimeoutSecs;
|
||||||
bool _enablePong;
|
bool _enablePong;
|
||||||
bool _enablePerMessageDeflate;
|
bool _enablePerMessageDeflate;
|
||||||
|
int _pingIntervalSeconds;
|
||||||
|
|
||||||
OnConnectionCallback _onConnectionCallback;
|
OnConnectionCallback _onConnectionCallback;
|
||||||
OnClientMessageCallback _onClientMessageCallback;
|
OnClientMessageCallback _onClientMessageCallback;
|
||||||
@ -68,10 +71,16 @@ namespace ix
|
|||||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||||
|
|
||||||
const static bool kDefaultEnablePong;
|
const static bool kDefaultEnablePong;
|
||||||
|
const static int kPingIntervalSeconds;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
||||||
std::shared_ptr<ConnectionState> connectionState);
|
std::shared_ptr<ConnectionState> connectionState);
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void handleUpgrade(std::unique_ptr<Socket> socket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState,
|
||||||
|
HttpRequestPtr request = nullptr);
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
@ -54,7 +53,6 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
|
|
||||||
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
||||||
const bool WebSocketTransport::kDefaultEnablePong(true);
|
const bool WebSocketTransport::kDefaultEnablePong(true);
|
||||||
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
|
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
|
||||||
@ -74,6 +72,9 @@ namespace ix
|
|||||||
, _enablePong(kDefaultEnablePong)
|
, _enablePong(kDefaultEnablePong)
|
||||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||||
, _pongReceived(false)
|
, _pongReceived(false)
|
||||||
|
, _setCustomMessage(false)
|
||||||
|
, _kPingMessage("ixwebsocket::heartbeat")
|
||||||
|
, _pingType(SendMessageKind::Ping)
|
||||||
, _pingCount(0)
|
, _pingCount(0)
|
||||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||||
{
|
{
|
||||||
@ -139,7 +140,7 @@ namespace ix
|
|||||||
_enablePerMessageDeflate);
|
_enablePerMessageDeflate);
|
||||||
|
|
||||||
result = webSocketHandshake.clientHandshake(
|
result = webSocketHandshake.clientHandshake(
|
||||||
remoteUrl, headers, host, path, port, timeoutSecs);
|
remoteUrl, headers, protocol, host, path, port, timeoutSecs);
|
||||||
|
|
||||||
if (result.http_status >= 300 && result.http_status < 400)
|
if (result.http_status >= 300 && result.http_status < 400)
|
||||||
{
|
{
|
||||||
@ -170,7 +171,8 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
|
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
|
||||||
int timeoutSecs,
|
int timeoutSecs,
|
||||||
bool enablePerMessageDeflate)
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
|
||||||
@ -187,7 +189,8 @@ namespace ix
|
|||||||
_perMessageDeflateOptions,
|
_perMessageDeflateOptions,
|
||||||
_enablePerMessageDeflate);
|
_enablePerMessageDeflate);
|
||||||
|
|
||||||
auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate);
|
auto result =
|
||||||
|
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
|
||||||
if (result.success)
|
if (result.success)
|
||||||
{
|
{
|
||||||
setReadyState(ReadyState::OPEN);
|
setReadyState(ReadyState::OPEN);
|
||||||
@ -248,14 +251,52 @@ namespace ix
|
|||||||
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
|
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocketTransport::sendHeartBeat()
|
void WebSocketTransport::setPingMessage(const std::string& message, SendMessageKind pingType)
|
||||||
|
{
|
||||||
|
_setCustomMessage = true;
|
||||||
|
_kPingMessage = message;
|
||||||
|
_pingType = pingType;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketSendInfo WebSocketTransport::sendHeartBeat(SendMessageKind pingMessage)
|
||||||
{
|
{
|
||||||
_pongReceived = false;
|
_pongReceived = false;
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
|
|
||||||
|
ss << _kPingMessage;
|
||||||
|
if (!_setCustomMessage)
|
||||||
|
{
|
||||||
|
ss << "::" << _pingIntervalSecs << "s"
|
||||||
<< "::" << _pingCount++;
|
<< "::" << _pingCount++;
|
||||||
|
}
|
||||||
|
if (pingMessage == SendMessageKind::Ping)
|
||||||
|
{
|
||||||
return sendPing(ss.str());
|
return sendPing(ss.str());
|
||||||
}
|
}
|
||||||
|
else if (pingMessage == SendMessageKind::Binary)
|
||||||
|
{
|
||||||
|
WebSocketSendInfo info = sendBinary(ss.str(), nullptr);
|
||||||
|
if (info.success)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
|
||||||
|
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
else if (pingMessage == SendMessageKind::Text)
|
||||||
|
{
|
||||||
|
WebSocketSendInfo info = sendText(ss.str(), nullptr);
|
||||||
|
if (info.success)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
|
||||||
|
_lastSendPingTimePoint = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknow type ping message
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
bool WebSocketTransport::closingDelayExceeded()
|
bool WebSocketTransport::closingDelayExceeded()
|
||||||
{
|
{
|
||||||
@ -270,7 +311,9 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (pingIntervalExceeded())
|
if (pingIntervalExceeded())
|
||||||
{
|
{
|
||||||
if (!_pongReceived)
|
// If it is not a 'ping' message of ping type, there is no need to judge whether
|
||||||
|
// pong will receive it
|
||||||
|
if (_pingType == SendMessageKind::Ping && !_pongReceived)
|
||||||
{
|
{
|
||||||
// ping response (PONG) exceeds the maximum delay, close the connection
|
// ping response (PONG) exceeds the maximum delay, close the connection
|
||||||
close(WebSocketCloseConstants::kInternalErrorCode,
|
close(WebSocketCloseConstants::kInternalErrorCode,
|
||||||
@ -278,7 +321,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sendHeartBeat();
|
sendHeartBeat(_pingType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -657,6 +700,7 @@ namespace ix
|
|||||||
if (_readyState != ReadyState::CLOSING)
|
if (_readyState != ReadyState::CLOSING)
|
||||||
{
|
{
|
||||||
// send back the CLOSE frame
|
// send back the CLOSE frame
|
||||||
|
setReadyState(ReadyState::CLOSING);
|
||||||
sendCloseFrame(code, reason);
|
sendCloseFrame(code, reason);
|
||||||
|
|
||||||
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
|
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
|
||||||
@ -1029,7 +1073,10 @@ namespace ix
|
|||||||
else if (ret <= 0)
|
else if (ret <= 0)
|
||||||
{
|
{
|
||||||
closeSocket();
|
closeSocket();
|
||||||
|
if (_readyState != ReadyState::CLOSING)
|
||||||
|
{
|
||||||
setReadyState(ReadyState::CLOSED);
|
setReadyState(ReadyState::CLOSED);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1127,7 +1174,22 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_requestInitCancellation = true;
|
_requestInitCancellation = true;
|
||||||
|
|
||||||
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED) return;
|
if (_readyState == ReadyState::CLOSING || _readyState == ReadyState::CLOSED)
|
||||||
|
{
|
||||||
|
// Wake up the socket polling thread, as
|
||||||
|
// Socket::isReadyToRead() might be still waiting the
|
||||||
|
// interrupt event to happen.
|
||||||
|
bool wakeUpPoll = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
wakeUpPoll = (_socket && _socket->isWakeUpFromPollSupported());
|
||||||
|
}
|
||||||
|
if (wakeUpPoll)
|
||||||
|
{
|
||||||
|
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (closeWireSize == 0)
|
if (closeWireSize == 0)
|
||||||
{
|
{
|
||||||
|
@ -18,9 +18,10 @@
|
|||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
#include "IXWebSocketPerMessageDeflate.h"
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include "IXWebSocketSendData.h"
|
#include "IXWebSocketSendData.h"
|
||||||
|
#include "IXWebSocketSendInfo.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -86,7 +87,8 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
|
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
|
||||||
int timeoutSecs,
|
int timeoutSecs,
|
||||||
bool enablePerMessageDeflate);
|
bool enablePerMessageDeflate,
|
||||||
|
HttpRequestPtr request = nullptr);
|
||||||
|
|
||||||
PollResult poll();
|
PollResult poll();
|
||||||
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
|
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
|
||||||
@ -108,8 +110,12 @@ namespace ix
|
|||||||
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
|
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
|
||||||
size_t bufferedAmount() const;
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
|
// set ping heartbeat message
|
||||||
|
void setPingMessage(const std::string& message, SendMessageKind pingType);
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
WebSocketSendInfo sendHeartBeat();
|
// send any type of ping packet, not only 'ping' type
|
||||||
|
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _url;
|
std::string _url;
|
||||||
@ -214,7 +220,10 @@ namespace ix
|
|||||||
std::atomic<bool> _pongReceived;
|
std::atomic<bool> _pongReceived;
|
||||||
|
|
||||||
static const int kDefaultPingIntervalSecs;
|
static const int kDefaultPingIntervalSecs;
|
||||||
static const std::string kPingMessage;
|
|
||||||
|
bool _setCustomMessage;
|
||||||
|
std::string _kPingMessage;
|
||||||
|
SendMessageKind _pingType;
|
||||||
std::atomic<uint64_t> _pingCount;
|
std::atomic<uint64_t> _pingCount;
|
||||||
|
|
||||||
// We record when ping are being sent so that we can know when to send the next one
|
// We record when ping are being sent so that we can know when to send the next one
|
||||||
|
@ -6,4 +6,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define IX_WEBSOCKET_VERSION "11.4.1"
|
#define IX_WEBSOCKET_VERSION "11.4.6"
|
||||||
|
20
makefile.dev
20
makefile.dev
@ -13,16 +13,24 @@ all: brew
|
|||||||
|
|
||||||
install: brew
|
install: brew
|
||||||
|
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/opt/homebrew
|
||||||
|
|
||||||
# Use -DCMAKE_INSTALL_PREFIX= to install into another location
|
# Use -DCMAKE_INSTALL_PREFIX= to install into another location
|
||||||
# on osx it is good practice to make /usr/local user writable
|
# on osx it is good practice to make /usr/local user writable
|
||||||
# sudo chown -R `whoami`/staff /usr/local
|
# sudo chown -R `whoami`/staff /usr/local
|
||||||
#
|
#
|
||||||
|
# Those days (since Apple Silicon mac shipped), on macOS homebrew installs in /opt/homebrew, and /usr/local is readonly
|
||||||
|
#
|
||||||
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
|
||||||
#
|
#
|
||||||
# Default rule does not use python as that requires first time users to have Python3 installed
|
# Default rule does not use python as that requires first time users to have Python3 installed
|
||||||
#
|
#
|
||||||
brew:
|
brew:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
|
ifeq ($(shell uname),Darwin)
|
||||||
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_PREFIX=/opt/homebrew -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja)
|
||||||
|
else
|
||||||
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja)
|
||||||
|
endif
|
||||||
|
|
||||||
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
|
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
|
||||||
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
# server side ?) and I can't work-around it easily, so we're using mbedtls on
|
||||||
@ -31,10 +39,10 @@ ws_mbedtls_install:
|
|||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_ZLIB=OFF -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja install)
|
||||||
|
|
||||||
ws:
|
ws:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja)
|
||||||
|
|
||||||
ws_unity:
|
ws_unity:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. && ninja)
|
||||||
|
|
||||||
ws_install:
|
ws_install:
|
||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. -DUSE_TEST=0 && ninja install)
|
||||||
@ -46,13 +54,13 @@ ws_openssl_install:
|
|||||||
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install)
|
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_OPEN_SSL=1 .. ; ninja install)
|
||||||
|
|
||||||
ws_mbedtls:
|
ws_mbedtls:
|
||||||
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j 4)
|
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; ninja)
|
||||||
|
|
||||||
ws_no_ssl:
|
ws_no_ssl:
|
||||||
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
|
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; ninja)
|
||||||
|
|
||||||
ws_no_python:
|
ws_no_python:
|
||||||
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j4 install)
|
mkdir -p build && (cd build ; cmake -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; ninja install)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
xargs rm -fv < build/install_manifest.txt
|
xargs rm -fv < build/install_manifest.txt
|
||||||
|
20
test/.certs/wrong-name-server-crt.pem
Normal file
20
test/.certs/wrong-name-server-crt.pem
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDNDCCAhwCFCl+O/rR8flqYKKvD0iwkucFwMaLMA0GCSqGSIb3DQEBCwUAMEEx
|
||||||
|
FDASBgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDETMBEG
|
||||||
|
A1UEAwwKdHJ1c3RlZC1jYTAgFw0yMjA4MjMyMDM2MjVaGA80MjgxMDYwMTIwMzYy
|
||||||
|
NVowajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxl
|
||||||
|
eTEbMBkGA1UECgwSRHVtbXkgT3JnYW5pemF0aW9uMR4wHAYDVQQDDBVub3QuYS52
|
||||||
|
YWxpZC5ob3N0Lm5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
|
||||||
|
9N806IjCvA82zfk9CPNwaEHOygNDJSXaZ38YDSI4C+Wf2imnMxrLQKaoccHUi+9L
|
||||||
|
4rQN/hSCg+uTULQUZ0iyssGDaIh4IcAeoEcNlXYHTrgP+aAaby3q5zeZ80K3+6e4
|
||||||
|
rqcuBPV2lLszJu3XXwEKbDSxW3De0gc2N8m9DN8Lx7i70DRf0F4m6RIMFF/kHXwa
|
||||||
|
zZLeG7rZb4xL684lmmQsWtk5Z600CvrBtUE7fQ7bhy0QhSt66kdTSL8IKQrbWcGj
|
||||||
|
q0tggFlOqeyZSi73gqUiAIuGO8/tRgmp4HygNyC24jpOB5nObOPPJTUEf5Mk1Bum
|
||||||
|
kD5a+yL6YbVdhiaK17wbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKsLXGLfO1IZ
|
||||||
|
LbofGc7/TCQwRayR3RdG4864PBy97KfJWyizg7Wm4X4yPFRG+6q3X5NKW32Ew9lI
|
||||||
|
jXulXCTjWOiSxiG4Pk20uczkOhWVHFdnS9gZvykPC/ElxBKPalT6MMstZWxpElsk
|
||||||
|
rCDKXj4LkD0po8bZrjlgSZQQQk6XMRkoRai2GWLJqIjaNCSF8nqb1wM/1OE1tAwi
|
||||||
|
polO1eFMA24yypvlXcNrNXjqcQ7LaoQFQltmi/RV+uTk7EK2F2jgYVzJ/pe2ET0i
|
||||||
|
RIMbGZTlAiemDGL9SpMsxftG6fSmP6QqDqYExmmPlZMLprb2da/2kelWFA+VkvdG
|
||||||
|
zFrnIcyfMY4=
|
||||||
|
-----END CERTIFICATE-----
|
27
test/.certs/wrong-name-server-key.pem
Normal file
27
test/.certs/wrong-name-server-key.pem
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAtvTfNOiIwrwPNs35PQjzcGhBzsoDQyUl2md/GA0iOAvln9op
|
||||||
|
pzMay0CmqHHB1IvvS+K0Df4UgoPrk1C0FGdIsrLBg2iIeCHAHqBHDZV2B064D/mg
|
||||||
|
Gm8t6uc3mfNCt/unuK6nLgT1dpS7Mybt118BCmw0sVtw3tIHNjfJvQzfC8e4u9A0
|
||||||
|
X9BeJukSDBRf5B18Gs2S3hu62W+MS+vOJZpkLFrZOWetNAr6wbVBO30O24ctEIUr
|
||||||
|
eupHU0i/CCkK21nBo6tLYIBZTqnsmUou94KlIgCLhjvP7UYJqeB8oDcgtuI6TgeZ
|
||||||
|
zmzjzyU1BH+TJNQbppA+Wvsi+mG1XYYmite8GwIDAQABAoIBAGRzAXG9EglI01mV
|
||||||
|
sPfvyCi5NRhiFXRyGtxU4pTD8TuwXHxtfV0NU/KwJlBpVLBrvBCAAbeE/qHB6D9T
|
||||||
|
metx4ZorRs/tPrAmZ6LpANnWa50LfUdYGK0qyZ0lIYPm6YS2KJnfWm6LznEyq60j
|
||||||
|
/IW45YthaXTO7aOI0OjVrG+dd4CxU1g1NtCQ9bdDMDjfXFVnoOifXIl8W22eRMoZ
|
||||||
|
LzCz+0sI0R0LenXCPT566de21F0NDkIK7NaQ1l5BMX4PA+RsN3cZlzyruA1woPKI
|
||||||
|
aBR2LQGNrBfDVGMATtUm89RpWAftb8FmXqYUsM7zAzTO6dViitiB7OFlw7Ax15YH
|
||||||
|
MTj5zGECgYEA35ocEEMfyahBN70bjyiqOHlzKwFjDl9DsUf8xqHsNhYAL+GrOK9A
|
||||||
|
8lN5ByzcnbV3TJtU4WYbPgQJld8gXFx4h3eS+SkA/ASkAdtgHfdMImZ1v7k3TIPf
|
||||||
|
DXOCsHzELsQY6OgiI572Nwzx/Tl+0dFwaOfLjU9iEmmqL667j1Y4NiMCgYEA0Xch
|
||||||
|
9K/vwZ1I9gM3ySvG40R2TRriC9Bf8jwrEWeRsWNvBcqtMMrgwAMsMCKDugSZR7n6
|
||||||
|
o3WZV6mpvYVLFx0b93v07i7EpFP27kMw3gLNBKX62snR9JbqwAMK7tktgLds5pKM
|
||||||
|
DvLHuAQ9XMMXMLX7WlSyhmtFeU7IDulTSHHqdakCgYEAywITCpy2xpKRK7bwx4gH
|
||||||
|
C6EQc/IdahYJ0nHmSL0IRY6x+sbrelp7H8ezcVVEs5bmylGYvc/DWgm2XjCnI9P8
|
||||||
|
xhlFAhw9PZJFCT6QRISaxfy6WSgi0cBEieTeubd9MmxtpT/khuyy5AZHyj0iLAL4
|
||||||
|
CPayMwjopIj0r7f3p8qC3HsCgYBmq2kmYVI4aZrIkv02CtIatYTy+DlSJxnQRvOp
|
||||||
|
PUWpWB6kDRrk7pxJIYT4NwKwG+7xvFQA6PR3hn7fmUUcGDWMEeMVGDFkho9ja+W4
|
||||||
|
/FB3dc/Gi+PwakS4RwWF20e1brLfNXeXICMKrHFTVYC5bIm+VgOHZW8RLa9bt7wN
|
||||||
|
p2CPuQKBgQCjW+rCODmMzEues/I143mYMDdZ1IschtWGiXBNrpkHm/DcZSutbacm
|
||||||
|
5C7Kwv44pfA90NHDTjuaIgRgfeUTawkrl8uPXEuj80mW72agf5oS8lJzD+2jibCj
|
||||||
|
Q4K52G+0LaTxHLZxufil28Rgja01c0mTcuLbhKtCgHl5EHP19wOKEg==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@ -16,7 +16,7 @@ set (TEST_TARGET_NAMES
|
|||||||
IXWebSocketServerTest
|
IXWebSocketServerTest
|
||||||
IXWebSocketTestConnectionDisconnection
|
IXWebSocketTestConnectionDisconnection
|
||||||
IXUrlParserTest
|
IXUrlParserTest
|
||||||
IXHttpClientTest
|
# IXHttpClientTest ## FIXME httpbin.org does not seem normal
|
||||||
IXUnityBuildsTest
|
IXUnityBuildsTest
|
||||||
IXHttpTest
|
IXHttpTest
|
||||||
IXDNSLookupTest
|
IXDNSLookupTest
|
||||||
@ -24,14 +24,13 @@ set (TEST_TARGET_NAMES
|
|||||||
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
|
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
|
||||||
IXStrCaseCompareTest
|
IXStrCaseCompareTest
|
||||||
IXExponentialBackoffTest
|
IXExponentialBackoffTest
|
||||||
|
IXWebSocketCloseTest
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
# Windows without TLS does not have hmac yet
|
# Windows without TLS does not have hmac yet
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
list(APPEND TEST_TARGET_NAMES
|
list(APPEND TEST_TARGET_NAMES
|
||||||
IXWebSocketCloseTest
|
|
||||||
|
|
||||||
# Fail on Windows in CI probably because the pathing is wrong and
|
# Fail on Windows in CI probably because the pathing is wrong and
|
||||||
# some resource files cannot be found
|
# some resource files cannot be found
|
||||||
IXHttpServerTest
|
IXHttpServerTest
|
||||||
|
@ -19,13 +19,9 @@ TEST_CASE("dns", "[net]")
|
|||||||
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
|
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res;
|
auto res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||||
|
|
||||||
res = dnsLookup->resolve(errMsg, [] { return false; });
|
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res != nullptr);
|
REQUIRE(res != nullptr);
|
||||||
|
|
||||||
dnsLookup->release(res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Test resolving a non-existing hostname")
|
SECTION("Test resolving a non-existing hostname")
|
||||||
@ -33,7 +29,7 @@ TEST_CASE("dns", "[net]")
|
|||||||
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
|
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
|
auto res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res == nullptr);
|
REQUIRE(res == nullptr);
|
||||||
}
|
}
|
||||||
@ -44,7 +40,7 @@ TEST_CASE("dns", "[net]")
|
|||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
// The callback returning true means we are requesting cancellation
|
// The callback returning true means we are requesting cancellation
|
||||||
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
|
auto res = dnsLookup->resolve(errMsg, [] { return true; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res == nullptr);
|
REQUIRE(res == nullptr);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXGetFreePort.h>
|
||||||
#include <ixwebsocket/IXHttpClient.h>
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
|
||||||
using namespace ix;
|
using namespace ix;
|
||||||
|
|
||||||
@ -95,6 +97,52 @@ TEST_CASE("http_client", "[http]")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(IXWEBSOCKET_USE_TLS) && !defined(IXWEBSOCKET_USE_SECURE_TRANSPORT)
|
||||||
|
SECTION("Disable hostname validation")
|
||||||
|
{
|
||||||
|
static auto test_cert_with_wrong_name = [](bool validate_hostname)
|
||||||
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::HttpServer server(port, "127.0.0.1");
|
||||||
|
|
||||||
|
SocketTLSOptions tlsOptionsServer;
|
||||||
|
tlsOptionsServer.tls = true;
|
||||||
|
tlsOptionsServer.caFile = "NONE";
|
||||||
|
tlsOptionsServer.certFile = "./.certs/wrong-name-server-crt.pem";
|
||||||
|
tlsOptionsServer.keyFile = "./.certs/wrong-name-server-key.pem";
|
||||||
|
server.setTLSOptions(tlsOptionsServer);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
REQUIRE(res.first);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
HttpClient httpClient;
|
||||||
|
SocketTLSOptions tlsOptionsClient;
|
||||||
|
tlsOptionsClient.caFile = "./.certs/trusted-ca-crt.pem";
|
||||||
|
tlsOptionsClient.disable_hostname_validation = validate_hostname;
|
||||||
|
httpClient.setTLSOptions(tlsOptionsClient);
|
||||||
|
|
||||||
|
std::string url("https://localhost:" + std::to_string(port));
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
args->connectTimeout = 10;
|
||||||
|
args->transferTimeout = 10;
|
||||||
|
|
||||||
|
auto response = httpClient.get(url, args);
|
||||||
|
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error code: " << (int) response->errorCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
return std::make_tuple(response->errorCode, response->statusCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
REQUIRE(test_cert_with_wrong_name(false) ==
|
||||||
|
std::make_tuple(HttpErrorCode::CannotConnect, 0));
|
||||||
|
REQUIRE(test_cert_with_wrong_name(true) == std::make_tuple(HttpErrorCode::Ok, 404));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("Async API, one call")
|
SECTION("Async API, one call")
|
||||||
{
|
{
|
||||||
bool async = true;
|
bool async = true;
|
||||||
|
@ -60,6 +60,7 @@ TEST_CASE("http server", "[httpd]")
|
|||||||
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||||
REQUIRE(response->statusCode == 200);
|
REQUIRE(response->statusCode == 200);
|
||||||
REQUIRE(response->headers["Accept-Encoding"] == "gzip");
|
REQUIRE(response->headers["Accept-Encoding"] == "gzip");
|
||||||
|
REQUIRE(response->headers["Content-Encoding"] == "gzip");
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,40 @@ namespace ix
|
|||||||
REQUIRE(port == 443); // default port for wss
|
REQUIRE(port == 443); // default port for wss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("wss://google.com/?arg=value")
|
||||||
|
{
|
||||||
|
std::string url = "wss://google.com/?arg=value&arg2=value2";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "wss");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/?arg=value&arg2=value2");
|
||||||
|
REQUIRE(query == "arg=value&arg2=value2");
|
||||||
|
REQUIRE(port == 443); // default port for wss
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("wss://google.com?arg=value")
|
||||||
|
{
|
||||||
|
std::string url = "wss://google.com?arg=value&arg2=value2";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "wss");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/?arg=value&arg2=value2");
|
||||||
|
REQUIRE(query == "arg=value&arg2=value2");
|
||||||
|
REQUIRE(port == 443); // default port for wss
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("real test")
|
SECTION("real test")
|
||||||
{
|
{
|
||||||
std::string url =
|
std::string url =
|
||||||
|
31
ws/ws.cpp
31
ws/ws.cpp
@ -77,24 +77,6 @@ namespace
|
|||||||
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume the file exists
|
|
||||||
std::string readBytes(const std::string& path)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> memblock;
|
|
||||||
std::ifstream file(path);
|
|
||||||
|
|
||||||
file.seekg(0, file.end);
|
|
||||||
std::streamoff size = file.tellg();
|
|
||||||
file.seekg(0, file.beg);
|
|
||||||
|
|
||||||
memblock.reserve((size_t) size);
|
|
||||||
memblock.insert(
|
|
||||||
memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
|
|
||||||
|
|
||||||
std::string bytes(memblock.begin(), memblock.end());
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string truncate(const std::string& str, size_t n)
|
std::string truncate(const std::string& str, size_t n)
|
||||||
{
|
{
|
||||||
if (str.size() < n)
|
if (str.size() < n)
|
||||||
@ -107,12 +89,6 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fileExists(const std::string& fileName)
|
|
||||||
{
|
|
||||||
std::ifstream infile(fileName);
|
|
||||||
return infile.good();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string extractFilename(const std::string& path)
|
std::string extractFilename(const std::string& path)
|
||||||
{
|
{
|
||||||
std::string::size_type idx;
|
std::string::size_type idx;
|
||||||
@ -916,9 +892,8 @@ namespace ix
|
|||||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
|
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res;
|
|
||||||
|
|
||||||
res = dnsLookup->resolve(errMsg, [] { return false; });
|
auto res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||||
|
|
||||||
auto addr = res->ai_addr;
|
auto addr = res->ai_addr;
|
||||||
|
|
||||||
@ -2486,10 +2461,8 @@ int main(int argc, char** argv)
|
|||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool save = false;
|
bool save = false;
|
||||||
bool quiet = false;
|
bool quiet = false;
|
||||||
bool fluentd = false;
|
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
bool compressRequest = false;
|
bool compressRequest = false;
|
||||||
bool stress = false;
|
|
||||||
bool disableAutomaticReconnection = false;
|
bool disableAutomaticReconnection = false;
|
||||||
bool disablePerMessageDeflate = false;
|
bool disablePerMessageDeflate = false;
|
||||||
bool greetings = false;
|
bool greetings = false;
|
||||||
@ -2505,7 +2478,6 @@ int main(int argc, char** argv)
|
|||||||
int transferTimeout = 1800;
|
int transferTimeout = 1800;
|
||||||
int maxRedirects = 5;
|
int maxRedirects = 5;
|
||||||
int delayMs = -1;
|
int delayMs = -1;
|
||||||
int count = 1;
|
|
||||||
int msgCount = 1000 * 1000;
|
int msgCount = 1000 * 1000;
|
||||||
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
|
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
|
||||||
int pingIntervalSecs = 30;
|
int pingIntervalSecs = 30;
|
||||||
@ -2529,6 +2501,7 @@ int main(int argc, char** argv)
|
|||||||
"A (comma/space/colon) separated list of ciphers to use for TLS");
|
"A (comma/space/colon) separated list of ciphers to use for TLS");
|
||||||
app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)");
|
app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)");
|
||||||
app->add_flag("--verify_none", verifyNone, "Disable peer cert verification");
|
app->add_flag("--verify_none", verifyNone, "Disable peer cert verification");
|
||||||
|
app->add_flag("--disable-hostname-validation", tlsOptions.disable_hostname_validation, "Disable validation of certificates' hostnames");
|
||||||
};
|
};
|
||||||
|
|
||||||
app.add_flag("--version", version, "Print ws version");
|
app.add_flag("--version", version, "Print ws version");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user