diff --git a/CMakeLists.txt b/CMakeLists.txt index 7055dee9..3274de27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,15 @@ set (CMAKE_CXX_STANDARD 11) set (CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32") + set( IXWEBSOCKET_SOURCES ixwebsocket/IXEventFd.cpp ixwebsocket/IXSocket.cpp ixwebsocket/IXSocketConnect.cpp ixwebsocket/IXDNSLookup.cpp ixwebsocket/IXWebSocket.cpp + ixwebsocket/IXWebSocketServer.cpp ixwebsocket/IXWebSocketTransport.cpp ixwebsocket/IXWebSocketPerMessageDeflate.cpp ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp @@ -29,6 +32,7 @@ set( IXWEBSOCKET_HEADERS ixwebsocket/IXDNSLookup.h ixwebsocket/IXCancellationRequest.h ixwebsocket/IXWebSocket.h + ixwebsocket/IXWebSocketServer.h ixwebsocket/IXWebSocketTransport.h ixwebsocket/IXWebSocketSendInfo.h ixwebsocket/IXWebSocketErrorInfo.h diff --git a/examples/echo_server/CMakeLists.txt b/examples/echo_server/CMakeLists.txt new file mode 100644 index 00000000..069afdb1 --- /dev/null +++ b/examples/echo_server/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Author: Benjamin Sergeant +# Copyright (c) 2018 Machine Zone, Inc. All rights reserved. +# + +cmake_minimum_required (VERSION 3.4.1) +project (echo_server) + +# There's -Weverything too for clang +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32") + +set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl + +set (CMAKE_CXX_STANDARD 11) + +option(USE_TLS "Add TLS support" ON) + +add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket) + +include_directories(echo_server .) + +add_executable(echo_server + echo_server.cpp) + +if (APPLE AND USE_TLS) + target_link_libraries(echo_server "-framework foundation" "-framework security") +endif() + +target_link_libraries(echo_server ixwebsocket) +install(TARGETS echo_server DESTINATION bin) diff --git a/examples/echo_server/echo_server.cpp b/examples/echo_server/echo_server.cpp new file mode 100644 index 00000000..c0801c69 --- /dev/null +++ b/examples/echo_server/echo_server.cpp @@ -0,0 +1,29 @@ +/* + * echo_server.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + int port = 8080; + if (argc == 2) + { + std::stringstream ss; + ss << argv[1]; + ss >> port; + } + + ix::WebSocketServer server(port); + auto res = server.run(); + if (!res.first) + { + std::cerr << res.second << std::endl; + } + + return 0; +} diff --git a/ixwebsocket/IXSocketConnect.h b/ixwebsocket/IXSocketConnect.h index 86b93602..4c7c7d4e 100644 --- a/ixwebsocket/IXSocketConnect.h +++ b/ixwebsocket/IXSocketConnect.h @@ -21,13 +21,13 @@ namespace ix std::string& errMsg, const CancellationRequest& isCancellationRequested); + static void configure(int sockfd); + private: static bool connectToAddress(const struct addrinfo *address, int& sockfd, std::string& errMsg, const CancellationRequest& isCancellationRequested); - - static void configure(int sockfd); }; } diff --git a/ixwebsocket/IXWebSocketServer.cpp b/ixwebsocket/IXWebSocketServer.cpp new file mode 100644 index 00000000..e0f921cb --- /dev/null +++ b/ixwebsocket/IXWebSocketServer.cpp @@ -0,0 +1,133 @@ +/* + * IXWebSocketServer.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#include "IXWebSocketServer.h" +#include "IXWebSocketTransport.h" +#include "IXWebSocket.h" +#include "IXSocketConnect.h" // for configure, cleanup, move it back to Socket + +#include +#include +#include +#include + +namespace ix +{ + WebSocketServer::WebSocketServer(int port) : + _port(port) + { + + } + + WebSocketServer::~WebSocketServer() + { + + } + + std::pair WebSocketServer::run() + { + // https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.hala001/server.htm + struct sockaddr_in client; /* client address information */ + struct sockaddr_in server; /* server address information */ + int s; /* socket for accepting connections */ + int ns; /* socket connected to client */ + + /* + * Get a socket for accepting connections. + */ + if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + std::string errMsg = "Socket()"; + return std::make_pair(false, errMsg); + } + + /* + * Bind the socket to the server address. + */ + server.sin_family = AF_INET; + server.sin_port = htons(_port); + server.sin_addr.s_addr = INADDR_ANY; + // server.sin_addr.s_addr = INADDR_LOOPBACK; + + if (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0) + { + std::string errMsg = "Bind()"; + return std::make_pair(false, errMsg); + } + + /* + * Listen for connections. Specify the backlog as 1. + */ + if (listen(s, 1) != 0) + { + std::string errMsg = "Listen()"; + return std::make_pair(false, errMsg); + } + + /* + * Accept a connection. + */ + socklen_t address_len = sizeof(socklen_t); + if ((ns = accept(s, (struct sockaddr *)&client, &address_len)) == -1) + { + std::string errMsg = "Accept()"; + return std::make_pair(false, errMsg); + } + + // We only handle one connection so far, and we just 'print received message from it' + ix::WebSocketTransport webSocketTransport; + SocketConnect::configure(ns); // We could/should do this inside initFromSocket + webSocketTransport.initFromSocket(ns); + + for (;;) + { + webSocketTransport.poll(); + + // 1. Dispatch the incoming messages + webSocketTransport.dispatch( + [this](const std::string& msg, + size_t wireSize, + bool decompressionError, + WebSocketTransport::MessageKind messageKind) + { + WebSocketMessageType webSocketMessageType; + switch (messageKind) + { + case WebSocketTransport::MSG: + { + webSocketMessageType = WebSocket_MessageType_Message; + } break; + + case WebSocketTransport::PING: + { + webSocketMessageType = WebSocket_MessageType_Ping; + } break; + + case WebSocketTransport::PONG: + { + webSocketMessageType = WebSocket_MessageType_Pong; + } break; + } + + WebSocketErrorInfo webSocketErrorInfo; + webSocketErrorInfo.decompressionError = decompressionError; + + // _onMessageCallback(webSocketMessageType, msg, wireSize, + // webSocketErrorInfo, WebSocketCloseInfo(), + // WebSocketHttpHeaders()); + + // WebSocket::invokeTrafficTrackerCallback(msg.size(), true); + + std::cout << "received: " << msg << std::endl; + }); + + std::chrono::duration wait(10); + std::this_thread::sleep_for(wait); + } + + return std::make_pair(true, ""); + } +} diff --git a/ixwebsocket/IXWebSocketServer.h b/ixwebsocket/IXWebSocketServer.h new file mode 100644 index 00000000..c623bd8c --- /dev/null +++ b/ixwebsocket/IXWebSocketServer.h @@ -0,0 +1,24 @@ +/* + * IXWebSocketServer.h + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ + +#pragma once + +#include // pair +#include + +namespace ix +{ + class WebSocketServer { + public: + WebSocketServer(int port = 8080); + virtual ~WebSocketServer(); + + std::pair run(); + + private: + int _port; + }; +} diff --git a/ixwebsocket/IXWebSocketTransport.cpp b/ixwebsocket/IXWebSocketTransport.cpp index cc45c132..895a04dd 100644 --- a/ixwebsocket/IXWebSocketTransport.cpp +++ b/ixwebsocket/IXWebSocketTransport.cpp @@ -10,6 +10,7 @@ #include "IXWebSocketTransport.h" #include "IXWebSocketHttpHeaders.h" +#include "IXSocketConnect.h" // for configure, cleanup, move it back to Socket #include "IXSocket.h" #ifdef IXWEBSOCKET_USE_TLS @@ -221,6 +222,7 @@ namespace ix return s; } + // Client WebSocketInitResult WebSocketTransport::init() { std::string protocol, host, path, query; @@ -380,12 +382,70 @@ namespace ix return WebSocketInitResult(true, status, "", headers); } + // Server WebSocketInitResult WebSocketTransport::initFromSocket(int fd) { + _requestInitCancellation = false; + _socket.reset(); _socket = std::make_shared(fd); - WebSocketHttpHeaders headers; + // Read first line + char line[256]; + int i; + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) + { + if (!readByte(line+i)) + { + return WebSocketInitResult(false, 0, std::string("Failed reading HTTP status line from ") + _url); + } + } + line[i] = 0; + if (i == 255) + { + return WebSocketInitResult(false, 0, std::string("Got bad status line connecting to ") + _url); + } + + std::cout << "initFromSocket::start" << std::endl; + std::cout << line << std::endl; + + auto result = parseHttpHeaders(); + auto headersValid = result.first; + auto headers = result.second; + + if (!headersValid) + { + return WebSocketInitResult(false, 401, "Error parsing HTTP headers"); + } + + if (headers.find("sec-websocket-key") == headers.end()) + { + std::string errorMsg("Missing Sec-WebSocket-Key value"); + return WebSocketInitResult(false, 401, errorMsg); + } + + std::cout << "FIXME perMessageDeflateOptions" << std::endl; + + char output[29] = {}; + WebSocketHandshake::generate(headers["sec-websocket-key"].c_str(), output); + + std::stringstream ss; + ss << "HTTP/1.1 101\r\n"; + ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n"; + + ss << "\r\n"; + + std::string dest("remove host"); // FIXME + + std::cout << ss.str() << std::endl; + + if (!writeBytes(ss.str())) + { + return WebSocketInitResult(false, 0, std::string("Failed sending response to ") + dest); + } + + std::cout << "initFromSocket::end" << std::endl; + return WebSocketInitResult(true, 200, "", headers); } diff --git a/ixwebsocket/apple/IXSetThreadName_apple.cpp b/ixwebsocket/apple/IXSetThreadName_apple.cpp new file mode 100644 index 00000000..f4ea58a9 --- /dev/null +++ b/ixwebsocket/apple/IXSetThreadName_apple.cpp @@ -0,0 +1,20 @@ +/* + * IXSetThreadName_apple.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ +#include "../IXSetThreadName.h" +#include + +namespace ix +{ + void setThreadName(const std::string& name) + { + // + // Apple reserves 16 bytes for its thread names + // Notice that the Apple version of pthread_setname_np + // does not take a pthread_t argument + // + pthread_setname_np(name.substr(0, 63).c_str()); + } +} diff --git a/ixwebsocket/linux/IXSetThreadName_linux.cpp b/ixwebsocket/linux/IXSetThreadName_linux.cpp new file mode 100644 index 00000000..e46236e4 --- /dev/null +++ b/ixwebsocket/linux/IXSetThreadName_linux.cpp @@ -0,0 +1,21 @@ +/* + * IXSetThreadName_linux.cpp + * Author: Benjamin Sergeant + * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. + */ +#include "../IXSetThreadName.h" +#include + +namespace ix +{ + void setThreadName(const std::string& name) + { + // + // Linux only reserves 16 bytes for its thread names + // See prctl and PR_SET_NAME property in + // http://man7.org/linux/man-pages/man2/prctl.2.html + // + pthread_setname_np(pthread_self(), + name.substr(0, 15).c_str()); + } +}