IXWebSocket/test/IXWebSocketChatTest.cpp

313 lines
9.2 KiB
C++
Raw Normal View History

2018-12-30 03:33:15 +01:00
/*
* cmd_websocket_chat.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2017 Machine Zone. All rights reserved.
*/
//
// Simple chat program that talks to the node.js server at
// websocket_chat_server/broacast-server.js
//
2019-09-23 19:25:23 +02:00
#include "IXTest.h"
#include "catch.hpp"
#include "msgpack11.hpp"
2018-12-30 03:33:15 +01:00
#include <iostream>
#include <ixwebsocket/IXWebSocket.h>
2019-01-02 01:34:05 +01:00
#include <ixwebsocket/IXWebSocketServer.h>
2019-09-23 19:25:23 +02:00
#include <mutex>
#include <sstream>
#include <vector>
2018-12-30 03:33:15 +01:00
using msgpack11::MsgPack;
using namespace ix;
namespace
{
class WebSocketChat
{
2019-09-23 19:25:23 +02:00
public:
WebSocketChat(const std::string& user, const std::string& session, int port);
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
void subscribe(const std::string& channel);
void start();
void stop();
bool isReady() const;
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
void sendMessage(const std::string& text);
size_t getReceivedMessagesCount() const;
const std::vector<std::string>& getReceivedMessages() const;
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
std::string encodeMessage(const std::string& text);
std::pair<std::string, std::string> decodeMessage(const std::string& str);
void appendMessage(const std::string& message);
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
private:
std::string _user;
std::string _session;
int _port;
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
ix::WebSocket _webSocket;
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
std::vector<std::string> _receivedMessages;
mutable std::mutex _mutex;
2018-12-30 03:33:15 +01:00
};
2019-09-23 19:25:23 +02:00
WebSocketChat::WebSocketChat(const std::string& user, const std::string& session, int port)
: _user(user)
, _session(session)
, _port(port)
2018-12-30 03:33:15 +01:00
{
;
}
size_t WebSocketChat::getReceivedMessagesCount() const
{
2019-02-16 19:31:55 +01:00
std::lock_guard<std::mutex> lock(_mutex);
return _receivedMessages.size();
}
const std::vector<std::string>& WebSocketChat::getReceivedMessages() const
{
std::lock_guard<std::mutex> lock(_mutex);
return _receivedMessages;
}
void WebSocketChat::appendMessage(const std::string& message)
2019-02-16 19:31:55 +01:00
{
std::lock_guard<std::mutex> lock(_mutex);
_receivedMessages.push_back(message);
2018-12-30 03:33:15 +01:00
}
bool WebSocketChat::isReady() const
{
return _webSocket.getReadyState() == ix::ReadyState::Open;
2018-12-30 03:33:15 +01:00
}
void WebSocketChat::stop()
{
_webSocket.stop();
}
void WebSocketChat::start()
{
std::string url;
{
std::stringstream ss;
2019-09-23 19:25:23 +02:00
ss << "ws://127.0.0.1:" << _port << "/" << _user;
url = ss.str();
}
2018-12-30 03:33:15 +01:00
_webSocket.setUrl(url);
std::stringstream ss;
log(std::string("Connecting to url: ") + url);
2019-09-23 19:25:23 +02:00
_webSocket.setOnMessageCallback([this](const ix::WebSocketMessagePtr& msg) {
std::stringstream ss;
if (msg->type == ix::WebSocketMessageType::Open)
2018-12-30 03:33:15 +01:00
{
2019-09-23 19:25:23 +02:00
ss << "cmd_websocket_chat: user " << _user << " Connected !";
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
ss << "cmd_websocket_chat: user " << _user << " disconnected !";
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
auto result = decodeMessage(msg->str);
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
// Our "chat" / "broacast" node.js server does not send us
// the messages we send, so we don't need to have a msg_user != user
// as we do for the satori chat example.
2019-02-16 19:31:55 +01:00
2019-09-23 19:25:23 +02:00
// store text
appendMessage(result.second);
2018-12-30 03:33:15 +01:00
2019-09-23 19:25:23 +02:00
std::string payload = result.second;
if (payload.size() > 2000)
{
2019-09-23 19:25:23 +02:00
payload = "<message too large>";
}
2019-09-23 19:25:23 +02:00
ss << std::endl << result.first << " > " << payload << std::endl << _user << " > ";
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Error)
{
ss << "cmd_websocket_chat: Error ! " << msg->errorInfo.reason;
log(ss.str());
}
else if (msg->type == ix::WebSocketMessageType::Ping)
{
log("cmd_websocket_chat: received ping message");
}
else if (msg->type == ix::WebSocketMessageType::Pong)
{
log("cmd_websocket_chat: received pong message");
}
else if (msg->type == ix::WebSocketMessageType::Fragment)
{
log("cmd_websocket_chat: received message fragment");
}
else
{
ss << "Unexpected ix::WebSocketMessageType";
log(ss.str());
}
});
2018-12-30 03:33:15 +01:00
_webSocket.start();
}
std::pair<std::string, std::string> WebSocketChat::decodeMessage(const std::string& str)
{
std::string errMsg;
MsgPack msg = MsgPack::parse(str, errMsg);
std::string msg_user = msg["user"].string_value();
std::string msg_text = msg["text"].string_value();
return std::pair<std::string, std::string>(msg_user, msg_text);
}
std::string WebSocketChat::encodeMessage(const std::string& text)
{
std::map<MsgPack, MsgPack> obj;
obj["user"] = _user;
obj["text"] = text;
MsgPack msg(obj);
std::string output = msg.dump();
return output;
}
void WebSocketChat::sendMessage(const std::string& text)
{
_webSocket.sendBinary(encodeMessage(text));
2018-12-30 03:33:15 +01:00
}
2019-01-02 01:34:05 +01:00
bool startServer(ix::WebSocketServer& server)
{
server.setOnClientMessageCallback(
[&server](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const ix::WebSocketMessagePtr& msg) {
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Open)
{
TLogger() << "New connection";
TLogger() << "remote ip: " << remoteIp;
TLogger() << "id: " << connectionState->getId();
TLogger() << "Uri: " << msg->openInfo.uri;
TLogger() << "Headers:";
for (auto it : msg->openInfo.headers)
2019-09-23 19:25:23 +02:00
{
TLogger() << it.first << ": " << it.second;
2019-09-23 19:25:23 +02:00
}
}
else if (msg->type == ix::WebSocketMessageType::Close)
{
log("Closed connection");
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
for (auto&& client : server.getClients())
2019-09-23 19:25:23 +02:00
{
if (client.get() != &webSocket)
2019-01-02 01:34:05 +01:00
{
client->sendBinary(msg->str);
2019-01-02 01:34:05 +01:00
}
}
}
});
2019-01-02 01:34:05 +01:00
auto res = server.listen();
if (!res.first)
{
2019-01-08 03:04:28 +01:00
log(res.second);
2019-01-02 01:34:05 +01:00
return false;
}
server.start();
return true;
}
2019-09-23 19:25:23 +02:00
} // namespace
2018-12-30 03:33:15 +01:00
TEST_CASE("Websocket_chat", "[websocket_chat]")
2018-12-30 03:33:15 +01:00
{
SECTION("Exchange and count sent/received messages.")
{
ix::setupWebSocketTrafficTrackerCallback();
int port = 8090;
2019-01-02 01:34:05 +01:00
ix::WebSocketServer server(port);
REQUIRE(startServer(server));
2019-01-02 01:34:05 +01:00
2018-12-30 03:33:15 +01:00
std::string session = ix::generateSessionId();
WebSocketChat chatA("jean", session, port);
WebSocketChat chatB("paul", session, port);
2018-12-30 03:33:15 +01:00
chatA.start();
chatB.start();
// Wait for all chat instance to be ready
while (true)
{
if (chatA.isReady() && chatB.isReady()) break;
ix::msleep(10);
}
REQUIRE(server.getClients().size() == 2);
2018-12-30 03:33:15 +01:00
// Add a bit of extra time, for the subscription to be active
ix::msleep(200);
chatA.sendMessage("from A1");
chatA.sendMessage("from A2");
chatA.sendMessage("from A3");
chatB.sendMessage("from B1");
chatB.sendMessage("from B2");
// Test large messages that need to be broken into small fragments
size_t size = 1 * 1024 * 1024; // ~1Mb
2019-02-16 19:31:55 +01:00
std::string bigMessage(size, 'a');
chatB.sendMessage(bigMessage);
log("Sent all messages");
2019-02-16 19:31:55 +01:00
// Wait until all messages are received. 10s timeout
int attempts = 0;
2019-09-23 19:25:23 +02:00
while (chatA.getReceivedMessagesCount() != 3 || chatB.getReceivedMessagesCount() != 3)
2019-02-16 19:31:55 +01:00
{
CHECK(attempts++ < 10);
2019-02-16 19:31:55 +01:00
ix::msleep(1000);
}
2018-12-30 03:33:15 +01:00
chatA.stop();
chatB.stop();
CHECK(chatA.getReceivedMessagesCount() == 3);
CHECK(chatB.getReceivedMessagesCount() == 3);
2018-12-30 03:33:15 +01:00
CHECK(chatB.getReceivedMessages()[0] == "from A1");
CHECK(chatB.getReceivedMessages()[1] == "from A2");
CHECK(chatB.getReceivedMessages()[2] == "from A3");
2019-02-16 19:31:55 +01:00
CHECK(chatA.getReceivedMessages()[0] == "from B1");
CHECK(chatA.getReceivedMessages()[1] == "from B2");
CHECK(chatA.getReceivedMessages()[2].size() == bigMessage.size());
2019-02-16 19:31:55 +01:00
// Give us 1000ms for the server to notice that clients went away
ix::msleep(1000);
CHECK(server.getClients().size() == 0);
2018-12-30 03:33:15 +01:00
ix::reportWebSocketTraffic();
}
}