Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

Sample Client/Server + Optional Sec-WebSocket-Protocol + No More Visual Studio Warnings #88

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ cd ..

#### Run server and client examples


### WS


```sh
./build/ws_examples
```

See also: [Simple-WebSocket-Sample](sample/README.md)

### WSS

Before running the WSS-examples, an RSA private key (server.key) and an SSL certificate (server.crt) must be created. Follow, for instance, the instructions given here (for a self-signed certificate): http://www.akadia.com/services/ssh_test_certificate.html
Expand All @@ -54,3 +58,5 @@ Then:
```
./build/wss_examples
```


191 changes: 100 additions & 91 deletions client_ws.hpp

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions client_wss.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,47 +43,47 @@ namespace SimpleWeb {

void connect() override {
std::unique_lock<std::mutex> connection_lock(connection_mutex);
auto connection = this->connection = std::shared_ptr<Connection>(new Connection(handler_runner, config.timeout_idle, *io_service, context));
auto new_connection = this->connection = std::shared_ptr<Connection>(new Connection(handler_runner, config.timeout_idle, *io_service, context));
connection_lock.unlock();
asio::ip::tcp::resolver::query query(host, std::to_string(port));
auto resolver = std::make_shared<asio::ip::tcp::resolver>(*io_service);
connection->set_timeout(config.timeout_request);
resolver->async_resolve(query, [this, connection, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {
connection->cancel_timeout();
auto lock = connection->handler_runner->continue_lock();
new_connection->set_timeout(config.timeout_request);
resolver->async_resolve(query, [this, new_connection, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) {
new_connection->cancel_timeout();
auto lock = new_connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
connection->set_timeout(this->config.timeout_request);
asio::async_connect(connection->socket->lowest_layer(), it, [this, connection, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {
connection->cancel_timeout();
auto lock = connection->handler_runner->continue_lock();
new_connection->set_timeout(this->config.timeout_request);
asio::async_connect(new_connection->socket->lowest_layer(), it, [this, new_connection, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) {
new_connection->cancel_timeout();
auto lock = new_connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
asio::ip::tcp::no_delay option(true);
connection->socket->lowest_layer().set_option(option);
new_connection->socket->lowest_layer().set_option(option);

SSL_set_tlsext_host_name(connection->socket->native_handle(), this->host.c_str());
SSL_set_tlsext_host_name(new_connection->socket->native_handle(), this->host.c_str());

connection->set_timeout(this->config.timeout_request);
connection->socket->async_handshake(asio::ssl::stream_base::client, [this, connection](const error_code &ec) {
connection->cancel_timeout();
auto lock = connection->handler_runner->continue_lock();
new_connection->set_timeout(this->config.timeout_request);
new_connection->socket->async_handshake(asio::ssl::stream_base::client, [this, new_connection](const error_code &ec) {
new_connection->cancel_timeout();
auto lock = new_connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
handshake(connection);
handshake(new_connection);
else
this->connection_error(connection, ec);
this->connection_error(new_connection, ec);
});
}
else
this->connection_error(connection, ec);
this->connection_error(new_connection, ec);
});
}
else
this->connection_error(connection, ec);
this->connection_error(new_connection, ec);
});
}
};
Expand Down
4 changes: 2 additions & 2 deletions crypto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ namespace SimpleWeb {
static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) noexcept {
std::string key;
key.resize(static_cast<size_t>(key_size));
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(),
reinterpret_cast<const unsigned char *>(salt.c_str()), salt.size(), iterations,
PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), static_cast<int>(password.size()),
reinterpret_cast<const unsigned char *>(salt.c_str()), static_cast<int>(salt.size()), iterations,
key_size, reinterpret_cast<unsigned char *>(&key[0]));
return key;
}
Expand Down
1 change: 1 addition & 0 deletions sample/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
92 changes: 92 additions & 0 deletions sample/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
cmake_minimum_required (VERSION 2.8)
project (Simple-WebSocket-Sample)
get_filename_component(SAMPLE_ROOT ${CMAKE_CURRENT_LIST_FILE} DIRECTORY)

if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX")
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wsign-conversion")
endif()



set(BOOST_COMPONENTS system coroutine context thread)
# Late 2017 TODO: remove the following checks and always use std::regex
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_BOOST_REGEX")
endif()
endif()

if (WIN32)

# prereq: populate $env:BoostRoot and $env:BoostVer to tell cmake where boost is
set(BOOST_ROOT "$ENV{BoostRoot}")
set(BOOST_LIBRARYDIR "$ENV{BoostRoot}/lib")
set(BOOST_INCLUDEDIR "$ENV{BoostRoot}/include/boost-$ENV{BoostVer}")
add_definitions(-DBOOST_ALL_DYN_LINK)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} regex)

file(GLOB boostDlls "$ENV{BoostRoot}/lib/*.dll")
foreach(boostDll ${boostDlls})
file(TO_CMAKE_PATH "${boostDll}" correctedBoostDll)
get_filename_component(boostFileName ${correctedBoostDll} NAME)
if(boostFileName MATCHES ".*boost_(chrono|system|regex|system|thread|coroutine|context).*\\.dll$")
configure_file( "${correctedBoostDll}" "${CMAKE_BINARY_DIR}" COPYONLY)
endif()
endforeach()

# prereq: populate $env:OpenSSLRoot to tell cmake where OpenSSL is
set(OPENSSL_ROOT_DIR "$ENV{OpenSSLRoot}")
set(OPENSSL_INCLUDE_DIR "$ENV{OpenSSLRoot}/include")

endif()

find_package(Boost 1.54.0 COMPONENTS ${BOOST_COMPONENTS} REQUIRED)
include_directories(SYSTEM ${Boost_INCLUDE_DIR})
include_directories(SYSTEM ${Boost_INCLUDE_DIR})

if(APPLE)
set(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
endif()

#TODO: add requirement for version 1.0.1g (can it be done in one line?)
find_package(OpenSSL REQUIRED)
include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR})

find_package(Threads REQUIRED)

include_directories(.)

include_directories("${SAMPLE_ROOT}/..")


add_executable(sample_client sample_client.cpp)
set_target_properties(sample_client PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(sample_client ${Boost_LIBRARIES})
target_link_libraries(sample_client ${OPENSSL_CRYPTO_LIBRARY})
target_link_libraries(sample_client ${CMAKE_THREAD_LIBS_INIT})

add_executable(sample_server sample_server.cpp)
set_target_properties(sample_server PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(sample_server ${Boost_LIBRARIES})
target_link_libraries(sample_server ${OPENSSL_LIBRARIES})
target_link_libraries(sample_server ${CMAKE_THREAD_LIBS_INIT})

add_executable(ws_examples ../ws_examples.cpp)
target_link_libraries(ws_examples ${Boost_LIBRARIES})
target_link_libraries(ws_examples ${OPENSSL_CRYPTO_LIBRARY})
target_link_libraries(ws_examples ${CMAKE_THREAD_LIBS_INIT})

add_executable(wss_examples ../wss_examples.cpp)
target_link_libraries(wss_examples ${Boost_LIBRARIES})
target_link_libraries(wss_examples ${OPENSSL_LIBRARIES})
target_link_libraries(wss_examples ${CMAKE_THREAD_LIBS_INIT})

if(MSYS)
target_link_libraries(sample_client ws2_32 wsock32)
target_link_libraries(sample_server ws2_32 wsock32)
target_link_libraries(ws_examples ws2_32 wsock32)
target_link_libraries(wss_examples ws2_32 wsock32)
endif()
63 changes: 63 additions & 0 deletions sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
Simple-WebSocket-Sample
=======================

This project contains two executables which allow the user to control the connections and message flow for Simple-WebSocket-Server. It might be useful for testing interoperability with other websockets implementations.

### sample_server controls

s : Start server
t : sTop server
m : send Message to all clients
q : Quit

### sample_client controls

s : Set up connection
l : cLose connection
c : stop Client
m : send Message
q : Quit

## Usage

Run one server and as many clients as you like. Type the letter for the desired action and hit enter. A typical session might look like this:

| sample_client | sample_server | Effect |
| :-----------: | :------------:|:-------|
| | **S**tart | The server starts listening for connections |
| **S**tart | | The client connects to the server |
| **M**essage | | The client sends a message to the server (the server will respond with an echo) |
| | **M**essage | The server sends a message to all connected clients (they will not respond) |
| c**L**ose | | The client disconnects with a message |
| | s**T**op | The server stops listening |
| s**T**op | | The client cleans itself up |
| **Q**uit | | The client quits |
| | **Q**uit | The server quits |
## Building

The sample uses [Simple-WebSocket-Server](../README.md) (duh). You'll need its dependencies installed.


#### Windows

Populate the following environmentla variables:

| variable | value |
|:--|:--|
| BoostRoot | C:\path\to\Boost |
| BoostVer | 1_62 |
| OpenSSLRoot | C:\path\to\OpenSSL |

Specify the correct generator in your call to cmake, this example uses 2017 with a 64 bit build:

mkdir build
cd build
cmake .. -G "Visual Studio 15 2017 Win64"

Open in your IDE of choice `build/Simple_WebSocket_Sample.sln` and build it.

#### Linux

mkdir build
cd build
cmake ..
106 changes: 106 additions & 0 deletions sample/sample_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include <string>
#include <iostream>
#include <iomanip>
#include "boost/thread.hpp"
#include "client_ws.hpp"
typedef SimpleWeb::SocketClient<SimpleWeb::WS> WsClient;

using namespace std;

int main(int, char**)
{
shared_ptr<WsClient> client;
shared_ptr<WsClient::Connection> _connection;
boost::thread client_thread;

cout << "s : Set up connection" << endl
<< "l : cLose connection" << endl
<< "c : stop Client" << endl
<< "m : send Message" << endl
<< "q : Quit" << endl;

string line;
while (line != "q")
{
getline(cin, line);

if (line == "s")
{
client = std::make_shared<WsClient>("localhost:8081/some/http/resource");


client->SetProtocol("some-protocol"); // optional

client->on_open = [&](shared_ptr<WsClient::Connection> connection)
{
_connection = connection;
cout << "Client Started & Connection " << (size_t)connection.get() << " Opened" << endl << endl;
};

client->on_close = [&](shared_ptr<WsClient::Connection> connection, int code, const string& reason)
{
_connection = nullptr;
cout << "Closed Connection " << (size_t)connection.get() << "(" << code << ")" << endl << " Reason: " << reason << endl << endl;
};

client->on_error = [](shared_ptr<WsClient::Connection> connection, const boost::system::error_code& code)
{
cout << "Error in Connection " << (size_t)connection.get() << "(" << code << ")" << endl << " Code: " << code.message() << endl << endl;
};

client->on_message = [](shared_ptr<WsClient::Connection> connection, shared_ptr<WsClient::Message> message)
{
cout << "Server Message on Connection " << (size_t)connection.get() << endl << " Message: " << message->string() << endl << endl;
};

client_thread = boost::thread([&client]()
{
client->start();
});
cout << "Connection started" << endl << endl;
}
else if (line == "c")
{
if (client != nullptr)
{
client->stop();
client = nullptr;
cout << "Stopped Client" << endl << endl;
}
else
{
cout << "Client Already Stopped" << endl << endl;
}

}
else if (line == "l")
{
if (_connection != nullptr)
{
_connection->send_close(10, "Word to your moms, I came to drop bombs, I got more rhymes than the bible's got psalms.", [](const boost::system::error_code code)
{
cout << "Error on send_close Code: " << code
<< " Message: " << code.message() << endl;
});
cout << "Closed connection " << (size_t)_connection.get() << " with message" << endl << endl;
}
else
{
cout << "Connection already closed" << endl << endl;
}
}

else if (line == "m")
{
auto msg = std::make_shared<WsClient::SendStream>();
*msg << "It's tricky to rock a rhyme to rock a rhyme that's right on time it's tricky!";
_connection->send(msg);
cout << "Message sent" << endl << endl;
}
}
if (client != nullptr)
{
client->stop();
client_thread.join();
}
}
Loading