Compare commits

...

16 Commits

Author SHA1 Message Date
a2cbfcebec Support server subprotocols 2024-07-05 18:49:31 +02:00
Benjamin Sergeant
9884c325dd makefile: remove some install targets 2024-05-17 02:02:58 -07:00
teejusb
c27f5a94bd Version check mbedtls instead of introducing a new define (#516) 2024-05-08 11:02:00 -07:00
Sergey Fedorov
2d47af89cf IXSocket.h: add missing <sys/types.h> for macOS (#512) 2024-05-08 07:33:37 -07:00
Sergey Fedorov
c106e6cb24 Minor fixes for < 10.6 (#515)
* Fix for missing AI_NUMERICSERV on < 10.6

* Do not use pthread_setname_np on < 10.6
2024-05-08 07:33:23 -07:00
teejusb
1d210c0139 Initialize the PSA Crypto API if requested (#514) 2024-04-29 21:12:56 -07:00
Benjamin Sergeant
dc8807ec9d changelog 2024-03-27 22:12:37 -07:00
Benjamin Sergeant
03e5a6970f bump version in CMakeLists.txt 2024-03-27 22:11:15 -07:00
Olivia (Zoe)
38d6da7755 Fix bad version variable (#510) 2024-03-27 22:06:44 -07:00
Benjamin Sergeant
9ef61bf224 bump version 2024-03-27 22:06:32 -07:00
Benjamin Sergeant
93e673da9f Merge branch 'master' of github.com:machinezone/IXWebSocket 2024-03-27 22:03:54 -07:00
Benjamin Sergeant
92beef8348 avoid some object copies 2024-03-27 22:03:26 -07:00
Giuseppe Penone
755d98d918 Support URLs with no slash before the question mark (#507)
* Support Url No Slash Before Question Mark

* Support Url No Slash Before Question Mark

* unit test fix

---------

Co-authored-by: Giuseppe Penone <giuseppe.penone@delonghigroup.com>
2024-03-18 22:26:45 -07:00
CryptoManiac
98b4828e93 Update IXSelectInterruptPipe.cpp (#502)
Valgrind keeps complaining that close() on the invalid descriptor -1 is happening here. I suppose that close shouldn't be called when the descriptor value is known to be invalid. It's not a fatal error or something, but this practice is able to create a lot of flood in the logs.
2024-03-18 22:24:11 -07:00
Paul Whiting
39e085bebc Fix MbedTLS disconnect handling. (#500) 2024-03-18 22:23:14 -07:00
Daniel Wymark
70602c4e6b Add ping interval to constructor params for WebSocketServer (#497)
* Update .gitignore for CLion compatibility

* Add pingIntervalSeconds to constructor for WebSocketServer

* Add Heartbeat section to WebSocketServer usage documentation

* Fix typo
2024-03-12 09:46:27 -07:00
21 changed files with 174 additions and 24 deletions

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ ws/.srl
ixhttpd
makefile
a.out
.idea/
cmake-build-debug/

View File

@@ -6,7 +6,7 @@
cmake_minimum_required(VERSION 3.4.1...3.17.2)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(ixwebsocket LANGUAGES C CXX VERSION 11.4.4)
project(ixwebsocket LANGUAGES C CXX VERSION 11.4.6)
set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON)
@@ -168,8 +168,7 @@ if(BUILD_SHARED_LIBS)
)
# Set library version
set_target_properties(ixwebsocket PROPERTIES VERSION ${CMAKE_PROJECT_VERSION})
set_target_properties(ixwebsocket PROPERTIES VERSION ${PROJECT_VERSION})
else()
# Static library
add_library( ixwebsocket

View File

@@ -110,6 +110,7 @@ If your company or project is using this library, feel free to open an issue or
- [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

View File

@@ -2,6 +2,10 @@
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

View File

@@ -445,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

View File

@@ -35,6 +35,12 @@
#endif
#endif
#ifdef __APPLE__
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
#endif
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms

View File

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

View File

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

View File

@@ -13,6 +13,10 @@
#include <mutex>
#include <string>
#ifdef __APPLE__
#include <sys/types.h>
#endif
#ifdef _WIN32
#include <basetsd.h>
#ifdef _MSC_VER

View File

@@ -47,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)
@@ -352,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;

View File

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

View File

@@ -209,7 +209,7 @@ namespace ix
WebSocketHttpHeaders headers(_extraHeaders);
std::string subProtocolsHeader;
auto subProtocols = getSubProtocols();
const auto &subProtocols = getSubProtocols();
if (!subProtocols.empty())
{
//
@@ -219,7 +219,7 @@ namespace ix
// 'json,msgpack'
//
int i = 0;
for (auto subProtocol : subProtocols)
for (const auto & subProtocol : subProtocols)
{
if (i++ != 0)
{
@@ -265,7 +265,8 @@ namespace ix
}
WebSocketInitResult status =
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request);
_ws.connectToSocket(
std::move(socket), timeoutSecs, enablePerMessageDeflate, getSubProtocols(), request);
if (!status.success)
{
return status;

View File

@@ -246,8 +246,30 @@ namespace ix
return WebSocketInitResult(true, status, "", headers, path);
}
void WebSocketHandshake::getSelectedSubProtocol(const std::vector<std::string>& subProtocols,
std::string& selectedSubProtocol,
const std::string& headerSubProtocol)
{
std::stringstream ss;
ss << headerSubProtocol;
std::string protocol;
while (std::getline(ss, protocol, ','))
{
bool subProtocolFound = false;
for (const auto& supportedSubProtocol : subProtocols)
{
if (protocol != supportedSubProtocol) continue;
selectedSubProtocol = protocol;
subProtocolFound = true;
break;
}
if (subProtocolFound) break;
}
}
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
const std::vector<std::string>& subProtocols,
HttpRequestPtr request)
{
_requestInitCancellation = false;
@@ -362,6 +384,17 @@ namespace ix
ss << "Connection: Upgrade\r\n";
ss << "Server: " << userAgent() << "\r\n";
if(!subProtocols.empty())
{
std::string headerSubProtocol = headers["sec-websocket-protocol"];
std::string selectedSubProtocol;
getSelectedSubProtocol(subProtocols, selectedSubProtocol, headerSubProtocol);
if(!selectedSubProtocol.empty())
{
ss << "Sec-WebSocket-Protocol: " << selectedSubProtocol << "\r\n";
}
}
// Parse the client headers. Does it support deflate ?
std::string header = headers["sec-websocket-extensions"];
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);

View File

@@ -39,6 +39,7 @@ namespace ix
WebSocketInitResult serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
const std::vector<std::string>& subProtocols,
HttpRequestPtr request = nullptr);
private:
@@ -49,6 +50,10 @@ namespace ix
bool insensitiveStringCompare(const std::string& a, const std::string& b);
static void getSelectedSubProtocol(const std::vector<std::string>& subProtocols,
std::string& selectedSubProtocol,
const std::string& headerSubProtocol);
std::atomic<bool>& _requestInitCancellation;
std::unique_ptr<Socket>& _socket;
WebSocketPerMessageDeflatePtr& _perMessageDeflate;

View File

@@ -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)
{
}
@@ -76,6 +79,16 @@ namespace ix
_onClientMessageCallback = callback;
}
void WebSocketServer::addSubProtocol(const std::string& subProtocol)
{
_subProtocols.push_back(subProtocol);
}
const std::vector<std::string>& WebSocketServer::getSubProtocols()
{
return _subProtocols;
}
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
@@ -93,6 +106,12 @@ namespace ix
auto webSocket = std::make_shared<WebSocket>();
webSocket->setAutoThreadName(false);
webSocket->setPingInterval(_pingIntervalSeconds);
if(!_subProtocols.empty())
{
for(const auto& subProtocol : _subProtocols)
webSocket->addSubProtocol(subProtocol);
}
if (_onConnectionCallback)
{

View File

@@ -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;
@@ -44,6 +45,9 @@ namespace ix
void setOnConnectionCallback(const OnConnectionCallback& callback);
void setOnClientMessageCallback(const OnClientMessageCallback& callback);
void addSubProtocol(const std::string& subProtocol);
const std::vector<std::string>& getSubProtocols();
// Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients();
@@ -61,6 +65,8 @@ namespace ix
int _handshakeTimeoutSecs;
bool _enablePong;
bool _enablePerMessageDeflate;
int _pingIntervalSeconds;
std::vector<std::string> _subProtocols;
OnConnectionCallback _onConnectionCallback;
OnClientMessageCallback _onClientMessageCallback;
@@ -69,6 +75,7 @@ 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,

View File

@@ -172,6 +172,7 @@ namespace ix
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate,
const std::vector<std::string>& subProtocols,
HttpRequestPtr request)
{
std::lock_guard<std::mutex> lock(_socketMutex);
@@ -190,7 +191,7 @@ namespace ix
_enablePerMessageDeflate);
auto result =
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, subProtocols, request);
if (result.success)
{
setReadyState(ReadyState::OPEN);

View File

@@ -88,6 +88,7 @@ namespace ix
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate,
const std::vector<std::string>& subProtocols,
HttpRequestPtr request = nullptr);
PollResult poll();

View File

@@ -6,4 +6,4 @@
#pragma once
#define IX_WEBSOCKET_VERSION "11.4.4"
#define IX_WEBSOCKET_VERSION "11.4.6"

View File

@@ -27,9 +27,9 @@ install: brew
#
brew:
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 install)
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 install)
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
@@ -39,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)
@@ -54,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

View File

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