Compare commits
65 Commits
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 | ||
|
6d8495bd73 | ||
|
b8563eddd1 | ||
|
46bd2aa4a1 | ||
|
4420bc70b5 | ||
|
20921f341a | ||
|
2829c62ef9 | ||
|
a3d2fa4b7e |
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:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
- uses: egor-tensin/setup-mingw@v2
|
||||
- uses: bsergean/setup-mingw@d79ce405bac9edef3a1726ef00554a56f0bafe66
|
||||
- run: |
|
||||
mkdir build
|
||||
cd build
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ ws/.srl
|
||||
ixhttpd
|
||||
makefile
|
||||
a.out
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
|
@ -6,11 +6,12 @@
|
||||
cmake_minimum_required(VERSION 3.4.1...3.17.2)
|
||||
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 (CXX_STANDARD_REQUIRED ON)
|
||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
|
||||
|
||||
option (BUILD_DEMO OFF)
|
||||
|
||||
@ -66,6 +67,7 @@ set( IXWEBSOCKET_SOURCES
|
||||
)
|
||||
|
||||
set( IXWEBSOCKET_HEADERS
|
||||
ixwebsocket/IXBase64.h
|
||||
ixwebsocket/IXBench.h
|
||||
ixwebsocket/IXCancellationRequest.h
|
||||
ixwebsocket/IXConnectionState.h
|
||||
@ -134,6 +136,7 @@ if (USE_TLS)
|
||||
else() # default to OpenSSL on all other platforms
|
||||
if (NOT USE_MBED_TLS) # Unless mbedtls is requested
|
||||
set(USE_OPEN_SSL ON)
|
||||
set(requires "openssl")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -165,8 +168,7 @@ if(BUILD_SHARED_LIBS)
|
||||
)
|
||||
|
||||
# Set library version
|
||||
set_target_properties(ixwebsocket PROPERTIES VERSION 11.3.2)
|
||||
|
||||
set_target_properties(ixwebsocket PROPERTIES VERSION ${PROJECT_VERSION})
|
||||
else()
|
||||
# Static library
|
||||
add_library( ixwebsocket
|
||||
@ -234,11 +236,7 @@ endif()
|
||||
option(USE_ZLIB "Enable zlib support" TRUE)
|
||||
|
||||
if (USE_ZLIB)
|
||||
# This ZLIB_FOUND check is to help find a cmake manually configured zlib
|
||||
if (NOT ZLIB_FOUND)
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${ZLIB_INCLUDE_DIRS}>)
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB)
|
||||
|
||||
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
|
||||
@ -253,7 +251,7 @@ if (WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
if (UNIX AND NOT APPLE)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads)
|
||||
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
|
||||
@ -289,10 +287,18 @@ if (IXWEBSOCKET_INSTALL)
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
|
||||
)
|
||||
|
||||
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
|
||||
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
|
||||
FILE ixwebsocket-config.cmake
|
||||
FILE ixwebsocket-targets.cmake
|
||||
NAMESPACE ixwebsocket::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket)
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket
|
||||
)
|
||||
endif()
|
||||
|
||||
if (USE_WS OR USE_TEST)
|
||||
|
18
README.md
18
README.md
@ -1,10 +1,10 @@
|
||||
## 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.
|
||||
|
||||
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. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness.
|
||||
|
||||
A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR).
|
||||
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.
|
||||
|
||||
```cpp
|
||||
/*
|
||||
@ -37,6 +37,7 @@ int main()
|
||||
|
||||
// Connect to a server with encryption
|
||||
// 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");
|
||||
webSocket.setUrl(url);
|
||||
|
||||
@ -100,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.
|
||||
|
||||
- [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.
|
||||
- [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
|
||||
- [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
|
||||
- [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
|
||||
|
||||
@ -115,8 +118,8 @@ There are plenty of great websocket libraries out there, which might work for yo
|
||||
|
||||
* [websocketpp](https://github.com/zaphoyd/websocketpp) - C++
|
||||
* [beast](https://github.com/boostorg/beast) - C++
|
||||
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - C++
|
||||
* [libwebsockets](https://libwebsockets.org/) - C
|
||||
* [µWebSockets](https://github.com/uNetworking/uWebSockets) - 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.
|
||||
@ -134,9 +137,7 @@ To check the performance of a websocket library, you can look at the [autoroute]
|
||||
| Windows | Disabled | None | [![Build2][5]][0] |
|
||||
| UWP | Disabled | None | [![Build2][6]][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
|
||||
* TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.
|
||||
|
||||
@ -148,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
|
||||
[6]: https://github.com/machinezone/IXWebSocket/workflows/uwp/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,22 @@
|
||||
|
||||
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
|
||||
|
||||
vckpg + cmake fix, to handle zlib as a dependency better
|
||||
|
||||
## [11.4.0] - 2022-01-05
|
||||
|
||||
(Windows) Use wsa select event, which should lead to a much better behavior on Windows in general, and also when sending large payloads (#342)
|
||||
|
@ -54,7 +54,7 @@ To use the installed package within a cmake project, use the following:
|
||||
# include headers
|
||||
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.
|
||||
// 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](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.
|
||||
// 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) {
|
||||
// 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
|
||||
|
||||
```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:
|
||||
1. You require clients to present a certificate
|
||||
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`.
|
||||
|
9
ixwebsocket-config.cmake.in
Normal file
9
ixwebsocket-config.cmake.in
Normal file
@ -0,0 +1,9 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
if (@USE_ZLIB@)
|
||||
find_dependency(ZLIB)
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-targets.cmake")
|
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
|
||||
|
||||
#include <chrono>
|
||||
#include <stdint.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -7,9 +7,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -23,12 +23,23 @@
|
||||
#include <chrono>
|
||||
#include <string.h>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
// mingw build quirks
|
||||
#if defined(_WIN32) && defined(__GNUC__)
|
||||
#ifndef AI_NUMERICSERV
|
||||
#define AI_NUMERICSERV NI_NUMERICSERV
|
||||
#endif
|
||||
#ifndef AI_ADDRCONFIG
|
||||
#define AI_ADDRCONFIG LUP_ADDRCONFIG
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifndef AI_NUMERICSERV
|
||||
#define AI_NUMERICSERV 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
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,
|
||||
std::string& errMsg)
|
||||
{
|
||||
@ -63,10 +74,10 @@ namespace ix
|
||||
errMsg = gai_strerror(getaddrinfo_result);
|
||||
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,
|
||||
bool cancellable)
|
||||
{
|
||||
@ -74,12 +85,7 @@ namespace ix
|
||||
: resolveUnCancellable(errMsg, isCancellationRequested);
|
||||
}
|
||||
|
||||
void DNSLookup::release(struct addrinfo* addr)
|
||||
{
|
||||
freeaddrinfo(addr);
|
||||
}
|
||||
|
||||
struct addrinfo* DNSLookup::resolveUnCancellable(
|
||||
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
@ -94,7 +100,7 @@ namespace ix
|
||||
return getAddrInfo(_hostname, _port, errMsg);
|
||||
}
|
||||
|
||||
struct addrinfo* DNSLookup::resolveCancellable(
|
||||
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable(
|
||||
std::string& errMsg, const CancellationRequest& isCancellationRequested)
|
||||
{
|
||||
errMsg = "no error";
|
||||
@ -157,7 +163,7 @@ namespace ix
|
||||
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
||||
// getAddrInfo needs to work.
|
||||
std::string errMsg;
|
||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||
auto res = getAddrInfo(hostname, port, errMsg);
|
||||
|
||||
if (auto lock = self.lock())
|
||||
{
|
||||
@ -181,13 +187,13 @@ namespace ix
|
||||
return _errMsg;
|
||||
}
|
||||
|
||||
void DNSLookup::setRes(struct addrinfo* addr)
|
||||
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr)
|
||||
{
|
||||
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);
|
||||
return _res;
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
@ -24,22 +25,21 @@ namespace ix
|
||||
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
|
||||
{
|
||||
public:
|
||||
using AddrInfoPtr = std::shared_ptr<addrinfo>;
|
||||
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
||||
~DNSLookup() = default;
|
||||
|
||||
struct addrinfo* resolve(std::string& errMsg,
|
||||
AddrInfoPtr resolve(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested,
|
||||
bool cancellable = true);
|
||||
|
||||
void release(struct addrinfo* addr);
|
||||
|
||||
private:
|
||||
struct addrinfo* resolveCancellable(std::string& errMsg,
|
||||
AddrInfoPtr resolveCancellable(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
struct addrinfo* resolveUnCancellable(std::string& errMsg,
|
||||
AddrInfoPtr resolveUnCancellable(std::string& errMsg,
|
||||
const CancellationRequest& isCancellationRequested);
|
||||
|
||||
static struct addrinfo* getAddrInfo(const std::string& hostname,
|
||||
AddrInfoPtr getAddrInfo(const std::string& hostname,
|
||||
int port,
|
||||
std::string& errMsg);
|
||||
|
||||
@ -48,15 +48,15 @@ namespace ix
|
||||
void setErrMsg(const std::string& errMsg);
|
||||
const std::string& getErrMsg();
|
||||
|
||||
void setRes(struct addrinfo* addr);
|
||||
struct addrinfo* getRes();
|
||||
void setRes(AddrInfoPtr addr);
|
||||
AddrInfoPtr getRes();
|
||||
|
||||
std::string _hostname;
|
||||
int _port;
|
||||
int64_t _wait;
|
||||
const static int64_t kDefaultWait;
|
||||
|
||||
struct addrinfo* _res;
|
||||
AddrInfoPtr _res;
|
||||
std::mutex _resMutex;
|
||||
|
||||
std::string _errMsg;
|
||||
|
@ -133,16 +133,20 @@ namespace ix
|
||||
if (headers.find("Content-Length") != headers.end())
|
||||
{
|
||||
int contentLength = 0;
|
||||
try
|
||||
{
|
||||
contentLength = std::stoi(headers["Content-Length"]);
|
||||
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(
|
||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
||||
}
|
||||
contentLength = val;
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
return std::make_tuple(
|
||||
false, "Error parsing HTTP Header 'Content-Length'", httpRequest);
|
||||
}
|
||||
|
||||
if (contentLength < 0)
|
||||
{
|
||||
return std::make_tuple(
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "IXProgressCallback.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "IXUserAgent.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include <assert.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
@ -139,8 +140,9 @@ namespace ix
|
||||
|
||||
std::string protocol, host, path, query;
|
||||
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;
|
||||
ss << "Cannot parse url: " << url;
|
||||
@ -173,7 +175,12 @@ namespace ix
|
||||
// Build request string
|
||||
std::stringstream ss;
|
||||
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
|
||||
if (args->compress && !args->onChunkCallback)
|
||||
@ -202,6 +209,12 @@ namespace ix
|
||||
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)
|
||||
{
|
||||
// Set request compression header
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXUserAgent.h"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
@ -40,6 +41,29 @@ namespace
|
||||
auto vec = res.second;
|
||||
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 ix
|
||||
@ -51,28 +75,14 @@ namespace ix
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int addressFamily,
|
||||
int timeoutSecs)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _connectedClientsCount(0)
|
||||
int timeoutSecs,
|
||||
int handshakeTimeoutSecs)
|
||||
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily)
|
||||
, _timeoutSecs(timeoutSecs)
|
||||
{
|
||||
setDefaultConnectionCallback();
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void HttpServer::stop()
|
||||
{
|
||||
stopAcceptingConnections();
|
||||
|
||||
// FIXME: cancelling / closing active clients ...
|
||||
|
||||
SocketServer::stop();
|
||||
}
|
||||
|
||||
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
||||
{
|
||||
_onConnectionCallback = callback;
|
||||
@ -81,34 +91,35 @@ namespace ix
|
||||
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState)
|
||||
{
|
||||
_connectedClientsCount++;
|
||||
|
||||
auto ret = Http::parseRequest(socket, _timeoutSecs);
|
||||
// FIXME: handle errors in parseRequest
|
||||
|
||||
if (std::get<0>(ret))
|
||||
{
|
||||
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||
if (!Http::sendResponse(response, socket))
|
||||
auto request = std::get<2>(ret);
|
||||
std::shared_ptr<ix::HttpResponse> response;
|
||||
if (request->headers["Upgrade"] == "websocket")
|
||||
{
|
||||
logError("Cannot send response");
|
||||
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto response = _onConnectionCallback(request, connectionState);
|
||||
if (!Http::sendResponse(response, socket))
|
||||
{
|
||||
logError("Cannot send response");
|
||||
}
|
||||
}
|
||||
}
|
||||
connectionState->setTerminated();
|
||||
|
||||
_connectedClientsCount--;
|
||||
}
|
||||
|
||||
size_t HttpServer::getConnectedClientsCount()
|
||||
{
|
||||
return _connectedClientsCount;
|
||||
}
|
||||
|
||||
void HttpServer::setDefaultConnectionCallback()
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||
{
|
||||
std::string uri(request->uri);
|
||||
if (uri.empty() || uri == "/")
|
||||
{
|
||||
@ -117,6 +128,7 @@ namespace ix
|
||||
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
headers["Content-Type"] = response_head_file(uri);
|
||||
|
||||
std::string path("." + uri);
|
||||
auto res = readAsString(path);
|
||||
@ -136,6 +148,7 @@ namespace ix
|
||||
content = gzipCompress(content);
|
||||
headers["Content-Encoding"] = "gzip";
|
||||
}
|
||||
headers["Accept-Encoding"] = "gzip";
|
||||
#endif
|
||||
|
||||
// Log request
|
||||
@ -149,11 +162,6 @@ namespace ix
|
||||
// headers["Content-Type"] = "application/octet-stream";
|
||||
headers["Accept-Ranges"] = "none";
|
||||
|
||||
for (auto&& it : request->headers)
|
||||
{
|
||||
headers[it.first] = it.second;
|
||||
}
|
||||
|
||||
return std::make_shared<HttpResponse>(
|
||||
200, "OK", HttpErrorCode::Ok, headers, content);
|
||||
});
|
||||
@ -165,9 +173,9 @@ namespace ix
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
|
||||
//
|
||||
setOnConnectionCallback(
|
||||
[this,
|
||||
redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
[this, redirectUrl](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||
{
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
@ -198,7 +206,8 @@ namespace ix
|
||||
{
|
||||
setOnConnectionCallback(
|
||||
[this](HttpRequestPtr request,
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
|
||||
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
|
||||
{
|
||||
WebSocketHttpHeaders headers;
|
||||
headers["Server"] = userAgent();
|
||||
|
||||
|
@ -7,8 +7,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocketServer.h"
|
||||
#include "IXWebSocket.h"
|
||||
#include "IXWebSocketServer.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
class HttpServer final : public SocketServer
|
||||
class HttpServer final : public WebSocketServer
|
||||
{
|
||||
public:
|
||||
using OnConnectionCallback =
|
||||
@ -30,9 +30,8 @@ namespace ix
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||
int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
|
||||
virtual ~HttpServer();
|
||||
virtual void stop() final;
|
||||
int timeoutSecs = HttpServer::kDefaultTimeoutSecs,
|
||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
|
||||
|
||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||
|
||||
@ -41,10 +40,10 @@ namespace ix
|
||||
void makeDebugServer();
|
||||
|
||||
int getTimeoutSecs();
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
std::atomic<int> _connectedClientsCount;
|
||||
|
||||
const static int kDefaultTimeoutSecs;
|
||||
int _timeoutSecs;
|
||||
@ -52,7 +51,6 @@ namespace ix
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket>,
|
||||
std::shared_ptr<ConnectionState> connectionState) final;
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
void setDefaultConnectionCallback();
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ namespace ix
|
||||
{
|
||||
// We must deselect the networkevents from the socket event. Otherwise the
|
||||
// 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);
|
||||
WSACloseEvent(_event);
|
||||
}
|
||||
@ -171,7 +171,7 @@ namespace ix
|
||||
int count = 0;
|
||||
// WSAWaitForMultipleEvents returns the index of the first signaled event. And to emulate WSAPoll()
|
||||
// all the signaled events must be processed.
|
||||
while (socketIndex < socketEvents.size())
|
||||
while (socketIndex < (int)socketEvents.size())
|
||||
{
|
||||
struct pollfd* fd = socketEvents[socketIndex];
|
||||
|
||||
@ -345,7 +345,7 @@ namespace ix
|
||||
buf[best] = buf[best + 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);
|
||||
return s;
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -5,8 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#ifdef _WIN32
|
||||
|
@ -34,8 +34,12 @@ namespace ix
|
||||
|
||||
SelectInterruptPipe::~SelectInterruptPipe()
|
||||
{
|
||||
::close(_fildes[kPipeReadIndex]);
|
||||
::close(_fildes[kPipeWriteIndex]);
|
||||
if (-1 != _fildes[kPipeReadIndex]) {
|
||||
::close(_fildes[kPipeReadIndex]);
|
||||
}
|
||||
if (-1 != _fildes[kPipeWriteIndex]) {
|
||||
::close(_fildes[kPipeWriteIndex]);
|
||||
}
|
||||
_fildes[kPipeReadIndex] = -1;
|
||||
_fildes[kPipeWriteIndex] = -1;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXSelectInterrupt.h"
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
@ -15,6 +15,10 @@
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
// Windows
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@ -58,7 +62,7 @@ namespace ix
|
||||
|
||||
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
|
||||
// Notice that the Apple version of pthread_setname_np
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include <array>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
@ -7,11 +7,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <basetsd.h>
|
||||
#ifdef _MSC_VER
|
||||
|
@ -205,7 +205,9 @@ namespace ix
|
||||
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
|
||||
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
|
||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
if (!_tlsOptions.disable_hostname_validation)
|
||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
||||
|
||||
if (_tlsOptions.isPeerVerifyDisabled())
|
||||
{
|
||||
|
@ -102,7 +102,7 @@ namespace ix
|
||||
// First do DNS resolution
|
||||
//
|
||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
auto res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||
if (res == nullptr)
|
||||
{
|
||||
return -1;
|
||||
@ -112,7 +112,7 @@ namespace ix
|
||||
|
||||
// iterate through the records to find a working peer
|
||||
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
|
||||
@ -124,7 +124,6 @@ namespace ix
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "IXNetSystem.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include <cstdint>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -46,6 +47,13 @@ namespace ix
|
||||
mbedtls_x509_crt_init(&_cacert);
|
||||
mbedtls_x509_crt_init(&_cert);
|
||||
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)
|
||||
@ -195,10 +203,13 @@ namespace ix
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
if (!_tlsOptions.disable_hostname_validation)
|
||||
{
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
||||
{
|
||||
errMsg = "SNI setup failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -348,6 +359,11 @@ namespace ix
|
||||
return res;
|
||||
}
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
errno = ECONNRESET;
|
||||
}
|
||||
|
||||
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||
{
|
||||
errno = EWOULDBLOCK;
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#ifdef _WIN32
|
||||
// For manipulating the certificate store
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#endif
|
||||
|
||||
@ -49,7 +50,7 @@ namespace
|
||||
X509_STORE* opensslStore = SSL_CTX_get_cert_store(ssl);
|
||||
|
||||
int certificateCount = 0;
|
||||
while (certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator))
|
||||
while ((certificateIterator = CertEnumCertificatesInStore(systemStore, certificateIterator)))
|
||||
{
|
||||
X509* x509 = d2i_X509(NULL,
|
||||
(const unsigned char**) &certificateIterator->pbCertEncoded,
|
||||
@ -293,15 +294,25 @@ namespace ix
|
||||
*/
|
||||
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
return true;
|
||||
#else
|
||||
|
||||
#ifdef _WIN32
|
||||
return PathMatchSpecA(host.c_str(), pattern);
|
||||
#else
|
||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
const std::string& hostname,
|
||||
#else
|
||||
const std::string& /* hostname */,
|
||||
#endif
|
||||
std::string& errMsg)
|
||||
{
|
||||
X509* server_cert = SSL_get_peer_certificate(ssl);
|
||||
@ -339,12 +350,12 @@ namespace ix
|
||||
{
|
||||
int cn_pos = X509_NAME_get_index_by_NID(
|
||||
X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
|
||||
if (cn_pos)
|
||||
if (cn_pos >= 0)
|
||||
{
|
||||
X509_NAME_ENTRY* cn_entry =
|
||||
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
|
||||
|
||||
if (cn_entry)
|
||||
if (cn_entry != nullptr)
|
||||
{
|
||||
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
|
||||
char* cn = (char*) ASN1_STRING_data(cn_asn1);
|
||||
@ -390,6 +401,11 @@ namespace ix
|
||||
int connect_result = SSL_connect(_ssl_connection);
|
||||
if (connect_result == 1)
|
||||
{
|
||||
if (_tlsOptions.disable_hostname_validation)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
|
||||
}
|
||||
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
|
||||
// 1.1.0, but it does not. To be on the safe side, the manual test
|
||||
// below is enabled for all versions prior to 1.1.0.)
|
||||
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
|
||||
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
|
||||
if (!_tlsOptions.disable_hostname_validation)
|
||||
{
|
||||
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
|
||||
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
|
||||
}
|
||||
#endif
|
||||
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
|
||||
}
|
||||
|
@ -219,6 +219,10 @@ namespace ix
|
||||
if (_gcThread.joinable())
|
||||
{
|
||||
_stopGc = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
|
||||
_canContinueGC = true;
|
||||
}
|
||||
_conditionVariableGC.notify_one();
|
||||
_gcThread.join();
|
||||
_stopGc = false;
|
||||
@ -268,7 +272,10 @@ namespace ix
|
||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||
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 (;;)
|
||||
{
|
||||
@ -425,7 +432,10 @@ namespace ix
|
||||
|
||||
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 (;;)
|
||||
{
|
||||
@ -445,7 +455,10 @@ namespace ix
|
||||
if (!_stopGc)
|
||||
{
|
||||
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,
|
||||
// so wake up the thread responsible for that
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
|
||||
_canContinueGC = true;
|
||||
}
|
||||
_conditionVariableGC.notify_one();
|
||||
}
|
||||
|
||||
|
@ -126,5 +126,6 @@ namespace ix
|
||||
// as a connection
|
||||
std::condition_variable _conditionVariableGC;
|
||||
std::mutex _conditionVariableMutexGC;
|
||||
bool _canContinueGC{ false };
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -33,6 +33,9 @@ namespace ix
|
||||
// whether tls is enabled, used for server code
|
||||
bool tls = false;
|
||||
|
||||
// whether to skip validating the peer's hostname against the certificate presented
|
||||
bool disable_hostname_validation = false;
|
||||
|
||||
bool hasCertAndKey() const;
|
||||
|
||||
bool isUsingSystemDefaults() const;
|
||||
|
@ -180,7 +180,7 @@ namespace
|
||||
bHasUserName = true;
|
||||
break;
|
||||
}
|
||||
else if (*LocalString == '/')
|
||||
else if (*LocalString == '/' || *LocalString == '?')
|
||||
{
|
||||
// end of <host>:<port> specification
|
||||
bHasUserName = false;
|
||||
@ -242,7 +242,7 @@ namespace
|
||||
LocalString++;
|
||||
break;
|
||||
}
|
||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/' || *LocalString == '?'))
|
||||
{
|
||||
// port number is specified
|
||||
break;
|
||||
@ -280,12 +280,14 @@ namespace
|
||||
}
|
||||
|
||||
// skip '/'
|
||||
if (*CurrentString != '/')
|
||||
if (*CurrentString != '/' && *CurrentString != '?')
|
||||
{
|
||||
return clParseURL(LUrlParserError_NoSlash);
|
||||
}
|
||||
|
||||
CurrentString++;
|
||||
if (*CurrentString != '?') {
|
||||
CurrentString++;
|
||||
}
|
||||
|
||||
// parse the path
|
||||
LocalString = CurrentString;
|
||||
@ -333,6 +335,19 @@ namespace
|
||||
|
||||
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 ix
|
||||
@ -343,6 +358,18 @@ namespace ix
|
||||
std::string& path,
|
||||
std::string& query,
|
||||
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);
|
||||
|
||||
@ -356,23 +383,12 @@ namespace ix
|
||||
path = res.m_Path;
|
||||
query = res.m_Query;
|
||||
|
||||
const auto protocolPort = getProtocolPort(protocol);
|
||||
if (!res.GetPort(&port))
|
||||
{
|
||||
if (protocol == "ws" || protocol == "http")
|
||||
{
|
||||
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;
|
||||
}
|
||||
port = protocolPort;
|
||||
}
|
||||
isProtocolDefaultPort = port == protocolPort;
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
|
@ -19,5 +19,13 @@ namespace ix
|
||||
std::string& path,
|
||||
std::string& query,
|
||||
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
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include "IXUuid.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "IXWebSocketHandshake.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace
|
||||
@ -39,9 +40,12 @@ namespace ix
|
||||
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||
, _pingType(SendMessageKind::Ping)
|
||||
, _autoThreadName(true)
|
||||
{
|
||||
_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(
|
||||
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
|
||||
emptyMsg,
|
||||
@ -100,6 +104,17 @@ namespace ix
|
||||
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)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
@ -194,7 +209,7 @@ namespace ix
|
||||
|
||||
WebSocketHttpHeaders headers(_extraHeaders);
|
||||
std::string subProtocolsHeader;
|
||||
auto subProtocols = getSubProtocols();
|
||||
const auto &subProtocols = getSubProtocols();
|
||||
if (!subProtocols.empty())
|
||||
{
|
||||
//
|
||||
@ -204,7 +219,7 @@ namespace ix
|
||||
// 'json,msgpack'
|
||||
//
|
||||
int i = 0;
|
||||
for (auto subProtocol : subProtocols)
|
||||
for (const auto & subProtocol : subProtocols)
|
||||
{
|
||||
if (i++ != 0)
|
||||
{
|
||||
@ -232,7 +247,7 @@ namespace ix
|
||||
if (_pingIntervalSecs > 0)
|
||||
{
|
||||
// Send a heart beat right away
|
||||
_ws.sendHeartBeat();
|
||||
_ws.sendHeartBeat(_pingType);
|
||||
}
|
||||
|
||||
return status;
|
||||
@ -240,7 +255,8 @@ namespace ix
|
||||
|
||||
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
|
||||
int timeoutSecs,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
@ -249,7 +265,7 @@ namespace ix
|
||||
}
|
||||
|
||||
WebSocketInitResult status =
|
||||
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate);
|
||||
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request);
|
||||
if (!status.success)
|
||||
{
|
||||
return status;
|
||||
@ -266,7 +282,7 @@ namespace ix
|
||||
if (_pingIntervalSecs > 0)
|
||||
{
|
||||
// Send a heart beat right away
|
||||
_ws.sendHeartBeat();
|
||||
_ws.sendHeartBeat(_pingType);
|
||||
}
|
||||
|
||||
return status;
|
||||
@ -355,7 +371,10 @@ namespace ix
|
||||
|
||||
void WebSocket::run()
|
||||
{
|
||||
setThreadName(getUrl());
|
||||
if (_autoThreadName)
|
||||
{
|
||||
setThreadName(getUrl());
|
||||
}
|
||||
|
||||
bool firstConnectionAttempt = true;
|
||||
|
||||
@ -384,8 +403,9 @@ namespace ix
|
||||
[this](const std::string& msg,
|
||||
size_t wireSize,
|
||||
bool decompressionError,
|
||||
WebSocketTransport::MessageKind messageKind) {
|
||||
WebSocketMessageType webSocketMessageType{WebSocketMessageType::Error};
|
||||
WebSocketTransport::MessageKind messageKind)
|
||||
{
|
||||
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
|
||||
switch (messageKind)
|
||||
{
|
||||
case WebSocketTransport::MessageKind::MSG_TEXT:
|
||||
@ -503,13 +523,13 @@ namespace ix
|
||||
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
|
||||
constexpr size_t pingMaxPayloadSize = 125;
|
||||
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
||||
|
||||
return sendMessage(text, SendMessageKind::Ping);
|
||||
return sendMessage(text, pingType);
|
||||
}
|
||||
|
||||
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,
|
||||
@ -611,4 +631,9 @@ namespace ix
|
||||
std::lock_guard<std::mutex> lock(_configMutex);
|
||||
return _subProtocols;
|
||||
}
|
||||
|
||||
void WebSocket::setAutoThreadName(bool enabled)
|
||||
{
|
||||
_autoThreadName = enabled;
|
||||
}
|
||||
} // namespace ix
|
||||
|
@ -16,11 +16,12 @@
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXWebSocketMessage.h"
|
||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||
#include "IXWebSocketSendInfo.h"
|
||||
#include "IXWebSocketSendData.h"
|
||||
#include "IXWebSocketSendInfo.h"
|
||||
#include "IXWebSocketTransport.h"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@ -53,6 +54,8 @@ namespace ix
|
||||
void setPerMessageDeflateOptions(
|
||||
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
|
||||
void setPingMessage(const std::string& sendMessage,
|
||||
SendMessageKind pingType = SendMessageKind::Ping);
|
||||
void setPingInterval(int pingIntervalSecs);
|
||||
void enablePong();
|
||||
void disablePong();
|
||||
@ -88,7 +91,7 @@ namespace ix
|
||||
const OnProgressCallback& onProgressCallback = nullptr);
|
||||
WebSocketSendInfo sendText(const std::string& text,
|
||||
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,
|
||||
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
||||
@ -103,6 +106,7 @@ namespace ix
|
||||
|
||||
const std::string getUrl() const;
|
||||
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
|
||||
const std::string getPingMessage() const;
|
||||
int getPingInterval() const;
|
||||
size_t bufferedAmount() const;
|
||||
|
||||
@ -115,6 +119,8 @@ namespace ix
|
||||
uint32_t getMinWaitBetweenReconnectionRetries() const;
|
||||
const std::vector<std::string>& getSubProtocols();
|
||||
|
||||
void setAutoThreadName(bool enabled);
|
||||
|
||||
private:
|
||||
WebSocketSendInfo sendMessage(const IXWebSocketSendData& message,
|
||||
SendMessageKind sendMessageKind,
|
||||
@ -128,7 +134,8 @@ namespace ix
|
||||
// Server
|
||||
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
|
||||
int timeoutSecs,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request = nullptr);
|
||||
|
||||
WebSocketTransport _ws;
|
||||
|
||||
@ -169,12 +176,17 @@ namespace ix
|
||||
// Optional ping and pong timeout
|
||||
int _pingIntervalSecs;
|
||||
int _pingTimeoutSecs;
|
||||
std::string _pingMessage;
|
||||
SendMessageKind _pingType;
|
||||
static const int kDefaultPingIntervalSecs;
|
||||
static const int kDefaultPingTimeoutSecs;
|
||||
|
||||
// Subprotocols
|
||||
std::vector<std::string> _subProtocols;
|
||||
|
||||
// enable or disable auto set thread name
|
||||
bool _autoThreadName;
|
||||
|
||||
friend class WebSocketServer;
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "IXWebSocketHandshake.h"
|
||||
|
||||
#include "IXBase64.h"
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocketConnect.h"
|
||||
#include "IXStrCaseCompare.h"
|
||||
@ -17,7 +18,6 @@
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace ix
|
||||
{
|
||||
WebSocketHandshake::WebSocketHandshake(
|
||||
@ -87,6 +87,7 @@ namespace ix
|
||||
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
||||
const std::string& url,
|
||||
const WebSocketHttpHeaders& extraHeaders,
|
||||
const std::string& protocol,
|
||||
const std::string& host,
|
||||
const std::string& path,
|
||||
int port,
|
||||
@ -106,15 +107,10 @@ namespace ix
|
||||
return WebSocketInitResult(false, 0, ss.str());
|
||||
}
|
||||
|
||||
//
|
||||
// Generate a random 24 bytes string which looks like it is base64 encoded
|
||||
// y3JJHMbDL1EzLkh9GBhXDw==
|
||||
// 0cb3Vd9HkbpVVumoS3Noka==
|
||||
// Generate a random 16 bytes string and base64 encode it.
|
||||
//
|
||||
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
||||
//
|
||||
std::string secWebSocketKey = genRandomString(22);
|
||||
secWebSocketKey += "==";
|
||||
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
||||
@ -130,6 +126,12 @@ namespace ix
|
||||
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)
|
||||
{
|
||||
ss << it.first << ": " << it.second << "\r\n";
|
||||
@ -245,28 +247,42 @@ namespace ix
|
||||
}
|
||||
|
||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request)
|
||||
{
|
||||
_requestInitCancellation = false;
|
||||
|
||||
auto isCancellationRequested =
|
||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
||||
|
||||
// Read first line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
std::string method;
|
||||
std::string uri;
|
||||
std::string httpVersion;
|
||||
|
||||
if (!lineValid)
|
||||
if (request)
|
||||
{
|
||||
return sendErrorResponse(400, "Error reading HTTP request line");
|
||||
method = request->method;
|
||||
uri = request->uri;
|
||||
httpVersion = request->version;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read first line
|
||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
||||
auto lineValid = lineResult.first;
|
||||
auto line = lineResult.second;
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
auto method = std::get<0>(requestLine);
|
||||
auto uri = std::get<1>(requestLine);
|
||||
auto httpVersion = std::get<2>(requestLine);
|
||||
if (!lineValid)
|
||||
{
|
||||
return sendErrorResponse(400, "Error reading HTTP request line");
|
||||
}
|
||||
|
||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||
auto requestLine = Http::parseRequestLine(line);
|
||||
method = std::get<0>(requestLine);
|
||||
uri = std::get<1>(requestLine);
|
||||
httpVersion = std::get<2>(requestLine);
|
||||
}
|
||||
|
||||
if (method != "GET")
|
||||
{
|
||||
@ -279,14 +295,22 @@ namespace ix
|
||||
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
||||
}
|
||||
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
auto headers = result.second;
|
||||
|
||||
if (!headersValid)
|
||||
WebSocketHttpHeaders headers;
|
||||
if (request)
|
||||
{
|
||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
||||
headers = request->headers;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retrieve and validate HTTP headers
|
||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
||||
auto headersValid = result.first;
|
||||
headers = result.second;
|
||||
|
||||
if (!headersValid)
|
||||
{
|
||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
||||
}
|
||||
}
|
||||
|
||||
if (headers.find("sec-websocket-key") == headers.end())
|
||||
|
@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "IXCancellationRequest.h"
|
||||
#include "IXHttp.h"
|
||||
#include "IXSocket.h"
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXWebSocketInitResult.h"
|
||||
@ -30,12 +31,15 @@ namespace ix
|
||||
|
||||
WebSocketInitResult clientHandshake(const std::string& url,
|
||||
const WebSocketHttpHeaders& extraHeaders,
|
||||
const std::string& protocol,
|
||||
const std::string& host,
|
||||
const std::string& path,
|
||||
int port,
|
||||
int timeoutSecs);
|
||||
|
||||
WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate);
|
||||
WebSocketInitResult serverHandshake(int timeoutSecs,
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request = nullptr);
|
||||
|
||||
private:
|
||||
std::string genRandomString(const int len);
|
||||
|
@ -46,6 +46,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "IXWebSocketPerMessageDeflate.h"
|
||||
|
||||
#include "IXUniquePtr.h"
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "zlib.h"
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "IXWebSocketSendData.h"
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace ix
|
||||
|
@ -57,7 +57,7 @@ namespace ix
|
||||
server.setOnConnectionCallback(
|
||||
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
||||
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();
|
||||
|
||||
// Server connection
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
@ -19,17 +19,20 @@ namespace ix
|
||||
{
|
||||
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
||||
const bool WebSocketServer::kDefaultEnablePong(true);
|
||||
const int WebSocketServer::kPingIntervalSeconds(-1); // disable heartbeat
|
||||
|
||||
WebSocketServer::WebSocketServer(int port,
|
||||
const std::string& host,
|
||||
int backlog,
|
||||
size_t maxConnections,
|
||||
int handshakeTimeoutSecs,
|
||||
int addressFamily)
|
||||
int addressFamily,
|
||||
int pingIntervalSeconds)
|
||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _enablePerMessageDeflate(true)
|
||||
, _pingIntervalSeconds(pingIntervalSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
@ -79,9 +82,22 @@ namespace ix
|
||||
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
|
||||
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>();
|
||||
|
||||
webSocket->setAutoThreadName(false);
|
||||
webSocket->setPingInterval(_pingIntervalSeconds);
|
||||
|
||||
if (_onConnectionCallback)
|
||||
{
|
||||
_onConnectionCallback(webSocket, connectionState);
|
||||
@ -89,7 +105,7 @@ namespace ix
|
||||
if (!webSocket->isOnMessageCallbackRegistered())
|
||||
{
|
||||
logError("WebSocketServer Application developer error: Server callback improperly "
|
||||
"registerered.");
|
||||
"registered.");
|
||||
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
|
||||
connectionState->setTerminated();
|
||||
return;
|
||||
@ -99,9 +115,8 @@ namespace ix
|
||||
{
|
||||
WebSocket* webSocketRawPtr = webSocket.get();
|
||||
webSocket->setOnMessageCallback(
|
||||
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
|
||||
_onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
|
||||
});
|
||||
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg)
|
||||
{ _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); });
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -130,7 +145,7 @@ namespace ix
|
||||
}
|
||||
|
||||
auto status = webSocket->connectToSocket(
|
||||
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate);
|
||||
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate, request);
|
||||
if (status.success)
|
||||
{
|
||||
// Process incoming messages and execute callbacks
|
||||
@ -155,8 +170,6 @@ namespace ix
|
||||
logError("Cannot delete client");
|
||||
}
|
||||
}
|
||||
|
||||
connectionState->setTerminated();
|
||||
}
|
||||
|
||||
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
||||
@ -176,28 +189,30 @@ namespace ix
|
||||
//
|
||||
void WebSocketServer::makeBroadcastServer()
|
||||
{
|
||||
setOnClientMessageCallback([this](std::shared_ptr<ConnectionState> connectionState,
|
||||
WebSocket& webSocket,
|
||||
const WebSocketMessagePtr& msg) {
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
setOnClientMessageCallback(
|
||||
[this](std::shared_ptr<ConnectionState> connectionState,
|
||||
WebSocket& webSocket,
|
||||
const WebSocketMessagePtr& msg)
|
||||
{
|
||||
for (auto&& client : getClients())
|
||||
auto remoteIp = connectionState->getRemoteIp();
|
||||
if (msg->type == ix::WebSocketMessageType::Message)
|
||||
{
|
||||
if (client.get() != &webSocket)
|
||||
for (auto&& client : getClients())
|
||||
{
|
||||
client->send(msg->str, msg->binary);
|
||||
|
||||
// Make sure the OS send buffer is flushed before moving on
|
||||
do
|
||||
if (client.get() != &webSocket)
|
||||
{
|
||||
std::chrono::duration<double, std::milli> duration(500);
|
||||
std::this_thread::sleep_for(duration);
|
||||
} while (client->bufferedAmount() != 0);
|
||||
client->send(msg->str, msg->binary);
|
||||
|
||||
// Make sure the OS send buffer is flushed before moving on
|
||||
do
|
||||
{
|
||||
std::chrono::duration<double, std::milli> duration(500);
|
||||
std::this_thread::sleep_for(duration);
|
||||
} while (client->bufferedAmount() != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
bool WebSocketServer::listenAndStart()
|
||||
|
@ -33,7 +33,8 @@ namespace ix
|
||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
||||
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||
int pingIntervalSeconds = WebSocketServer::kPingIntervalSeconds);
|
||||
virtual ~WebSocketServer();
|
||||
virtual void stop() final;
|
||||
|
||||
@ -55,11 +56,13 @@ namespace ix
|
||||
int getHandshakeTimeoutSecs();
|
||||
bool isPongEnabled();
|
||||
bool isPerMessageDeflateEnabled();
|
||||
|
||||
private:
|
||||
// Member variables
|
||||
int _handshakeTimeoutSecs;
|
||||
bool _enablePong;
|
||||
bool _enablePerMessageDeflate;
|
||||
int _pingIntervalSeconds;
|
||||
|
||||
OnConnectionCallback _onConnectionCallback;
|
||||
OnClientMessageCallback _onClientMessageCallback;
|
||||
@ -68,10 +71,16 @@ namespace ix
|
||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||
|
||||
const static bool kDefaultEnablePong;
|
||||
const static int kPingIntervalSeconds;
|
||||
|
||||
// Methods
|
||||
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState);
|
||||
virtual size_t getConnectedClientsCount() final;
|
||||
|
||||
protected:
|
||||
void handleUpgrade(std::unique_ptr<Socket> socket,
|
||||
std::shared_ptr<ConnectionState> connectionState,
|
||||
HttpRequestPtr request = nullptr);
|
||||
};
|
||||
} // namespace ix
|
||||
|
@ -45,7 +45,6 @@
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@ -54,7 +53,6 @@
|
||||
|
||||
namespace ix
|
||||
{
|
||||
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
|
||||
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
|
||||
const bool WebSocketTransport::kDefaultEnablePong(true);
|
||||
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
|
||||
@ -74,6 +72,9 @@ namespace ix
|
||||
, _enablePong(kDefaultEnablePong)
|
||||
, _pingIntervalSecs(kDefaultPingIntervalSecs)
|
||||
, _pongReceived(false)
|
||||
, _setCustomMessage(false)
|
||||
, _kPingMessage("ixwebsocket::heartbeat")
|
||||
, _pingType(SendMessageKind::Ping)
|
||||
, _pingCount(0)
|
||||
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
|
||||
{
|
||||
@ -139,7 +140,7 @@ namespace ix
|
||||
_enablePerMessageDeflate);
|
||||
|
||||
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)
|
||||
{
|
||||
@ -170,7 +171,8 @@ namespace ix
|
||||
// Server
|
||||
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
|
||||
int timeoutSecs,
|
||||
bool enablePerMessageDeflate)
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||
|
||||
@ -187,7 +189,8 @@ namespace ix
|
||||
_perMessageDeflateOptions,
|
||||
_enablePerMessageDeflate);
|
||||
|
||||
auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate);
|
||||
auto result =
|
||||
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
|
||||
if (result.success)
|
||||
{
|
||||
setReadyState(ReadyState::OPEN);
|
||||
@ -248,13 +251,51 @@ namespace ix
|
||||
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;
|
||||
std::stringstream ss;
|
||||
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
|
||||
<< "::" << _pingCount++;
|
||||
return sendPing(ss.str());
|
||||
|
||||
ss << _kPingMessage;
|
||||
if (!_setCustomMessage)
|
||||
{
|
||||
ss << "::" << _pingIntervalSecs << "s"
|
||||
<< "::" << _pingCount++;
|
||||
}
|
||||
if (pingMessage == SendMessageKind::Ping)
|
||||
{
|
||||
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()
|
||||
@ -270,7 +311,9 @@ namespace ix
|
||||
{
|
||||
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
|
||||
close(WebSocketCloseConstants::kInternalErrorCode,
|
||||
@ -278,7 +321,7 @@ namespace ix
|
||||
}
|
||||
else
|
||||
{
|
||||
sendHeartBeat();
|
||||
sendHeartBeat(_pingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -657,6 +700,7 @@ namespace ix
|
||||
if (_readyState != ReadyState::CLOSING)
|
||||
{
|
||||
// send back the CLOSE frame
|
||||
setReadyState(ReadyState::CLOSING);
|
||||
sendCloseFrame(code, reason);
|
||||
|
||||
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
|
||||
@ -1029,7 +1073,10 @@ namespace ix
|
||||
else if (ret <= 0)
|
||||
{
|
||||
closeSocket();
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
if (_readyState != ReadyState::CLOSING)
|
||||
{
|
||||
setReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -1127,7 +1174,22 @@ namespace ix
|
||||
{
|
||||
_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)
|
||||
{
|
||||
|
@ -18,9 +18,10 @@
|
||||
#include "IXWebSocketHttpHeaders.h"
|
||||
#include "IXWebSocketPerMessageDeflate.h"
|
||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
||||
#include "IXWebSocketSendInfo.h"
|
||||
#include "IXWebSocketSendData.h"
|
||||
#include "IXWebSocketSendInfo.h"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@ -86,7 +87,8 @@ namespace ix
|
||||
// Server
|
||||
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
|
||||
int timeoutSecs,
|
||||
bool enablePerMessageDeflate);
|
||||
bool enablePerMessageDeflate,
|
||||
HttpRequestPtr request = nullptr);
|
||||
|
||||
PollResult poll();
|
||||
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
|
||||
@ -108,8 +110,12 @@ namespace ix
|
||||
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
|
||||
size_t bufferedAmount() const;
|
||||
|
||||
// set ping heartbeat message
|
||||
void setPingMessage(const std::string& message, SendMessageKind pingType);
|
||||
|
||||
// internal
|
||||
WebSocketSendInfo sendHeartBeat();
|
||||
// send any type of ping packet, not only 'ping' type
|
||||
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
|
||||
|
||||
private:
|
||||
std::string _url;
|
||||
@ -214,7 +220,10 @@ namespace ix
|
||||
std::atomic<bool> _pongReceived;
|
||||
|
||||
static const int kDefaultPingIntervalSecs;
|
||||
static const std::string kPingMessage;
|
||||
|
||||
bool _setCustomMessage;
|
||||
std::string _kPingMessage;
|
||||
SendMessageKind _pingType;
|
||||
std::atomic<uint64_t> _pingCount;
|
||||
|
||||
// We record when ping are being sent so that we can know when to send the next one
|
||||
|
@ -6,4 +6,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define IX_WEBSOCKET_VERSION "11.4.0"
|
||||
#define IX_WEBSOCKET_VERSION "11.4.6"
|
||||
|
20
makefile.dev
20
makefile.dev
@ -13,16 +13,24 @@ all: brew
|
||||
|
||||
install: brew
|
||||
|
||||
-DCMAKE_INSTALL_PREFIX=/opt/homebrew
|
||||
|
||||
# Use -DCMAKE_INSTALL_PREFIX= to install into another location
|
||||
# on osx it is good practice to make /usr/local user writable
|
||||
# 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
|
||||
#
|
||||
# Default rule does not use python as that requires first time users to have Python3 installed
|
||||
#
|
||||
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
|
||||
# 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)
|
||||
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
IXWebSocketTestConnectionDisconnection
|
||||
IXUrlParserTest
|
||||
IXHttpClientTest
|
||||
# IXHttpClientTest ## FIXME httpbin.org does not seem normal
|
||||
IXUnityBuildsTest
|
||||
IXHttpTest
|
||||
IXDNSLookupTest
|
||||
@ -24,14 +24,13 @@ set (TEST_TARGET_NAMES
|
||||
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
|
||||
IXStrCaseCompareTest
|
||||
IXExponentialBackoffTest
|
||||
IXWebSocketCloseTest
|
||||
)
|
||||
|
||||
# Some unittest don't work on windows yet
|
||||
# Windows without TLS does not have hmac yet
|
||||
if (UNIX)
|
||||
list(APPEND TEST_TARGET_NAMES
|
||||
IXWebSocketCloseTest
|
||||
|
||||
# Fail on Windows in CI probably because the pathing is wrong and
|
||||
# some resource files cannot be found
|
||||
IXHttpServerTest
|
||||
|
@ -19,13 +19,9 @@ TEST_CASE("dns", "[net]")
|
||||
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
|
||||
|
||||
std::string errMsg;
|
||||
struct addrinfo* res;
|
||||
|
||||
res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||
auto res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||
std::cerr << "Error message: " << errMsg << std::endl;
|
||||
REQUIRE(res != nullptr);
|
||||
|
||||
dnsLookup->release(res);
|
||||
}
|
||||
|
||||
SECTION("Test resolving a non-existing hostname")
|
||||
@ -33,7 +29,7 @@ TEST_CASE("dns", "[net]")
|
||||
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
|
||||
|
||||
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;
|
||||
REQUIRE(res == nullptr);
|
||||
}
|
||||
@ -44,7 +40,7 @@ TEST_CASE("dns", "[net]")
|
||||
|
||||
std::string errMsg;
|
||||
// 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;
|
||||
REQUIRE(res == nullptr);
|
||||
}
|
||||
|
@ -7,7 +7,9 @@
|
||||
#include "catch.hpp"
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <ixwebsocket/IXGetFreePort.h>
|
||||
#include <ixwebsocket/IXHttpClient.h>
|
||||
#include <ixwebsocket/IXHttpServer.h>
|
||||
|
||||
using namespace ix;
|
||||
|
||||
@ -95,6 +97,52 @@ TEST_CASE("http_client", "[http]")
|
||||
}
|
||||
#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")
|
||||
{
|
||||
bool async = true;
|
||||
|
@ -60,6 +60,7 @@ TEST_CASE("http server", "[httpd]")
|
||||
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||
REQUIRE(response->statusCode == 200);
|
||||
REQUIRE(response->headers["Accept-Encoding"] == "gzip");
|
||||
REQUIRE(response->headers["Content-Encoding"] == "gzip");
|
||||
|
||||
server.stop();
|
||||
}
|
||||
|
@ -84,6 +84,40 @@ namespace ix
|
||||
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")
|
||||
{
|
||||
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()));
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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::size_type idx;
|
||||
@ -916,9 +892,8 @@ namespace ix
|
||||
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
|
||||
|
||||
std::string errMsg;
|
||||
struct addrinfo* res;
|
||||
|
||||
res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||
auto res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||
|
||||
auto addr = res->ai_addr;
|
||||
|
||||
@ -2486,10 +2461,8 @@ int main(int argc, char** argv)
|
||||
bool verbose = false;
|
||||
bool save = false;
|
||||
bool quiet = false;
|
||||
bool fluentd = false;
|
||||
bool compress = false;
|
||||
bool compressRequest = false;
|
||||
bool stress = false;
|
||||
bool disableAutomaticReconnection = false;
|
||||
bool disablePerMessageDeflate = false;
|
||||
bool greetings = false;
|
||||
@ -2505,7 +2478,6 @@ int main(int argc, char** argv)
|
||||
int transferTimeout = 1800;
|
||||
int maxRedirects = 5;
|
||||
int delayMs = -1;
|
||||
int count = 1;
|
||||
int msgCount = 1000 * 1000;
|
||||
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
|
||||
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");
|
||||
app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)");
|
||||
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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user