(websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file
This commit is contained in:
		@@ -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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [10.1.9] - 2020-08-13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(websocket proxy server) add ability to map different hosts to different websocket servers, using a json config file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [10.1.8] - 2020-08-12
 | 
					## [10.1.8] - 2020-08-12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
(ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs
 | 
					(ws) on macOS, with OpenSSL or MbedTLS, use /etc/ssl/cert.pem as the system certs
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ namespace ix
 | 
				
			|||||||
                                    const std::string& hostname,
 | 
					                                    const std::string& hostname,
 | 
				
			||||||
                                    const ix::SocketTLSOptions& tlsOptions,
 | 
					                                    const ix::SocketTLSOptions& tlsOptions,
 | 
				
			||||||
                                    const std::string& remoteUrl,
 | 
					                                    const std::string& remoteUrl,
 | 
				
			||||||
 | 
					                                    const RemoteUrlsMapping& remoteUrlsMapping,
 | 
				
			||||||
                                    bool /*verbose*/)
 | 
					                                    bool /*verbose*/)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ix::WebSocketServer server(port, hostname);
 | 
					        ix::WebSocketServer server(port, hostname);
 | 
				
			||||||
@@ -53,61 +54,75 @@ namespace ix
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
        server.setConnectionStateFactory(factory);
 | 
					        server.setConnectionStateFactory(factory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        server.setOnConnectionCallback([remoteUrl](std::weak_ptr<ix::WebSocket> webSocket,
 | 
					        server.setOnConnectionCallback(
 | 
				
			||||||
                                                   std::shared_ptr<ConnectionState> connectionState,
 | 
					            [remoteUrl, remoteUrlsMapping](std::weak_ptr<ix::WebSocket> webSocket,
 | 
				
			||||||
                                                   std::unique_ptr<ConnectionInfo> connectionInfo) {
 | 
					                                           std::shared_ptr<ConnectionState> connectionState,
 | 
				
			||||||
            auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
 | 
					                                           std::unique_ptr<ConnectionInfo> connectionInfo) {
 | 
				
			||||||
            auto remoteIp = connectionInfo->remoteIp;
 | 
					                auto state = std::dynamic_pointer_cast<ProxyConnectionState>(connectionState);
 | 
				
			||||||
 | 
					                auto remoteIp = connectionInfo->remoteIp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Server connection
 | 
					                // Server connection
 | 
				
			||||||
            state->webSocket().setOnMessageCallback(
 | 
					                state->webSocket().setOnMessageCallback(
 | 
				
			||||||
                [webSocket, state, remoteIp](const WebSocketMessagePtr& msg) {
 | 
					                    [webSocket, state, remoteIp](const WebSocketMessagePtr& msg) {
 | 
				
			||||||
                    if (msg->type == ix::WebSocketMessageType::Close)
 | 
					                        if (msg->type == ix::WebSocketMessageType::Close)
 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        state->setTerminated();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else if (msg->type == ix::WebSocketMessageType::Message)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        auto ws = webSocket.lock();
 | 
					 | 
				
			||||||
                        if (ws)
 | 
					 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            ws->send(msg->str, msg->binary);
 | 
					                            state->setTerminated();
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                        else if (msg->type == ix::WebSocketMessageType::Message)
 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Client connection
 | 
					 | 
				
			||||||
            auto ws = webSocket.lock();
 | 
					 | 
				
			||||||
            if (ws)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ws->setOnMessageCallback([state, remoteUrl](const WebSocketMessagePtr& msg) {
 | 
					 | 
				
			||||||
                    if (msg->type == ix::WebSocketMessageType::Open)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        // Connect to the 'real' server
 | 
					 | 
				
			||||||
                        std::string url(remoteUrl);
 | 
					 | 
				
			||||||
                        url += msg->openInfo.uri;
 | 
					 | 
				
			||||||
                        state->webSocket().setUrl(url);
 | 
					 | 
				
			||||||
                        state->webSocket().disableAutomaticReconnection();
 | 
					 | 
				
			||||||
                        state->webSocket().start();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        // we should sleep here for a bit until we've established the
 | 
					 | 
				
			||||||
                        // connection with the remote server
 | 
					 | 
				
			||||||
                        while (state->webSocket().getReadyState() != ReadyState::Open)
 | 
					 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            std::this_thread::sleep_for(std::chrono::milliseconds(10));
 | 
					                            auto ws = webSocket.lock();
 | 
				
			||||||
 | 
					                            if (ws)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                ws->send(msg->str, msg->binary);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    });
 | 
				
			||||||
                    else if (msg->type == ix::WebSocketMessageType::Close)
 | 
					
 | 
				
			||||||
                    {
 | 
					                // Client connection
 | 
				
			||||||
                        state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
 | 
					                auto ws = webSocket.lock();
 | 
				
			||||||
                    }
 | 
					                if (ws)
 | 
				
			||||||
                    else if (msg->type == ix::WebSocketMessageType::Message)
 | 
					                {
 | 
				
			||||||
                    {
 | 
					                    ws->setOnMessageCallback([state, remoteUrl, remoteUrlsMapping](
 | 
				
			||||||
                        state->webSocket().send(msg->str, msg->binary);
 | 
					                                                 const WebSocketMessagePtr& msg) {
 | 
				
			||||||
                    }
 | 
					                        if (msg->type == ix::WebSocketMessageType::Open)
 | 
				
			||||||
                });
 | 
					                        {
 | 
				
			||||||
            }
 | 
					                            // Connect to the 'real' server
 | 
				
			||||||
        });
 | 
					                            std::string url(remoteUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // maybe we want a different url based on the mapping
 | 
				
			||||||
 | 
					                            std::string host = msg->openInfo.headers["Host"];
 | 
				
			||||||
 | 
					                            auto it = remoteUrlsMapping.find(host);
 | 
				
			||||||
 | 
					                            if (it != remoteUrlsMapping.end())
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                url = it->second;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // append the uri to form the full url 
 | 
				
			||||||
 | 
					                            // (say ws://localhost:1234/foo/?bar=baz)
 | 
				
			||||||
 | 
					                            url += msg->openInfo.uri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            state->webSocket().setUrl(url);
 | 
				
			||||||
 | 
					                            state->webSocket().disableAutomaticReconnection();
 | 
				
			||||||
 | 
					                            state->webSocket().start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // we should sleep here for a bit until we've established the
 | 
				
			||||||
 | 
					                            // connection with the remote server
 | 
				
			||||||
 | 
					                            while (state->webSocket().getReadyState() != ReadyState::Open)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                std::this_thread::sleep_for(std::chrono::milliseconds(10));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else if (msg->type == ix::WebSocketMessageType::Close)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            state->webSocket().close(msg->closeInfo.code, msg->closeInfo.reason);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else if (msg->type == ix::WebSocketMessageType::Message)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            state->webSocket().send(msg->str, msg->binary);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        auto res = server.listen();
 | 
					        auto res = server.listen();
 | 
				
			||||||
        if (!res.first)
 | 
					        if (!res.first)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,14 +7,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXSocketTLSOptions.h"
 | 
					#include "IXSocketTLSOptions.h"
 | 
				
			||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
#include <stddef.h>
 | 
					#include <stddef.h>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    using RemoteUrlsMapping = std::map<std::string, std::string>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int websocket_proxy_server_main(int port,
 | 
					    int websocket_proxy_server_main(int port,
 | 
				
			||||||
                                    const std::string& hostname,
 | 
					                                    const std::string& hostname,
 | 
				
			||||||
                                    const ix::SocketTLSOptions& tlsOptions,
 | 
					                                    const ix::SocketTLSOptions& tlsOptions,
 | 
				
			||||||
                                    const std::string& remoteUrl,
 | 
					                                    const std::string& remoteUrl,
 | 
				
			||||||
 | 
					                                    const RemoteUrlsMapping& remoteUrlsMapping,
 | 
				
			||||||
                                    bool verbose);
 | 
					                                    bool verbose);
 | 
				
			||||||
} // namespace ix
 | 
					} // namespace ix
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,4 +6,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define IX_WEBSOCKET_VERSION "10.1.8"
 | 
					#define IX_WEBSOCKET_VERSION "10.1.9"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								makefile
									
									
									
									
									
								
							@@ -48,6 +48,9 @@ ws_mbedtls:
 | 
				
			|||||||
ws_no_ssl:
 | 
					ws_no_ssl:
 | 
				
			||||||
	mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
 | 
						mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_WS=1 .. ; make -j 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ws_no_python:
 | 
				
			||||||
 | 
						mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -DUSE_TLS=1 -DUSE_WS=1 .. ; ninja install)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
uninstall:
 | 
					uninstall:
 | 
				
			||||||
	xargs rm -fv < build/install_manifest.txt
 | 
						xargs rm -fv < build/install_manifest.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								ws/proxyConfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ws/proxyConfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"remote_urls": {
 | 
				
			||||||
 | 
							"echo.localhost:8008": "ws://localhost:9009",
 | 
				
			||||||
 | 
							"cobra.localhost:8008": "ws://localhost:8765"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								ws/ws.cpp
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								ws/ws.cpp
									
									
									
									
									
								
							@@ -24,6 +24,7 @@
 | 
				
			|||||||
#include <ixwebsocket/IXSocket.h>
 | 
					#include <ixwebsocket/IXSocket.h>
 | 
				
			||||||
#include <ixwebsocket/IXUserAgent.h>
 | 
					#include <ixwebsocket/IXUserAgent.h>
 | 
				
			||||||
#include <ixwebsocket/IXWebSocketProxyServer.h>
 | 
					#include <ixwebsocket/IXWebSocketProxyServer.h>
 | 
				
			||||||
 | 
					#include <nlohmann/json.hpp>
 | 
				
			||||||
#include <spdlog/sinks/basic_file_sink.h>
 | 
					#include <spdlog/sinks/basic_file_sink.h>
 | 
				
			||||||
#include <spdlog/spdlog.h>
 | 
					#include <spdlog/spdlog.h>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
@@ -36,6 +37,32 @@
 | 
				
			|||||||
#define getpid _getpid
 | 
					#define getpid _getpid
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::vector<uint8_t> memblock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::ifstream file(path);
 | 
				
			||||||
 | 
					        if (!file.is_open()) return std::make_pair(false, memblock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        file.seekg(0, file.end);
 | 
				
			||||||
 | 
					        std::streamoff size = file.tellg();
 | 
				
			||||||
 | 
					        file.seekg(0, file.beg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        memblock.resize((size_t) size);
 | 
				
			||||||
 | 
					        file.read((char*) &memblock.front(), static_cast<std::streamsize>(size));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return std::make_pair(true, memblock);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::pair<bool, std::string> readAsString(const std::string& path)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        auto res = load(path);
 | 
				
			||||||
 | 
					        auto vec = res.second;
 | 
				
			||||||
 | 
					        return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int main(int argc, char** argv)
 | 
					int main(int argc, char** argv)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -117,6 +144,7 @@ int main(int argc, char** argv)
 | 
				
			|||||||
    std::string redisHosts("127.0.0.1");
 | 
					    std::string redisHosts("127.0.0.1");
 | 
				
			||||||
    std::string redisPassword;
 | 
					    std::string redisPassword;
 | 
				
			||||||
    std::string appsConfigPath("appsConfig.json");
 | 
					    std::string appsConfigPath("appsConfig.json");
 | 
				
			||||||
 | 
					    std::string configPath;
 | 
				
			||||||
    std::string subprotocol;
 | 
					    std::string subprotocol;
 | 
				
			||||||
    std::string remoteHost;
 | 
					    std::string remoteHost;
 | 
				
			||||||
    std::string minidump;
 | 
					    std::string minidump;
 | 
				
			||||||
@@ -452,6 +480,8 @@ int main(int argc, char** argv)
 | 
				
			|||||||
    proxyServerApp->add_option("--host", hostname, "Hostname");
 | 
					    proxyServerApp->add_option("--host", hostname, "Hostname");
 | 
				
			||||||
    proxyServerApp->add_option("--remote_host", remoteHost, "Remote Hostname");
 | 
					    proxyServerApp->add_option("--remote_host", remoteHost, "Remote Hostname");
 | 
				
			||||||
    proxyServerApp->add_flag("-v", verbose, "Verbose");
 | 
					    proxyServerApp->add_flag("-v", verbose, "Verbose");
 | 
				
			||||||
 | 
					    proxyServerApp->add_option("--config_path", configPath, "Path to config data")
 | 
				
			||||||
 | 
					        ->check(CLI::ExistingPath);
 | 
				
			||||||
    addTLSOptions(proxyServerApp);
 | 
					    addTLSOptions(proxyServerApp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CLI::App* minidumpApp = app.add_subcommand("upload_minidump", "Upload a minidump to sentry");
 | 
					    CLI::App* minidumpApp = app.add_subcommand("upload_minidump", "Upload a minidump to sentry");
 | 
				
			||||||
@@ -724,7 +754,30 @@ int main(int argc, char** argv)
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    else if (app.got_subcommand("proxy_server"))
 | 
					    else if (app.got_subcommand("proxy_server"))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ret = ix::websocket_proxy_server_main(port, hostname, tlsOptions, remoteHost, verbose);
 | 
					        ix::RemoteUrlsMapping remoteUrlsMapping;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!configPath.empty())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto res = readAsString(configPath);
 | 
				
			||||||
 | 
					            bool found = res.first;
 | 
				
			||||||
 | 
					            if (!found)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                spdlog::error("Cannot read config file {} from disk", configPath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                auto jsonData = nlohmann::json::parse(res.second);
 | 
				
			||||||
 | 
					                auto remoteUrls = jsonData["remote_urls"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (auto& el : remoteUrls.items())
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    remoteUrlsMapping[el.key()] = el.value();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ret = ix::websocket_proxy_server_main(
 | 
				
			||||||
 | 
					            port, hostname, tlsOptions, remoteHost, remoteUrlsMapping, verbose);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else if (app.got_subcommand("upload_minidump"))
 | 
					    else if (app.got_subcommand("upload_minidump"))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user