Merge branch 'master' of github.com:machinezone/IXWebSocket
This commit is contained in:
commit
93e673da9f
2
.github/workflows/unittest_windows_gcc.yml
vendored
2
.github/workflows/unittest_windows_gcc.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||||
- uses: egor-tensin/setup-mingw@v2.2.0
|
- uses: bsergean/setup-mingw@d79ce405bac9edef3a1726ef00554a56f0bafe66
|
||||||
- run: |
|
- run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ ws/.srl
|
|||||||
ixhttpd
|
ixhttpd
|
||||||
makefile
|
makefile
|
||||||
a.out
|
a.out
|
||||||
|
.idea/
|
||||||
|
cmake-build-debug/
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
## Hello world
|
## Hello world
|
||||||
|
|
||||||
|
(note from the main developer, sadly I don't have too much time to devote to this library anymore, maybe it's time to pass the maintenance to someone else more motivated ?)
|
||||||
|
|
||||||
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
|
||||||
|
|
||||||
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
|
||||||
|
@ -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
|
## HTTP client API
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
@ -133,16 +133,20 @@ namespace ix
|
|||||||
if (headers.find("Content-Length") != headers.end())
|
if (headers.find("Content-Length") != headers.end())
|
||||||
{
|
{
|
||||||
int contentLength = 0;
|
int contentLength = 0;
|
||||||
try
|
|
||||||
{
|
{
|
||||||
contentLength = std::stoi(headers["Content-Length"]);
|
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)
|
if (contentLength < 0)
|
||||||
{
|
{
|
||||||
return std::make_tuple(
|
return std::make_tuple(
|
||||||
|
@ -34,8 +34,12 @@ namespace ix
|
|||||||
|
|
||||||
SelectInterruptPipe::~SelectInterruptPipe()
|
SelectInterruptPipe::~SelectInterruptPipe()
|
||||||
{
|
{
|
||||||
::close(_fildes[kPipeReadIndex]);
|
if (-1 != _fildes[kPipeReadIndex]) {
|
||||||
::close(_fildes[kPipeWriteIndex]);
|
::close(_fildes[kPipeReadIndex]);
|
||||||
|
}
|
||||||
|
if (-1 != _fildes[kPipeWriteIndex]) {
|
||||||
|
::close(_fildes[kPipeWriteIndex]);
|
||||||
|
}
|
||||||
_fildes[kPipeReadIndex] = -1;
|
_fildes[kPipeReadIndex] = -1;
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
_fildes[kPipeWriteIndex] = -1;
|
||||||
}
|
}
|
||||||
|
@ -352,6 +352,11 @@ namespace ix
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
errno = ECONNRESET;
|
||||||
|
}
|
||||||
|
|
||||||
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
||||||
{
|
{
|
||||||
errno = EWOULDBLOCK;
|
errno = EWOULDBLOCK;
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// For manipulating the certificate store
|
// For manipulating the certificate store
|
||||||
|
#include <windows.h>
|
||||||
#include <wincrypt.h>
|
#include <wincrypt.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -293,10 +294,16 @@ namespace ix
|
|||||||
*/
|
*/
|
||||||
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
bool SocketOpenSSL::checkHost(const std::string& host, const char* pattern)
|
||||||
{
|
{
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return PathMatchSpecA(host.c_str(), pattern);
|
return PathMatchSpecA(host.c_str(), pattern);
|
||||||
#else
|
#else
|
||||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ namespace
|
|||||||
bHasUserName = true;
|
bHasUserName = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (*LocalString == '/')
|
else if (*LocalString == '/' || *LocalString == '?')
|
||||||
{
|
{
|
||||||
// end of <host>:<port> specification
|
// end of <host>:<port> specification
|
||||||
bHasUserName = false;
|
bHasUserName = false;
|
||||||
@ -242,7 +242,7 @@ namespace
|
|||||||
LocalString++;
|
LocalString++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/'))
|
else if (!bHasBracket && (*LocalString == ':' || *LocalString == '/' || *LocalString == '?'))
|
||||||
{
|
{
|
||||||
// port number is specified
|
// port number is specified
|
||||||
break;
|
break;
|
||||||
@ -280,12 +280,14 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
// skip '/'
|
// skip '/'
|
||||||
if (*CurrentString != '/')
|
if (*CurrentString != '/' && *CurrentString != '?')
|
||||||
{
|
{
|
||||||
return clParseURL(LUrlParserError_NoSlash);
|
return clParseURL(LUrlParserError_NoSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentString++;
|
if (*CurrentString != '?') {
|
||||||
|
CurrentString++;
|
||||||
|
}
|
||||||
|
|
||||||
// parse the path
|
// parse the path
|
||||||
LocalString = CurrentString;
|
LocalString = CurrentString;
|
||||||
|
@ -57,7 +57,7 @@ namespace ix
|
|||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
[remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
|
||||||
std::shared_ptr<ConnectionState> connectionState) {
|
std::shared_ptr<ConnectionState> connectionState) {
|
||||||
auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
|
auto state = std::static_pointer_cast<ProxyConnectionState>(connectionState);
|
||||||
auto remoteIp = connectionState->getRemoteIp();
|
auto remoteIp = connectionState->getRemoteIp();
|
||||||
|
|
||||||
// Server connection
|
// Server connection
|
||||||
|
@ -19,17 +19,20 @@ namespace ix
|
|||||||
{
|
{
|
||||||
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
||||||
const bool WebSocketServer::kDefaultEnablePong(true);
|
const bool WebSocketServer::kDefaultEnablePong(true);
|
||||||
|
const int WebSocketServer::kPingIntervalSeconds(-1); // disable heartbeat
|
||||||
|
|
||||||
WebSocketServer::WebSocketServer(int port,
|
WebSocketServer::WebSocketServer(int port,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
int backlog,
|
int backlog,
|
||||||
size_t maxConnections,
|
size_t maxConnections,
|
||||||
int handshakeTimeoutSecs,
|
int handshakeTimeoutSecs,
|
||||||
int addressFamily)
|
int addressFamily,
|
||||||
|
int pingIntervalSeconds)
|
||||||
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
: SocketServer(port, host, backlog, maxConnections, addressFamily)
|
||||||
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
, _handshakeTimeoutSecs(handshakeTimeoutSecs)
|
||||||
, _enablePong(kDefaultEnablePong)
|
, _enablePong(kDefaultEnablePong)
|
||||||
, _enablePerMessageDeflate(true)
|
, _enablePerMessageDeflate(true)
|
||||||
|
, _pingIntervalSeconds(pingIntervalSeconds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +96,7 @@ namespace ix
|
|||||||
auto webSocket = std::make_shared<WebSocket>();
|
auto webSocket = std::make_shared<WebSocket>();
|
||||||
|
|
||||||
webSocket->setAutoThreadName(false);
|
webSocket->setAutoThreadName(false);
|
||||||
|
webSocket->setPingInterval(_pingIntervalSeconds);
|
||||||
|
|
||||||
if (_onConnectionCallback)
|
if (_onConnectionCallback)
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,8 @@ namespace ix
|
|||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
||||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
||||||
int addressFamily = SocketServer::kDefaultAddressFamily);
|
int addressFamily = SocketServer::kDefaultAddressFamily,
|
||||||
|
int pingIntervalSeconds = WebSocketServer::kPingIntervalSeconds);
|
||||||
virtual ~WebSocketServer();
|
virtual ~WebSocketServer();
|
||||||
virtual void stop() final;
|
virtual void stop() final;
|
||||||
|
|
||||||
@ -61,6 +62,7 @@ namespace ix
|
|||||||
int _handshakeTimeoutSecs;
|
int _handshakeTimeoutSecs;
|
||||||
bool _enablePong;
|
bool _enablePong;
|
||||||
bool _enablePerMessageDeflate;
|
bool _enablePerMessageDeflate;
|
||||||
|
int _pingIntervalSeconds;
|
||||||
|
|
||||||
OnConnectionCallback _onConnectionCallback;
|
OnConnectionCallback _onConnectionCallback;
|
||||||
OnClientMessageCallback _onClientMessageCallback;
|
OnClientMessageCallback _onClientMessageCallback;
|
||||||
@ -69,6 +71,7 @@ namespace ix
|
|||||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
std::set<std::shared_ptr<WebSocket>> _clients;
|
||||||
|
|
||||||
const static bool kDefaultEnablePong;
|
const static bool kDefaultEnablePong;
|
||||||
|
const static int kPingIntervalSeconds;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
virtual void handleConnection(std::unique_ptr<Socket> socket,
|
||||||
|
@ -84,6 +84,40 @@ namespace ix
|
|||||||
REQUIRE(port == 443); // default port for wss
|
REQUIRE(port == 443); // default port for wss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("wss://google.com/?arg=value")
|
||||||
|
{
|
||||||
|
std::string url = "wss://google.com/?arg=value&arg2=value2";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "wss");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/?arg=value&arg2=value2");
|
||||||
|
REQUIRE(query == "arg=value&arg2=value2");
|
||||||
|
REQUIRE(port == 443); // default port for wss
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("wss://google.com?arg=value")
|
||||||
|
{
|
||||||
|
std::string url = "wss://google.com?arg=value&arg2=value2";
|
||||||
|
std::string protocol, host, path, query;
|
||||||
|
int port;
|
||||||
|
bool res;
|
||||||
|
|
||||||
|
res = UrlParser::parse(url, protocol, host, path, query, port);
|
||||||
|
|
||||||
|
REQUIRE(res);
|
||||||
|
REQUIRE(protocol == "wss");
|
||||||
|
REQUIRE(host == "google.com");
|
||||||
|
REQUIRE(path == "/?arg=value&arg2=value2");
|
||||||
|
REQUIRE(query == "arg=value&arg2=value2");
|
||||||
|
REQUIRE(port == 443); // default port for wss
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("real test")
|
SECTION("real test")
|
||||||
{
|
{
|
||||||
std::string url =
|
std::string url =
|
||||||
|
Loading…
Reference in New Issue
Block a user