Compare commits

..

12 Commits

20 changed files with 119 additions and 129 deletions

View File

@ -2,7 +2,7 @@ name: linux
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
linux: linux:

View File

@ -2,7 +2,7 @@ name: linux_asan
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
linux: linux:

View File

@ -2,7 +2,7 @@ name: mac_tsan_mbedtls
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
mac_tsan_mbedtls: mac_tsan_mbedtls:

View File

@ -2,7 +2,7 @@ name: mac_tsan_openssl
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
mac_tsan_openssl: mac_tsan_openssl:

View File

@ -2,7 +2,7 @@ name: mac_tsan_sectransport
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
mac_tsan_sectransport: mac_tsan_sectransport:

View File

@ -2,7 +2,7 @@ name: uwp
on: on:
push: push:
paths-ignore: paths-ignore:
- './**' - 'docs/**'
jobs: jobs:
uwp: uwp:

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ ws/.certs/
ws/.srl ws/.srl
ixhttpd ixhttpd
makefile makefile
a.out

View File

@ -191,7 +191,7 @@ if (USE_TLS)
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES}) target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
elseif (USE_SECURE_TRANSPORT) elseif (USE_SECURE_TRANSPORT)
message(STATUS "TLS configured to use secure transport") message(STATUS "TLS configured to use secure transport")
target_link_libraries(ixwebsocket "-framework foundation" "-framework security") target_link_libraries(ixwebsocket "-framework Foundation" "-framework Security")
endif() endif()
endif() endif()

View File

@ -15,8 +15,8 @@ A bad security bug affecting users compiling with SSL enabled and OpenSSL as the
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more. * Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
* *
* On macOS * On macOS
* $ mkdir -p build ; cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install * $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
* $ clang++ --std=c++14 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation * $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
* $ ./a.out * $ ./a.out
*/ */
@ -44,10 +44,12 @@ int main()
if (msg->type == ix::WebSocketMessageType::Message) if (msg->type == ix::WebSocketMessageType::Message)
{ {
std::cout << "received message: " << msg->str << std::endl; std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
} }
else if (msg->type == ix::WebSocketMessageType::Open) else if (msg->type == ix::WebSocketMessageType::Open)
{ {
std::cout << "Connection established" << std::endl; std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
} }
} }
); );
@ -58,13 +60,16 @@ int main()
// Send a message to the server (default to TEXT mode) // Send a message to the server (default to TEXT mode)
webSocket.send("hello world"); webSocket.send("hello world");
while (true) // Display a prompt
{ std::cout << "> " << std::flush;
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text); webSocket.send(text);
std::cout << "> " << std::flush;
} }
return 0; return 0;
@ -77,6 +82,8 @@ IXWebSocket is actively being developed, check out the [changelog](https://machi
IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code. IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current [test results](https://bsergean.github.io/autobahn/reports/clients/index.html). Some tests are still failing in the server code.
Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.
## Users ## Users
If your company or project is using this library, feel free to open an issue or PR to amend this list. If your company or project is using this library, feel free to open an issue or PR to amend this list.
@ -113,6 +120,10 @@ To check the performance of a websocket library, you can look at the [autoroute]
| UWP | Disabled | None | [![Build2][6]][0] | | UWP | Disabled | None | [![Build2][6]][0] |
| Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] | | Linux | OpenSSL | Address Sanitizer | [![Build2][7]][0] |
* 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.
[0]: https://github.com/machinezone/IXWebSocket [0]: https://github.com/machinezone/IXWebSocket
[1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg [1]: https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg
[2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg [2]: https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg

View File

@ -2,6 +2,10 @@
All changes to this project will be documented in this file. All changes to this project will be documented in this file.
## [11.0.9] - 2021-03-07
(ixwebsocket) Expose setHandshakeTimeout method
## [11.0.8] - 2020-12-25 ## [11.0.8] - 2020-12-25
(ws) trim ws dependencies no more ixcrypto and ixcore deps (ws) trim ws dependencies no more ixcrypto and ixcore deps

View File

@ -263,6 +263,15 @@ webSocket.setMaxWaitBetweenReconnectionRetries(5 * 1000); // 5000ms = 5s
uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries(); uint32_t m = webSocket.getMaxWaitBetweenReconnectionRetries();
``` ```
## Handshake timeout
You can control how long to wait until timing out while waiting for the websocket handshake to be performed.
```
int handshakeTimeoutSecs = 1;
setHandshakeTimeout(handshakeTimeoutSecs);
```
## WebSocket server API ## WebSocket server API
### Legacy api ### Legacy api

View File

@ -56,6 +56,11 @@ namespace ix
_url = url; _url = url;
} }
void WebSocket::setHandshakeTimeout(int handshakeTimeoutSecs)
{
_handshakeTimeoutSecs = handshakeTimeoutSecs;
}
void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers) void WebSocket::setExtraHeaders(const WebSocketHttpHeaders& headers)
{ {
std::lock_guard<std::mutex> lock(_configMutex); std::lock_guard<std::mutex> lock(_configMutex);

View File

@ -58,6 +58,7 @@ namespace ix
void enablePerMessageDeflate(); void enablePerMessageDeflate();
void disablePerMessageDeflate(); void disablePerMessageDeflate();
void addSubProtocol(const std::string& subProtocol); void addSubProtocol(const std::string& subProtocol);
void setHandshakeTimeout(int handshakeTimeoutSecs);
// Run asynchronously, by calling start and stop. // Run asynchronously, by calling start and stop.
void start(); void start();

View File

@ -97,9 +97,10 @@ namespace ix
} }
else if (_onClientMessageCallback) else if (_onClientMessageCallback)
{ {
WebSocket* webSocketRawPtr = webSocket.get();
webSocket->setOnMessageCallback( webSocket->setOnMessageCallback(
[this, &ws = *webSocket.get(), connectionState](const WebSocketMessagePtr& msg) { [this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
_onClientMessageCallback(connectionState, ws, msg); _onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
}); });
} }
else else
@ -168,4 +169,45 @@ namespace ix
std::lock_guard<std::mutex> lock(_clientsMutex); std::lock_guard<std::mutex> lock(_clientsMutex);
return _clients.size(); return _clients.size();
} }
//
// Classic servers
//
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)
{
for (auto&& client : getClients())
{
if (client.get() != &webSocket)
{
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on
do
{
size_t bufferedAmount = client->bufferedAmount();
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
});
}
int WebSocketServer::listenAndStart()
{
auto res = listen();
if (!res.first)
{
return 1;
}
start();
}
} // namespace ix } // namespace ix

View File

@ -47,6 +47,9 @@ namespace ix
// Get all the connected clients // Get all the connected clients
std::set<std::shared_ptr<WebSocket>> getClients(); std::set<std::shared_ptr<WebSocket>> getClients();
void makeBroadcastServer();
int listenAndStart();
const static int kDefaultHandShakeTimeoutSecs; const static int kDefaultHandShakeTimeoutSecs;
private: private:

View File

@ -6,4 +6,4 @@
#pragma once #pragma once
#define IX_WEBSOCKET_VERSION "11.0.8" #define IX_WEBSOCKET_VERSION "11.0.9"

View File

@ -35,10 +35,12 @@ int main()
if (msg->type == ix::WebSocketMessageType::Message) if (msg->type == ix::WebSocketMessageType::Message)
{ {
std::cout << "received message: " << msg->str << std::endl; std::cout << "received message: " << msg->str << std::endl;
std::cout << "> " << std::flush;
} }
else if (msg->type == ix::WebSocketMessageType::Open) else if (msg->type == ix::WebSocketMessageType::Open)
{ {
std::cout << "Connection established" << std::endl; std::cout << "Connection established" << std::endl;
std::cout << "> " << std::flush;
} }
} }
); );
@ -49,13 +51,16 @@ int main()
// Send a message to the server (default to TEXT mode) // Send a message to the server (default to TEXT mode)
webSocket.send("hello world"); webSocket.send("hello world");
while (true) // Display a prompt
{ std::cout << "> " << std::flush;
std::string text;
std::cout << "> " << std::flush;
std::getline(std::cin, text);
std::string text;
// Read text from the console and send messages in text mode.
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
while (std::getline(std::cin, text))
{
webSocket.send(text); webSocket.send(text);
std::cout << "> " << std::flush;
} }
return 0; return 0;

View File

@ -33,11 +33,7 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80); auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
std::string errMsg; std::string errMsg;
struct addrinfo* res = dnsLookup->resolve(errMsg, struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
[]
{
return false;
});
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }
@ -48,11 +44,7 @@ TEST_CASE("dns", "[net]")
std::string errMsg; std::string errMsg;
// The callback returning true means we are requesting cancellation // The callback returning true means we are requesting cancellation
struct addrinfo* res = dnsLookup->resolve(errMsg, struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
[]
{
return true;
});
std::cerr << "Error message: " << errMsg << std::endl; std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr); REQUIRE(res == nullptr);
} }

View File

@ -29,6 +29,7 @@ namespace ix
// Comparison should be case insensitive // Comparison should be case insensitive
REQUIRE(httpHeaders["Foo"] == "foo"); REQUIRE(httpHeaders["Foo"] == "foo");
REQUIRE(httpHeaders["Foo"] != "bar");
} }
SECTION("2") SECTION("2")
@ -39,7 +40,7 @@ namespace ix
headers["Upgrade"] = "webSocket"; headers["Upgrade"] = "webSocket";
REQUIRE(CaseInsensitiveLess::cmp(headers["upgrade"], "WebSocket") == 0); REQUIRE(!CaseInsensitiveLess::cmp(headers["upGRADE"], "webSocket"));
} }
} }

108
ws/ws.cpp
View File

@ -439,93 +439,6 @@ namespace ix
return generateReport(url) ? 0 : 1; return generateReport(url) ? 0 : 1;
} }
//
// broadcast server
//
int ws_broadcast_server_main(int port,
const std::string& hostname,
const ix::SocketTLSOptions& tlsOptions)
{
spdlog::info("Listening on {}:{}", hostname, port);
ix::WebSocketServer server(port, hostname);
server.setTLSOptions(tlsOptions);
server.setOnClientMessageCallback(
[&server](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const WebSocketMessagePtr& msg) {
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Open)
{
spdlog::info("New connection");
spdlog::info("remote ip: {}", remoteIp);
spdlog::info("id: {}", connectionState->getId());
spdlog::info("Uri: {}", msg->openInfo.uri);
spdlog::info("Headers:");
for (auto it : msg->openInfo.headers)
{
spdlog::info("{}: {}", it.first, it.second);
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
spdlog::info("Closed connection: code {} reason {}",
msg->closeInfo.code,
msg->closeInfo.reason);
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Connection error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->errorInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->errorInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->errorInfo.http_status << std::endl;
spdlog::info(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Fragment)
{
spdlog::info("Received message fragment");
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
spdlog::info("Received {} bytes", msg->wireSize);
for (auto&& client : server.getClients())
{
if (client.get() != &webSocket)
{
client->send(msg->str, msg->binary, [](int current, int total) -> bool {
spdlog::info("Step {} out of {}", current, total);
return true;
});
do
{
size_t bufferedAmount = client->bufferedAmount();
spdlog::info("{} bytes left to be sent", bufferedAmount);
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
});
auto res = server.listen();
if (!res.first)
{
spdlog::info(res.second);
return 1;
}
server.start();
server.wait();
return 0;
}
/* /*
* ws_chat.cpp * ws_chat.cpp
* Author: Benjamin Sergeant * Author: Benjamin Sergeant
@ -988,6 +901,9 @@ namespace ix
auto addr = res->ai_addr; auto addr = res->ai_addr;
// FIXME: this display weird addresses / we could steal libuv inet.c
// code which display correct results
char str[INET_ADDRSTRLEN]; char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN); inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN);
@ -2462,9 +2378,9 @@ namespace ix
else else
{ {
std::string readyStateString = std::string readyStateString =
readyState == ReadyState::Connecting readyState == ReadyState::Connecting ? "Connecting"
? "Connecting" : readyState == ReadyState::Closing ? "Closing"
: readyState == ReadyState::Closing ? "Closing" : "Closed"; : "Closed";
size_t bufferedAmount = client->bufferedAmount(); size_t bufferedAmount = client->bufferedAmount();
spdlog::info( spdlog::info(
@ -2853,9 +2769,13 @@ int main(int argc, char** argv)
ret = ix::ws_push_server( ret = ix::ws_push_server(
port, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong, sendMsg); port, hostname, tlsOptions, ipv6, disablePerMessageDeflate, disablePong, sendMsg);
} }
else if (app.got_subcommand("transfer")) else if (app.got_subcommand("transfer") || app.got_subcommand("broadcast_server"))
{ {
ret = ix::ws_transfer_main(port, hostname, tlsOptions); ix::WebSocketServer server(port, hostname);
server.setTLSOptions(tlsOptions);
server.makeBroadcastServer();
server.listenAndStart();
server.wait();
} }
else if (app.got_subcommand("send")) else if (app.got_subcommand("send"))
{ {
@ -2870,10 +2790,6 @@ int main(int argc, char** argv)
{ {
ret = ix::ws_chat_main(url, user); ret = ix::ws_chat_main(url, user);
} }
else if (app.got_subcommand("broadcast_server"))
{
ret = ix::ws_broadcast_server_main(port, hostname, tlsOptions);
}
else if (app.got_subcommand("ping")) else if (app.got_subcommand("ping"))
{ {
ret = ix::ws_ping_pong_main(url, tlsOptions); ret = ix::ws_ping_pong_main(url, tlsOptions);