Compare commits

...

29 Commits

Author SHA1 Message Date
a2cbfcebec Support server subprotocols 2024-07-05 18:49:31 +02:00
9884c325dd makefile: remove some install targets 2024-05-17 02:02:58 -07:00
c27f5a94bd Version check mbedtls instead of introducing a new define (#516) 2024-05-08 11:02:00 -07:00
2d47af89cf IXSocket.h: add missing <sys/types.h> for macOS (#512) 2024-05-08 07:33:37 -07:00
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
1d210c0139 Initialize the PSA Crypto API if requested (#514) 2024-04-29 21:12:56 -07:00
dc8807ec9d changelog 2024-03-27 22:12:37 -07:00
03e5a6970f bump version in CMakeLists.txt 2024-03-27 22:11:15 -07:00
38d6da7755 Fix bad version variable (#510) 2024-03-27 22:06:44 -07:00
9ef61bf224 bump version 2024-03-27 22:06:32 -07:00
93e673da9f Merge branch 'master' of github.com:machinezone/IXWebSocket 2024-03-27 22:03:54 -07:00
92beef8348 avoid some object copies 2024-03-27 22:03:26 -07:00
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
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
39e085bebc Fix MbedTLS disconnect handling. (#500) 2024-03-18 22:23:14 -07:00
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
c5a02f1066 Update README.md 2024-02-10 22:03:22 -08:00
e03c0be8a4 Update unittest_windows_gcc.yml (#494)
* Update unittest_windows_gcc.yml

* Update unittest_windows_gcc.yml
2023-11-20 22:28:12 -08:00
RH
3b66efbb6a Fix C++/WinRT compile issue (#493) 2023-11-15 10:40:49 -08:00
f29906c72f Allow building without rtti (#487)
Since factory returns a ProxyConnectionState, setOnConnectionCallback
will be a ProxyConnectionState. The code already makes that assumption,
since it does not check of state return value. Using a
static_pointer_cast will allow the library to be build with rtti.
2023-10-13 19:56:24 -07:00
872f516ede allow building when cpp exceptions are disabled (#489)
IXWebSocket needs exceptions support because of the use of stoi. In
order to build when cpp exceptions are disabled, we can use strtol.
2023-10-13 19:55:26 -07:00
014d43eb13 stop building mingw ; if someone wants to maintain this port please reach out ! 2023-10-13 19:54:25 -07:00
d77067e50f Update window gcc action to latest setup_mingw 2023-10-13 19:39:41 -07:00
ed5b1a0895 Fix links & update info in README (#485) 2023-09-15 22:36:52 -07:00
ef57e3a2b1 Fix hanging of WebSocket::stop() waiting for its thread to join (#481)
This might happen in some special cases where
WebSocketTransport::sendOnSocket() fails, closes the socket, and set
the ready state to CLOSED, but WebSocket::run() stills wait the
interrupt event to happen.

Possibly related to #474
2023-09-01 08:11:36 -07:00
28832f8732 Fix #286 - http response headers overwritten with request headers (#483) 2023-09-01 08:11:07 -07:00
0dd284267a Fix MinGW build warning (#482) 2023-09-01 08:00:35 -07:00
a7019631b7 Fix server empty thread name (#478) 2023-08-01 22:16:43 -07:00
632ee31509 Update IXSocketMbedTLS.cpp (#471) 2023-06-22 14:12:51 -07:00
30 changed files with 245 additions and 79 deletions

View File

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

View File

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

@ -1,5 +1,7 @@
## 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. Two important design goals are simplicity and correctness.
@ -99,15 +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

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

@ -27,9 +27,19 @@
// 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
{

View File

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

View File

@ -148,6 +148,7 @@ namespace ix
content = gzipCompress(content);
headers["Content-Encoding"] = "gzip";
}
headers["Accept-Encoding"] = "gzip";
#endif
// Log request
@ -161,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);
});

View File

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

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,9 +47,16 @@ 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 */)
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
{
#ifdef _WIN32
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
@ -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

@ -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,10 +294,16 @@ 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
}

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

@ -41,6 +41,7 @@ namespace ix
, _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pingType(SendMessageKind::Ping)
, _autoThreadName(true)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
@ -208,7 +209,7 @@ namespace ix
WebSocketHttpHeaders headers(_extraHeaders);
std::string subProtocolsHeader;
auto subProtocols = getSubProtocols();
const auto &subProtocols = getSubProtocols();
if (!subProtocols.empty())
{
//
@ -218,7 +219,7 @@ namespace ix
// 'json,msgpack'
//
int i = 0;
for (auto subProtocol : subProtocols)
for (const auto & subProtocol : subProtocols)
{
if (i++ != 0)
{
@ -264,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;
@ -370,7 +372,10 @@ namespace ix
void WebSocket::run()
{
setThreadName(getUrl());
if (_autoThreadName)
{
setThreadName(getUrl());
}
bool firstConnectionAttempt = true;
@ -627,4 +632,9 @@ namespace ix
std::lock_guard<std::mutex> lock(_configMutex);
return _subProtocols;
}
void WebSocket::setAutoThreadName(bool enabled)
{
_autoThreadName = enabled;
}
} // namespace ix

View File

@ -119,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,
@ -182,6 +184,9 @@ namespace ix
// Subprotocols
std::vector<std::string> _subProtocols;
// enable or disable auto set thread name
bool _autoThreadName;
friend class WebSocketServer;
};
} // namespace ix

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

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

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)
{
@ -91,6 +104,15 @@ namespace ix
setThreadName("Srv:ws:" + connectionState->getId());
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)
{
_onConnectionCallback(webSocket, connectionState);

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);
@ -1174,7 +1175,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)
{

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

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

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 =