Compare commits

...

26 Commits

Author SHA1 Message Date
4027f9282b Set an origin header in websocket and http clients 2023-04-01 09:14:59 -07:00
eb9a7bed76 fix warning about member variable initialization order + minor makefile build fix 2023-04-01 09:03:39 -07:00
d20864d7d1 Replace CMAKE_BINARY_DIR with CMAKE_CURRENT_BINARY_DIR (#454) 2023-03-29 20:59:39 -07:00
f184a7adef fix incorrect closures with code 1011 Internal error (#450)
* fix incorrect closures with code 1011 Internal error

* enable IXWebSocketCloseTest
2023-03-29 20:45:29 -07:00
dc7b986e10 Build fix for FreeBSD (#449) 2023-03-13 09:36:25 -07:00
1e3560014f Prevent deadlock when server is stopping (#426) 2023-02-25 14:41:05 -08:00
9157873f5b Fix compilation on GCC-13 (#443)
* Fix compilation on GCC-13

GCC-13 changes internal cstdint includes, and now files that uses
standart integer types should directly include cstdint header.

See: https://gcc.gnu.org/gcc-13/porting_to.html#header-dep-changes
Bug: https://bugs.gentoo.org/865117
Bug: https://bugs.gentoo.org/895440

* Convert line endings to Unix format
2023-02-25 13:50:35 -08:00
aa2ca19895 Added support for setting custom ping messages with support for any 'ping' message type (binary ping message, text ping message) (#438)
* Added support for setting custom ping messages with support for any 'ping' message type (binary ping message, text ping message)

* Add default value

---------

Co-authored-by: YuHuanTin <2@>
2023-02-23 08:29:07 -08:00
6cc21f3658 HTTP should contain port in 'Host' header (#427) 2023-02-11 15:27:40 -08:00
679ce519dd Fix DNSLookup memory leak (#422)
* Fix memory leak with shared_ptr and -fsanitize=address

* Replace addrinfo* by shared_ptr

* fsanitize=address only on Linux

* Add USE_WS Linux CI test

* Remove fsanitize from the cmake files

* Remove USE_WS in linux test suite
2022-12-22 17:13:51 -08:00
a5d4911a16 Fix macos link error (#423)
* Fix macos link error

* Update CMakeLists.txt

* Update CMakeLists.txt
2022-12-22 09:16:10 -08:00
b0fd119d14 Host HTTP and WS on the same port (#414)
* Quick hack to host HTTP and WS on the same port #373

* Quick hack to host HTTP and WS on the same port #373 - simplify code

* ran clang-format

Co-authored-by: En Shih <seanstone5923@gmail.com>
Co-authored-by: The Artful Bodger <TheArtfulBodger@users.noreply.github.com>
2022-11-05 18:53:11 -07:00
472cf68c31 add Content-Type support (#405) 2022-10-12 06:43:05 -07:00
1e46466114 Add option to disable hostname check (#399)
* Suppress compiler warnings about unused elements.

* Enable CMake's compilation database.

* Add TLS option to disable checking a certificate's host name.

* Add `--disable-hostname-validation` to `ws`.

* Add test for disabling hostname validation.
2022-10-12 06:41:32 -07:00
0b8b5608dc Update doc to talk about binding to 0.0.0.0 2022-09-08 14:58:19 -07:00
20a028e2ae Fix spelling mistake (#401) 2022-08-02 13:28:59 -07:00
8d7b557be6 Delete stale.yml
Remove the stale github action.
2022-06-30 10:41:08 +02:00
e417e63605 Update CHANGELOG.md 2022-05-13 10:45:46 -07:00
7b1524d7ec Update IXWebSocketVersion.h 2022-05-13 10:43:32 -07:00
e8048ad826 BoringSSL does not allow setting the hostname with a null-terminated string. The length is always required: https://boringssl.googlesource.com/boringssl/+/master/crypto/x509/x509_vpm.c#93 (#391) 2022-05-05 08:11:18 -07:00
2b40a30c8f Update README.md 2022-05-02 09:34:43 -07:00
d7bfe89e43 Set shorter thread names (#379) 2022-04-30 10:18:20 -07:00
84aa652846 Set shorter thread names (#379) 2022-04-30 10:16:53 -07:00
edb6ded99f Fix Sec-WebSocket-Key to contain valid Base64. (#389)
The generated header only "looked like" Base64, but if the other side
actually tried to decode it as such, it could fail. This change fixes
that to always generate a valid Base64 value.

The Base64 code is copied from
https://gist.github.com/tomykaira/f0fd86b6c73063283afe550bc5d77594.
2022-04-29 00:05:06 -07:00
2f560ff4c0 Update IXWebSocketVersion.h 2022-04-28 23:56:40 -07:00
002d9c8985 Update ixwebsocket-config.cmake.in (#390) 2022-04-28 23:56:00 -07:00
52 changed files with 778 additions and 371 deletions

View File

@ -1,19 +0,0 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

View File

@ -11,6 +11,7 @@ project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
option (BUILD_DEMO OFF)
@ -66,6 +67,7 @@ set( IXWEBSOCKET_SOURCES
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXBase64.h
ixwebsocket/IXBench.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
@ -249,7 +251,7 @@ if (WIN32)
endif()
endif()
if (UNIX)
if (UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads)
target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
@ -285,8 +287,8 @@ if (IXWEBSOCKET_INSTALL)
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/
)
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
install(FILES "${CMAKE_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
configure_file("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ixwebsocket-config.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket")
install(EXPORT ixwebsocket
FILE ixwebsocket-targets.cmake

View File

@ -35,6 +35,7 @@ int main()
// Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
// https://github.com/machinezone/IXWebSocket/issues/386#issuecomment-1105235227 (self signed certificates)
std::string url("wss://echo.websocket.org");
webSocket.setUrl(url);

View File

@ -2,6 +2,12 @@
All changes to this project will be documented in this file.
## [11.4.3] - 2022-05-13
Set shorter thread names
BoringSSL fix with SNI
Websocket computed header is valid Base64
## [11.4.1] - 2022-04-23
vckpg + cmake fix, to handle zlib as a dependency better

View File

@ -54,7 +54,7 @@ To use the installed package within a cmake project, use the following:
# include headers
include_directories(${IXWEBSOCKET_INCLUDE_DIR})
# ...
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUNS
target_link_libraries(${PROJECT_NAME} ... ${IXWEBSOCKET_LIBRARY}) # Cmake will automatically fail the generation if the lib was not found, i.e is set to NOTFOUND
```

View File

@ -301,7 +301,9 @@ This api was actually changed to take a weak_ptr<WebSocket> as the first argumen
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnConnectionCallback(
[&server](std::weak_ptr<WebSocket> webSocket,
@ -384,7 +386,9 @@ The webSocket reference is guaranteed to be always valid ; by design the callbac
// Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters.
ix::WebSocketServer server(port);
int port = 8008;
std::string host("127.0.0.1"); // If you need this server to be accessible on a different machine, use "0.0.0.0"
ix::WebSocketServer server(port, host);
server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection,
@ -624,3 +628,5 @@ For a client, specifying `caFile` can be used if connecting to a server that use
For a server, specifying `caFile` implies that:
1. You require clients to present a certificate
1. It must be signed by one of the trusted roots in the file
By default, a destination's hostname is always validated against the certificate that it presents. To accept certificates with any hostname, set `ix::SocketTLSOptions::disable_hostname_validation` to `true`.

View File

@ -2,8 +2,8 @@
include(CMakeFindDependencyMacro)
if@USE_ZLIB@
if (@USE_ZLIB@)
find_dependency(ZLIB)
endif()
include("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ixwebsocket-targets.cmake")

125
ixwebsocket/IXBase64.h Normal file
View File

@ -0,0 +1,125 @@
#ifndef _MACARON_BASE64_H_
#define _MACARON_BASE64_H_
/**
* The MIT License (MIT)
* Copyright (c) 2016 tomykaira
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <cstdint>
#include <string>
namespace macaron {
class Base64 {
public:
static std::string Encode(const std::string data) {
static constexpr char sEncodingTable[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
size_t in_len = data.size();
size_t out_len = 4 * ((in_len + 2) / 3);
std::string ret(out_len, '\0');
size_t i;
char *p = const_cast<char*>(ret.c_str());
for (i = 0; i < in_len - 2; i += 3) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | ((int) (data[i + 2] & 0xC0) >> 6)];
*p++ = sEncodingTable[data[i + 2] & 0x3F];
}
if (i < in_len) {
*p++ = sEncodingTable[(data[i] >> 2) & 0x3F];
if (i == (in_len - 1)) {
*p++ = sEncodingTable[((data[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = sEncodingTable[((data[i] & 0x3) << 4) | ((int) (data[i + 1] & 0xF0) >> 4)];
*p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
return ret;
}
static std::string Decode(const std::string& input, std::string& out) {
static constexpr unsigned char kDecodingTable[] = {
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
size_t in_len = input.size();
if (in_len % 4 != 0) return "Input data size is not a multiple of 4";
size_t out_len = in_len / 4 * 3;
if (input[in_len - 1] == '=') out_len--;
if (input[in_len - 2] == '=') out_len--;
out.resize(out_len);
for (size_t i = 0, j = 0; i < in_len;) {
uint32_t a = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t b = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t c = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t d = input[i] == '=' ? 0 & i++ : kDecodingTable[static_cast<int>(input[i++])];
uint32_t triple = (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6);
if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF;
if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF;
}
return "";
}
};
}
#endif /* _MACARON_BASE64_H_ */

View File

@ -6,7 +6,7 @@
#pragma once
#include <chrono>
#include <stdint.h>
#include <cstdint>
#include <string>
namespace ix

View File

@ -7,9 +7,9 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <stdint.h>
#include <string>
namespace ix

View File

@ -23,6 +23,7 @@
#include <chrono>
#include <string.h>
#include <thread>
#include <utility>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
@ -44,7 +45,7 @@ namespace ix
;
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
DNSLookup::AddrInfoPtr DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg)
{
@ -63,10 +64,10 @@ namespace ix
errMsg = gai_strerror(getaddrinfo_result);
res = nullptr;
}
return res;
return AddrInfoPtr{ res, freeaddrinfo };
}
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
DNSLookup::AddrInfoPtr DNSLookup::resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable)
{
@ -74,12 +75,7 @@ namespace ix
: resolveUnCancellable(errMsg, isCancellationRequested);
}
void DNSLookup::release(struct addrinfo* addr)
{
freeaddrinfo(addr);
}
struct addrinfo* DNSLookup::resolveUnCancellable(
DNSLookup::AddrInfoPtr DNSLookup::resolveUnCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -94,7 +90,7 @@ namespace ix
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo* DNSLookup::resolveCancellable(
DNSLookup::AddrInfoPtr DNSLookup::resolveCancellable(
std::string& errMsg, const CancellationRequest& isCancellationRequested)
{
errMsg = "no error";
@ -157,7 +153,7 @@ namespace ix
// gone, so we use temporary variables (res) or we pass in by copy everything that
// getAddrInfo needs to work.
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
auto res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock())
{
@ -181,13 +177,13 @@ namespace ix
return _errMsg;
}
void DNSLookup::setRes(struct addrinfo* addr)
void DNSLookup::setRes(DNSLookup::AddrInfoPtr addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
_res = std::move(addr);
}
struct addrinfo* DNSLookup::getRes()
DNSLookup::AddrInfoPtr DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;

View File

@ -12,6 +12,7 @@
#include "IXCancellationRequest.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <mutex>
#include <set>
@ -24,22 +25,21 @@ namespace ix
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
using AddrInfoPtr = std::shared_ptr<addrinfo>;
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup() = default;
struct addrinfo* resolve(std::string& errMsg,
AddrInfoPtr resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
bool cancellable = true);
void release(struct addrinfo* addr);
private:
struct addrinfo* resolveCancellable(std::string& errMsg,
AddrInfoPtr resolveCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
struct addrinfo* resolveUnCancellable(std::string& errMsg,
AddrInfoPtr resolveUnCancellable(std::string& errMsg,
const CancellationRequest& isCancellationRequested);
static struct addrinfo* getAddrInfo(const std::string& hostname,
AddrInfoPtr getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg);
@ -48,15 +48,15 @@ namespace ix
void setErrMsg(const std::string& errMsg);
const std::string& getErrMsg();
void setRes(struct addrinfo* addr);
struct addrinfo* getRes();
void setRes(AddrInfoPtr addr);
AddrInfoPtr getRes();
std::string _hostname;
int _port;
int64_t _wait;
const static int64_t kDefaultWait;
struct addrinfo* _res;
AddrInfoPtr _res;
std::mutex _resMutex;
std::string _errMsg;

View File

@ -9,6 +9,7 @@
#include "IXProgressCallback.h"
#include "IXWebSocketHttpHeaders.h"
#include <atomic>
#include <cstdint>
#include <tuple>
#include <unordered_map>

View File

@ -12,6 +12,7 @@
#include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h"
#include <assert.h>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <random>
@ -139,8 +140,9 @@ namespace ix
std::string protocol, host, path, query;
int port;
bool isProtocolDefaultPort;
if (!UrlParser::parse(url, protocol, host, path, query, port))
if (!UrlParser::parse(url, protocol, host, path, query, port, isProtocolDefaultPort))
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
@ -173,7 +175,12 @@ namespace ix
// Build request string
std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n";
ss << "Host: " << host << "\r\n";
ss << "Host: " << host;
if (!isProtocolDefaultPort)
{
ss << ":" << port;
}
ss << "\r\n";
#ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress && !args->onChunkCallback)
@ -202,6 +209,12 @@ namespace ix
ss << "User-Agent: " << userAgent() << "\r\n";
}
// Set an origin header if missing
if (args->extraHeaders.find("Origin") == args->extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
if (verb == kPost || verb == kPut || verb == kPatch || _forceBody)
{
// Set request compression header

View File

@ -10,6 +10,7 @@
#include "IXNetSystem.h"
#include "IXSocketConnect.h"
#include "IXUserAgent.h"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <sstream>
@ -40,6 +41,29 @@ namespace
auto vec = res.second;
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
}
std::string response_head_file(const std::string& file_name){
if (std::string::npos != file_name.find(".html") || std::string::npos != file_name.find(".htm"))
return "text/html";
else if (std::string::npos != file_name.find(".css"))
return "text/css";
else if (std::string::npos != file_name.find(".js") || std::string::npos != file_name.find(".mjs"))
return "application/x-javascript";
else if (std::string::npos != file_name.find(".ico"))
return "image/x-icon";
else if (std::string::npos != file_name.find(".png"))
return "image/png";
else if (std::string::npos != file_name.find(".jpg") || std::string::npos != file_name.find(".jpeg"))
return "image/jpeg";
else if (std::string::npos != file_name.find(".gif"))
return "image/gif";
else if (std::string::npos != file_name.find(".svg"))
return "image/svg+xml";
else
return "application/octet-stream";
}
} // namespace
namespace ix
@ -51,28 +75,14 @@ namespace ix
int backlog,
size_t maxConnections,
int addressFamily,
int timeoutSecs)
: SocketServer(port, host, backlog, maxConnections, addressFamily)
, _connectedClientsCount(0)
int timeoutSecs,
int handshakeTimeoutSecs)
: WebSocketServer(port, host, backlog, maxConnections, handshakeTimeoutSecs, addressFamily)
, _timeoutSecs(timeoutSecs)
{
setDefaultConnectionCallback();
}
HttpServer::~HttpServer()
{
stop();
}
void HttpServer::stop()
{
stopAcceptingConnections();
// FIXME: cancelling / closing active clients ...
SocketServer::stop();
}
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
{
_onConnectionCallback = callback;
@ -81,34 +91,35 @@ namespace ix
void HttpServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
_connectedClientsCount++;
auto ret = Http::parseRequest(socket, _timeoutSecs);
// FIXME: handle errors in parseRequest
if (std::get<0>(ret))
{
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
if (!Http::sendResponse(response, socket))
auto request = std::get<2>(ret);
std::shared_ptr<ix::HttpResponse> response;
if (request->headers["Upgrade"] == "websocket")
{
logError("Cannot send response");
WebSocketServer::handleUpgrade(std::move(socket), connectionState, request);
}
else
{
auto response = _onConnectionCallback(request, connectionState);
if (!Http::sendResponse(response, socket))
{
logError("Cannot send response");
}
}
}
connectionState->setTerminated();
_connectedClientsCount--;
}
size_t HttpServer::getConnectedClientsCount()
{
return _connectedClientsCount;
}
void HttpServer::setDefaultConnectionCallback()
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
std::string uri(request->uri);
if (uri.empty() || uri == "/")
{
@ -117,6 +128,7 @@ namespace ix
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
headers["Content-Type"] = response_head_file(uri);
std::string path("." + uri);
auto res = readAsString(path);
@ -165,9 +177,9 @@ namespace ix
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
//
setOnConnectionCallback(
[this,
redirectUrl](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
[this, redirectUrl](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();
@ -198,7 +210,8 @@ namespace ix
{
setOnConnectionCallback(
[this](HttpRequestPtr request,
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr {
std::shared_ptr<ConnectionState> connectionState) -> HttpResponsePtr
{
WebSocketHttpHeaders headers;
headers["Server"] = userAgent();

View File

@ -7,8 +7,8 @@
#pragma once
#include "IXHttp.h"
#include "IXSocketServer.h"
#include "IXWebSocket.h"
#include "IXWebSocketServer.h"
#include <functional>
#include <memory>
#include <mutex>
@ -19,7 +19,7 @@
namespace ix
{
class HttpServer final : public SocketServer
class HttpServer final : public WebSocketServer
{
public:
using OnConnectionCallback =
@ -30,9 +30,8 @@ namespace ix
int backlog = SocketServer::kDefaultTcpBacklog,
size_t maxConnections = SocketServer::kDefaultMaxConnections,
int addressFamily = SocketServer::kDefaultAddressFamily,
int timeoutSecs = HttpServer::kDefaultTimeoutSecs);
virtual ~HttpServer();
virtual void stop() final;
int timeoutSecs = HttpServer::kDefaultTimeoutSecs,
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
void setOnConnectionCallback(const OnConnectionCallback& callback);
@ -41,10 +40,10 @@ namespace ix
void makeDebugServer();
int getTimeoutSecs();
private:
// Member variables
OnConnectionCallback _onConnectionCallback;
std::atomic<int> _connectedClientsCount;
const static int kDefaultTimeoutSecs;
int _timeoutSecs;
@ -52,7 +51,6 @@ namespace ix
// Methods
virtual void handleConnection(std::unique_ptr<Socket>,
std::shared_ptr<ConnectionState> connectionState) final;
virtual size_t getConnectedClientsCount() final;
void setDefaultConnectionCallback();
};

View File

@ -6,6 +6,12 @@
#pragma once
#include <cstdint>
#ifdef __FreeBSD__
#include <sys/types.h>
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN

View File

@ -6,8 +6,8 @@
#pragma once
#include <cstdint>
#include <memory>
#include <stdint.h>
#include <string>
namespace ix

View File

@ -5,8 +5,8 @@
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <stdint.h>
#include <string>
#include <deque>
#ifdef _WIN32

View File

@ -7,6 +7,7 @@
#pragma once
#include "IXSelectInterrupt.h"
#include <cstdint>
#include <mutex>
#include <stdint.h>
#include <string>

View File

@ -14,7 +14,6 @@
#include <array>
#include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

View File

@ -7,6 +7,7 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>

View File

@ -205,7 +205,9 @@ namespace ix
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (!_tlsOptions.disable_hostname_validation)
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
if (_tlsOptions.isPeerVerifyDisabled())
{

View File

@ -102,7 +102,7 @@ namespace ix
// First do DNS resolution
//
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested);
auto res = dnsLookup->resolve(errMsg, isCancellationRequested);
if (res == nullptr)
{
return -1;
@ -112,7 +112,7 @@ namespace ix
// iterate through the records to find a working peer
struct addrinfo* address;
for (address = res; address != nullptr; address = address->ai_next)
for (address = res.get(); address != nullptr; address = address->ai_next)
{
//
// Second try to connect to the remote host
@ -124,7 +124,6 @@ namespace ix
}
}
freeaddrinfo(res);
return sockfd;
}

View File

@ -14,6 +14,7 @@
#include "IXNetSystem.h"
#include "IXSocket.h"
#include "IXSocketConnect.h"
#include <cstdint>
#include <string.h>
#ifdef _WIN32
@ -48,7 +49,7 @@ namespace ix
mbedtls_pk_init(&_pkey);
}
bool SocketMbedTLS::loadSystemCertificates(std::string& errorMsg)
bool SocketMbedTLS::loadSystemCertificates(std::string& /* errorMsg */)
{
#ifdef _WIN32
DWORD flags = CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG |
@ -195,10 +196,13 @@ namespace ix
return false;
}
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
if (!_tlsOptions.disable_hostname_validation)
{
errMsg = "SNI setup failed";
return false;
if (!host.empty() && mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
{
errMsg = "SNI setup failed";
return false;
}
}
return true;

View File

@ -301,7 +301,11 @@ namespace ix
}
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
#if OPENSSL_VERSION_NUMBER < 0x10100000L
const std::string& hostname,
#else
const std::string& /* hostname */,
#endif
std::string& errMsg)
{
X509* server_cert = SSL_get_peer_certificate(ssl);
@ -390,6 +394,11 @@ namespace ix
int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1)
{
if (_tlsOptions.disable_hostname_validation)
{
return true;
}
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
}
int reason = SSL_get_error(_ssl_connection, connect_result);
@ -754,8 +763,11 @@ namespace ix
// (The docs say that this should work from 1.0.2, and is the default from
// 1.1.0, but it does not. To be on the safe side, the manual test
// below is enabled for all versions prior to 1.1.0.)
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0);
if (!_tlsOptions.disable_hostname_validation)
{
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
}
#endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
}

View File

@ -219,6 +219,10 @@ namespace ix
if (_gcThread.joinable())
{
_stopGc = true;
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
_gcThread.join();
_stopGc = false;
@ -268,7 +272,10 @@ namespace ix
// Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd);
setThreadName("SocketServer::accept");
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:ac:" + std::to_string(_port));
for (;;)
{
@ -425,7 +432,10 @@ namespace ix
void SocketServer::runGC()
{
setThreadName("SocketServer::GC");
// Use a cryptic name to stay within the 16 bytes limit thread name limitation
// $ echo Srv:gc:64000 | wc -c
// 13
setThreadName("Srv:gc:" + std::to_string(_port));
for (;;)
{
@ -445,7 +455,10 @@ namespace ix
if (!_stopGc)
{
std::unique_lock<std::mutex> lock(_conditionVariableMutexGC);
_conditionVariableGC.wait(lock);
if(!_canContinueGC) {
_conditionVariableGC.wait(lock, [this]{ return _canContinueGC; });
}
_canContinueGC = false;
}
}
}
@ -459,6 +472,10 @@ namespace ix
{
// a connection got terminated, we can run the connection thread GC,
// so wake up the thread responsible for that
{
std::lock_guard<std::mutex> lock{ _conditionVariableMutexGC };
_canContinueGC = true;
}
_conditionVariableGC.notify_one();
}

View File

@ -126,5 +126,6 @@ namespace ix
// as a connection
std::condition_variable _conditionVariableGC;
std::mutex _conditionVariableMutexGC;
bool _canContinueGC{ false };
};
} // namespace ix

View File

@ -33,6 +33,9 @@ namespace ix
// whether tls is enabled, used for server code
bool tls = false;
// whether to skip validating the peer's hostname against the certificate presented
bool disable_hostname_validation = false;
bool hasCertAndKey() const;
bool isUsingSystemDefaults() const;

View File

@ -333,6 +333,19 @@ namespace
return Result;
}
int getProtocolPort(const std::string& protocol)
{
if (protocol == "ws" || protocol == "http")
{
return 80;
}
else if (protocol == "wss" || protocol == "https")
{
return 443;
}
return -1;
}
} // namespace
namespace ix
@ -343,6 +356,18 @@ namespace ix
std::string& path,
std::string& query,
int& port)
{
bool isProtocolDefaultPort;
return parse(url, protocol, host, path, query, port, isProtocolDefaultPort);
}
bool UrlParser::parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort)
{
clParseURL res = clParseURL::ParseURL(url);
@ -356,23 +381,12 @@ namespace ix
path = res.m_Path;
query = res.m_Query;
const auto protocolPort = getProtocolPort(protocol);
if (!res.GetPort(&port))
{
if (protocol == "ws" || protocol == "http")
{
port = 80;
}
else if (protocol == "wss" || protocol == "https")
{
port = 443;
}
else
{
// Invalid protocol. Should be caught by regex check
// but this missing branch trigger cpplint linter.
return false;
}
port = protocolPort;
}
isProtocolDefaultPort = port == protocolPort;
if (path.empty())
{

View File

@ -19,5 +19,13 @@ namespace ix
std::string& path,
std::string& query,
int& port);
static bool parse(const std::string& url,
std::string& protocol,
std::string& host,
std::string& path,
std::string& query,
int& port,
bool& isProtocolDefaultPort);
};
} // namespace ix

View File

@ -16,6 +16,7 @@
#include "IXUuid.h"
#include <cstdint>
#include <iomanip>
#include <random>
#include <sstream>

View File

@ -13,6 +13,7 @@
#include "IXWebSocketHandshake.h"
#include <cassert>
#include <cmath>
#include <cstdint>
namespace
@ -39,9 +40,11 @@ namespace ix
, _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
, _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pingType(SendMessageKind::Ping)
{
_ws.setOnCloseCallback(
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote) {
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
{
_onMessageCallback(
ix::make_unique<WebSocketMessage>(WebSocketMessageType::Close,
emptyMsg,
@ -100,6 +103,17 @@ namespace ix
return _perMessageDeflateOptions;
}
void WebSocket::setPingMessage(const std::string& sendMessage, SendMessageKind pingType)
{
std::lock_guard<std::mutex> lock(_configMutex);
_pingMessage = sendMessage;
_ws.setPingMessage(_pingMessage, pingType);
}
const std::string WebSocket::getPingMessage() const
{
std::lock_guard<std::mutex> lock(_configMutex);
return _pingMessage;
}
void WebSocket::setPingInterval(int pingIntervalSecs)
{
std::lock_guard<std::mutex> lock(_configMutex);
@ -232,7 +246,7 @@ namespace ix
if (_pingIntervalSecs > 0)
{
// Send a heart beat right away
_ws.sendHeartBeat();
_ws.sendHeartBeat(_pingType);
}
return status;
@ -240,7 +254,8 @@ namespace ix
WebSocketInitResult WebSocket::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate)
bool enablePerMessageDeflate,
HttpRequestPtr request)
{
{
std::lock_guard<std::mutex> lock(_configMutex);
@ -249,7 +264,7 @@ namespace ix
}
WebSocketInitResult status =
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate);
_ws.connectToSocket(std::move(socket), timeoutSecs, enablePerMessageDeflate, request);
if (!status.success)
{
return status;
@ -266,7 +281,7 @@ namespace ix
if (_pingIntervalSecs > 0)
{
// Send a heart beat right away
_ws.sendHeartBeat();
_ws.sendHeartBeat(_pingType);
}
return status;
@ -384,8 +399,9 @@ namespace ix
[this](const std::string& msg,
size_t wireSize,
bool decompressionError,
WebSocketTransport::MessageKind messageKind) {
WebSocketMessageType webSocketMessageType{WebSocketMessageType::Error};
WebSocketTransport::MessageKind messageKind)
{
WebSocketMessageType webSocketMessageType {WebSocketMessageType::Error};
switch (messageKind)
{
case WebSocketTransport::MessageKind::MSG_TEXT:
@ -503,13 +519,13 @@ namespace ix
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
}
WebSocketSendInfo WebSocket::ping(const std::string& text)
WebSocketSendInfo WebSocket::ping(const std::string& text, SendMessageKind pingType)
{
// Standard limit ping message size
constexpr size_t pingMaxPayloadSize = 125;
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
return sendMessage(text, SendMessageKind::Ping);
return sendMessage(text, pingType);
}
WebSocketSendInfo WebSocket::sendMessage(const IXWebSocketSendData& message,

View File

@ -16,11 +16,12 @@
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketMessage.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketTransport.h"
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>
@ -53,6 +54,8 @@ namespace ix
void setPerMessageDeflateOptions(
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
void setTLSOptions(const SocketTLSOptions& socketTLSOptions);
void setPingMessage(const std::string& sendMessage,
SendMessageKind pingType = SendMessageKind::Ping);
void setPingInterval(int pingIntervalSecs);
void enablePong();
void disablePong();
@ -88,7 +91,7 @@ namespace ix
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo ping(const std::string& text);
WebSocketSendInfo ping(const std::string& text,SendMessageKind pingType = SendMessageKind::Ping);
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
@ -103,6 +106,7 @@ namespace ix
const std::string getUrl() const;
const WebSocketPerMessageDeflateOptions getPerMessageDeflateOptions() const;
const std::string getPingMessage() const;
int getPingInterval() const;
size_t bufferedAmount() const;
@ -128,7 +132,8 @@ namespace ix
// Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket>,
int timeoutSecs,
bool enablePerMessageDeflate);
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
WebSocketTransport _ws;
@ -169,6 +174,8 @@ namespace ix
// Optional ping and pong timeout
int _pingIntervalSecs;
int _pingTimeoutSecs;
std::string _pingMessage;
SendMessageKind _pingType;
static const int kDefaultPingIntervalSecs;
static const int kDefaultPingTimeoutSecs;

View File

@ -6,6 +6,7 @@
#include "IXWebSocketHandshake.h"
#include "IXBase64.h"
#include "IXHttp.h"
#include "IXSocketConnect.h"
#include "IXStrCaseCompare.h"
@ -17,7 +18,6 @@
#include <random>
#include <sstream>
namespace ix
{
WebSocketHandshake::WebSocketHandshake(
@ -87,6 +87,7 @@ namespace ix
WebSocketInitResult WebSocketHandshake::clientHandshake(
const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host,
const std::string& path,
int port,
@ -106,15 +107,10 @@ namespace ix
return WebSocketInitResult(false, 0, ss.str());
}
//
// Generate a random 24 bytes string which looks like it is base64 encoded
// y3JJHMbDL1EzLkh9GBhXDw==
// 0cb3Vd9HkbpVVumoS3Noka==
// Generate a random 16 bytes string and base64 encode it.
//
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
//
std::string secWebSocketKey = genRandomString(22);
secWebSocketKey += "==";
std::string secWebSocketKey = macaron::Base64::Encode(genRandomString(16));
std::stringstream ss;
ss << "GET " << path << " HTTP/1.1\r\n";
@ -130,6 +126,12 @@ namespace ix
ss << "User-Agent: " << userAgent() << "\r\n";
}
// Set an origin header if missing
if (extraHeaders.find("Origin") == extraHeaders.end())
{
ss << "Origin: " << protocol << "://" << host << ":" << port << "\r\n";
}
for (auto& it : extraHeaders)
{
ss << it.first << ": " << it.second << "\r\n";
@ -245,28 +247,42 @@ namespace ix
}
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate)
bool enablePerMessageDeflate,
HttpRequestPtr request)
{
_requestInitCancellation = false;
auto isCancellationRequested =
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
// Read first line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
std::string method;
std::string uri;
std::string httpVersion;
if (!lineValid)
if (request)
{
return sendErrorResponse(400, "Error reading HTTP request line");
method = request->method;
uri = request->uri;
httpVersion = request->version;
}
else
{
// Read first line
auto lineResult = _socket->readLine(isCancellationRequested);
auto lineValid = lineResult.first;
auto line = lineResult.second;
// Validate request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
auto method = std::get<0>(requestLine);
auto uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine);
if (!lineValid)
{
return sendErrorResponse(400, "Error reading HTTP request line");
}
// Validate request line (GET /foo HTTP/1.1\r\n)
auto requestLine = Http::parseRequestLine(line);
method = std::get<0>(requestLine);
uri = std::get<1>(requestLine);
httpVersion = std::get<2>(requestLine);
}
if (method != "GET")
{
@ -279,14 +295,22 @@ namespace ix
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
}
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
auto headers = result.second;
if (!headersValid)
WebSocketHttpHeaders headers;
if (request)
{
return sendErrorResponse(400, "Error parsing HTTP headers");
headers = request->headers;
}
else
{
// Retrieve and validate HTTP headers
auto result = parseHttpHeaders(_socket, isCancellationRequested);
auto headersValid = result.first;
headers = result.second;
if (!headersValid)
{
return sendErrorResponse(400, "Error parsing HTTP headers");
}
}
if (headers.find("sec-websocket-key") == headers.end())

View File

@ -7,6 +7,7 @@
#pragma once
#include "IXCancellationRequest.h"
#include "IXHttp.h"
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketInitResult.h"
@ -30,12 +31,15 @@ namespace ix
WebSocketInitResult clientHandshake(const std::string& url,
const WebSocketHttpHeaders& extraHeaders,
const std::string& protocol,
const std::string& host,
const std::string& path,
int port,
int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate);
WebSocketInitResult serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
private:
std::string genRandomString(const int len);

View File

@ -46,6 +46,8 @@
*
*/
#include <cstdint>
#include "IXWebSocketPerMessageDeflate.h"
#include "IXUniquePtr.h"

View File

@ -10,6 +10,7 @@
#include "zlib.h"
#endif
#include <array>
#include <cstdint>
#include <string>
#include <vector>
#include "IXWebSocketSendData.h"

View File

@ -6,6 +6,7 @@
#pragma once
#include <cstdint>
#include <string>
namespace ix

View File

@ -1,128 +1,129 @@
/*
* IXWebSocketSendData.h
*
* WebSocket (Binary/Text) send data buffer
*/
#pragma once
#include <string>
#include <vector>
#include <iterator>
namespace ix
{
/*
* IXWebSocketSendData implements a wrapper for std::string, std:vector<char/uint8_t> and char*.
* It removes the necessarity to copy the data or string into a std::string
*/
class IXWebSocketSendData {
public:
template<typename T>
struct IXWebSocketSendData_const_iterator
//: public std::iterator<std::forward_iterator_tag, T>
{
typedef IXWebSocketSendData_const_iterator<T> const_iterator;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = const value_type&;
pointer _ptr;
public:
IXWebSocketSendData_const_iterator() : _ptr(nullptr) {}
IXWebSocketSendData_const_iterator(pointer ptr) : _ptr(ptr) {}
~IXWebSocketSendData_const_iterator() {}
const_iterator operator++(int) { return const_iterator(_ptr++); }
const_iterator& operator++() { ++_ptr; return *this; }
reference operator* () const { return *_ptr; }
pointer operator->() const { return _ptr; }
const_iterator operator+ (const difference_type offset) const { return const_iterator(_ptr + offset); }
const_iterator operator- (const difference_type offset) const { return const_iterator(_ptr - offset); }
difference_type operator- (const const_iterator& rhs) const { return _ptr - rhs._ptr; }
bool operator==(const const_iterator& rhs) const { return _ptr == rhs._ptr; }
bool operator!=(const const_iterator& rhs) const { return _ptr != rhs._ptr; }
const_iterator& operator+=(const difference_type offset) { _ptr += offset; return *this; }
const_iterator& operator-=(const difference_type offset) { _ptr -= offset; return *this; }
};
using const_iterator = IXWebSocketSendData_const_iterator<char>;
/* The assigned std::string must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::string& str)
: _data(str.data())
, _size(str.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<char>& v)
: _data(v.data())
, _size(v.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<uint8_t>& v)
: _data(reinterpret_cast<const char*>(v.data()))
, _size(v.size())
{
}
/* The assigned memory must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const char* data, size_t size)
: _data(data)
, _size(data == nullptr ? 0 : size)
{
}
bool empty() const
{
return _data == nullptr || _size == 0;
}
const char* c_str() const
{
return _data;
}
const char* data() const
{
return _data;
}
size_t size() const
{
return _size;
}
inline const_iterator begin() const
{
return const_iterator(const_cast<char*>(_data));
}
inline const_iterator end() const
{
return const_iterator(const_cast<char*>(_data) + _size);
}
inline const_iterator cbegin() const
{
return begin();
}
inline const_iterator cend() const
{
return end();
}
private:
const char* _data;
const size_t _size;
};
/*
* IXWebSocketSendData.h
*
* WebSocket (Binary/Text) send data buffer
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <iterator>
namespace ix
{
/*
* IXWebSocketSendData implements a wrapper for std::string, std:vector<char/uint8_t> and char*.
* It removes the necessarity to copy the data or string into a std::string
*/
class IXWebSocketSendData {
public:
template<typename T>
struct IXWebSocketSendData_const_iterator
//: public std::iterator<std::forward_iterator_tag, T>
{
typedef IXWebSocketSendData_const_iterator<T> const_iterator;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = value_type*;
using reference = const value_type&;
pointer _ptr;
public:
IXWebSocketSendData_const_iterator() : _ptr(nullptr) {}
IXWebSocketSendData_const_iterator(pointer ptr) : _ptr(ptr) {}
~IXWebSocketSendData_const_iterator() {}
const_iterator operator++(int) { return const_iterator(_ptr++); }
const_iterator& operator++() { ++_ptr; return *this; }
reference operator* () const { return *_ptr; }
pointer operator->() const { return _ptr; }
const_iterator operator+ (const difference_type offset) const { return const_iterator(_ptr + offset); }
const_iterator operator- (const difference_type offset) const { return const_iterator(_ptr - offset); }
difference_type operator- (const const_iterator& rhs) const { return _ptr - rhs._ptr; }
bool operator==(const const_iterator& rhs) const { return _ptr == rhs._ptr; }
bool operator!=(const const_iterator& rhs) const { return _ptr != rhs._ptr; }
const_iterator& operator+=(const difference_type offset) { _ptr += offset; return *this; }
const_iterator& operator-=(const difference_type offset) { _ptr -= offset; return *this; }
};
using const_iterator = IXWebSocketSendData_const_iterator<char>;
/* The assigned std::string must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::string& str)
: _data(str.data())
, _size(str.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<char>& v)
: _data(v.data())
, _size(v.size())
{
}
/* The assigned std::vector must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const std::vector<uint8_t>& v)
: _data(reinterpret_cast<const char*>(v.data()))
, _size(v.size())
{
}
/* The assigned memory must be kept alive for the lifetime of the input buffer */
IXWebSocketSendData(const char* data, size_t size)
: _data(data)
, _size(data == nullptr ? 0 : size)
{
}
bool empty() const
{
return _data == nullptr || _size == 0;
}
const char* c_str() const
{
return _data;
}
const char* data() const
{
return _data;
}
size_t size() const
{
return _size;
}
inline const_iterator begin() const
{
return const_iterator(const_cast<char*>(_data));
}
inline const_iterator end() const
{
return const_iterator(const_cast<char*>(_data) + _size);
}
inline const_iterator cbegin() const
{
return begin();
}
inline const_iterator cend() const
{
return end();
}
private:
const char* _data;
const size_t _size;
};
}

View File

@ -79,7 +79,16 @@ namespace ix
void WebSocketServer::handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState)
{
setThreadName("WebSocketServer::" + connectionState->getId());
handleUpgrade(std::move(socket), connectionState);
connectionState->setTerminated();
}
void WebSocketServer::handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request)
{
setThreadName("Srv:ws:" + connectionState->getId());
auto webSocket = std::make_shared<WebSocket>();
if (_onConnectionCallback)
@ -89,7 +98,7 @@ namespace ix
if (!webSocket->isOnMessageCallbackRegistered())
{
logError("WebSocketServer Application developer error: Server callback improperly "
"registerered.");
"registered.");
logError("Missing call to setOnMessageCallback inside setOnConnectionCallback.");
connectionState->setTerminated();
return;
@ -99,9 +108,8 @@ namespace ix
{
WebSocket* webSocketRawPtr = webSocket.get();
webSocket->setOnMessageCallback(
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg) {
_onClientMessageCallback(connectionState, *webSocketRawPtr, msg);
});
[this, webSocketRawPtr, connectionState](const WebSocketMessagePtr& msg)
{ _onClientMessageCallback(connectionState, *webSocketRawPtr, msg); });
}
else
{
@ -130,7 +138,7 @@ namespace ix
}
auto status = webSocket->connectToSocket(
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate);
std::move(socket), _handshakeTimeoutSecs, _enablePerMessageDeflate, request);
if (status.success)
{
// Process incoming messages and execute callbacks
@ -155,8 +163,6 @@ namespace ix
logError("Cannot delete client");
}
}
connectionState->setTerminated();
}
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
@ -176,28 +182,30 @@ namespace ix
//
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)
setOnClientMessageCallback(
[this](std::shared_ptr<ConnectionState> connectionState,
WebSocket& webSocket,
const WebSocketMessagePtr& msg)
{
for (auto&& client : getClients())
auto remoteIp = connectionState->getRemoteIp();
if (msg->type == ix::WebSocketMessageType::Message)
{
if (client.get() != &webSocket)
for (auto&& client : getClients())
{
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on
do
if (client.get() != &webSocket)
{
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
client->send(msg->str, msg->binary);
// Make sure the OS send buffer is flushed before moving on
do
{
std::chrono::duration<double, std::milli> duration(500);
std::this_thread::sleep_for(duration);
} while (client->bufferedAmount() != 0);
}
}
}
}
});
});
}
bool WebSocketServer::listenAndStart()

View File

@ -55,6 +55,7 @@ namespace ix
int getHandshakeTimeoutSecs();
bool isPongEnabled();
bool isPerMessageDeflateEnabled();
private:
// Member variables
int _handshakeTimeoutSecs;
@ -73,5 +74,10 @@ namespace ix
virtual void handleConnection(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState);
virtual size_t getConnectedClientsCount() final;
protected:
void handleUpgrade(std::unique_ptr<Socket> socket,
std::shared_ptr<ConnectionState> connectionState,
HttpRequestPtr request = nullptr);
};
} // namespace ix

View File

@ -45,7 +45,6 @@
#include <cstdarg>
#include <cstdlib>
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <thread>
@ -54,7 +53,6 @@
namespace ix
{
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true);
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
@ -74,6 +72,9 @@ namespace ix
, _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs)
, _pongReceived(false)
, _setCustomMessage(false)
, _kPingMessage("ixwebsocket::heartbeat")
, _pingType(SendMessageKind::Ping)
, _pingCount(0)
, _lastSendPingTimePoint(std::chrono::steady_clock::now())
{
@ -139,7 +140,7 @@ namespace ix
_enablePerMessageDeflate);
result = webSocketHandshake.clientHandshake(
remoteUrl, headers, host, path, port, timeoutSecs);
remoteUrl, headers, protocol, host, path, port, timeoutSecs);
if (result.http_status >= 300 && result.http_status < 400)
{
@ -170,7 +171,8 @@ namespace ix
// Server
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate)
bool enablePerMessageDeflate,
HttpRequestPtr request)
{
std::lock_guard<std::mutex> lock(_socketMutex);
@ -187,7 +189,8 @@ namespace ix
_perMessageDeflateOptions,
_enablePerMessageDeflate);
auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate);
auto result =
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
if (result.success)
{
setReadyState(ReadyState::OPEN);
@ -248,13 +251,51 @@ namespace ix
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs);
}
WebSocketSendInfo WebSocketTransport::sendHeartBeat()
void WebSocketTransport::setPingMessage(const std::string& message, SendMessageKind pingType)
{
_setCustomMessage = true;
_kPingMessage = message;
_pingType = pingType;
}
WebSocketSendInfo WebSocketTransport::sendHeartBeat(SendMessageKind pingMessage)
{
_pongReceived = false;
std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
<< "::" << _pingCount++;
return sendPing(ss.str());
ss << _kPingMessage;
if (!_setCustomMessage)
{
ss << "::" << _pingIntervalSecs << "s"
<< "::" << _pingCount++;
}
if (pingMessage == SendMessageKind::Ping)
{
return sendPing(ss.str());
}
else if (pingMessage == SendMessageKind::Binary)
{
WebSocketSendInfo info = sendBinary(ss.str(), nullptr);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
}
else if (pingMessage == SendMessageKind::Text)
{
WebSocketSendInfo info = sendText(ss.str(), nullptr);
if (info.success)
{
std::lock_guard<std::mutex> lck(_lastSendPingTimePointMutex);
_lastSendPingTimePoint = std::chrono::steady_clock::now();
}
return info;
}
// unknow type ping message
return {};
}
bool WebSocketTransport::closingDelayExceeded()
@ -270,7 +311,9 @@ namespace ix
{
if (pingIntervalExceeded())
{
if (!_pongReceived)
// If it is not a 'ping' message of ping type, there is no need to judge whether
// pong will receive it
if (_pingType == SendMessageKind::Ping && !_pongReceived)
{
// ping response (PONG) exceeds the maximum delay, close the connection
close(WebSocketCloseConstants::kInternalErrorCode,
@ -278,7 +321,7 @@ namespace ix
}
else
{
sendHeartBeat();
sendHeartBeat(_pingType);
}
}
}
@ -657,6 +700,7 @@ namespace ix
if (_readyState != ReadyState::CLOSING)
{
// send back the CLOSE frame
setReadyState(ReadyState::CLOSING);
sendCloseFrame(code, reason);
wakeUpFromPoll(SelectInterrupt::kCloseRequest);
@ -1029,7 +1073,10 @@ namespace ix
else if (ret <= 0)
{
closeSocket();
setReadyState(ReadyState::CLOSED);
if (_readyState != ReadyState::CLOSING)
{
setReadyState(ReadyState::CLOSED);
}
return false;
}
else

View File

@ -18,9 +18,10 @@
#include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h"
#include <atomic>
#include <cstdint>
#include <functional>
#include <list>
#include <memory>
@ -86,7 +87,8 @@ namespace ix
// Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs,
bool enablePerMessageDeflate);
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
PollResult poll();
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
@ -108,8 +110,12 @@ namespace ix
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const;
// set ping heartbeat message
void setPingMessage(const std::string& message, SendMessageKind pingType);
// internal
WebSocketSendInfo sendHeartBeat();
// send any type of ping packet, not only 'ping' type
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
private:
std::string _url;
@ -214,7 +220,10 @@ namespace ix
std::atomic<bool> _pongReceived;
static const int kDefaultPingIntervalSecs;
static const std::string kPingMessage;
bool _setCustomMessage;
std::string _kPingMessage;
SendMessageKind _pingType;
std::atomic<uint64_t> _pingCount;
// We record when ping are being sent so that we can know when to send the next one

View File

@ -6,4 +6,4 @@
#pragma once
#define IX_WEBSOCKET_VERSION "11.4.1"
#define IX_WEBSOCKET_VERSION "11.4.3"

View File

@ -13,16 +13,24 @@ all: brew
install: brew
-DCMAKE_INSTALL_PREFIX=/opt/homebrew
# Use -DCMAKE_INSTALL_PREFIX= to install into another location
# on osx it is good practice to make /usr/local user writable
# sudo chown -R `whoami`/staff /usr/local
#
# Those days (since Apple Silicon mac shipped), on macOS homebrew installs in /opt/homebrew, and /usr/local is readonly
#
# Release, Debug, MinSizeRel, RelWithDebInfo are the build types
#
# Default rule does not use python as that requires first time users to have Python3 installed
#
brew:
ifeq ($(shell uname),Darwin)
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_INSTALL_PREFIX=/opt/homebrew -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
else
mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install)
endif
# Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the
# server side ?) and I can't work-around it easily, so we're using mbedtls on

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNDCCAhwCFCl+O/rR8flqYKKvD0iwkucFwMaLMA0GCSqGSIb3DQEBCwUAMEEx
FDASBgNVBAoMC21hY2hpbmV6b25lMRQwEgYDVQQKDAtJWFdlYlNvY2tldDETMBEG
A1UEAwwKdHJ1c3RlZC1jYTAgFw0yMjA4MjMyMDM2MjVaGA80MjgxMDYwMTIwMzYy
NVowajELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhCZXJrZWxl
eTEbMBkGA1UECgwSRHVtbXkgT3JnYW5pemF0aW9uMR4wHAYDVQQDDBVub3QuYS52
YWxpZC5ob3N0Lm5hbWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2
9N806IjCvA82zfk9CPNwaEHOygNDJSXaZ38YDSI4C+Wf2imnMxrLQKaoccHUi+9L
4rQN/hSCg+uTULQUZ0iyssGDaIh4IcAeoEcNlXYHTrgP+aAaby3q5zeZ80K3+6e4
rqcuBPV2lLszJu3XXwEKbDSxW3De0gc2N8m9DN8Lx7i70DRf0F4m6RIMFF/kHXwa
zZLeG7rZb4xL684lmmQsWtk5Z600CvrBtUE7fQ7bhy0QhSt66kdTSL8IKQrbWcGj
q0tggFlOqeyZSi73gqUiAIuGO8/tRgmp4HygNyC24jpOB5nObOPPJTUEf5Mk1Bum
kD5a+yL6YbVdhiaK17wbAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKsLXGLfO1IZ
LbofGc7/TCQwRayR3RdG4864PBy97KfJWyizg7Wm4X4yPFRG+6q3X5NKW32Ew9lI
jXulXCTjWOiSxiG4Pk20uczkOhWVHFdnS9gZvykPC/ElxBKPalT6MMstZWxpElsk
rCDKXj4LkD0po8bZrjlgSZQQQk6XMRkoRai2GWLJqIjaNCSF8nqb1wM/1OE1tAwi
polO1eFMA24yypvlXcNrNXjqcQ7LaoQFQltmi/RV+uTk7EK2F2jgYVzJ/pe2ET0i
RIMbGZTlAiemDGL9SpMsxftG6fSmP6QqDqYExmmPlZMLprb2da/2kelWFA+VkvdG
zFrnIcyfMY4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtvTfNOiIwrwPNs35PQjzcGhBzsoDQyUl2md/GA0iOAvln9op
pzMay0CmqHHB1IvvS+K0Df4UgoPrk1C0FGdIsrLBg2iIeCHAHqBHDZV2B064D/mg
Gm8t6uc3mfNCt/unuK6nLgT1dpS7Mybt118BCmw0sVtw3tIHNjfJvQzfC8e4u9A0
X9BeJukSDBRf5B18Gs2S3hu62W+MS+vOJZpkLFrZOWetNAr6wbVBO30O24ctEIUr
eupHU0i/CCkK21nBo6tLYIBZTqnsmUou94KlIgCLhjvP7UYJqeB8oDcgtuI6TgeZ
zmzjzyU1BH+TJNQbppA+Wvsi+mG1XYYmite8GwIDAQABAoIBAGRzAXG9EglI01mV
sPfvyCi5NRhiFXRyGtxU4pTD8TuwXHxtfV0NU/KwJlBpVLBrvBCAAbeE/qHB6D9T
metx4ZorRs/tPrAmZ6LpANnWa50LfUdYGK0qyZ0lIYPm6YS2KJnfWm6LznEyq60j
/IW45YthaXTO7aOI0OjVrG+dd4CxU1g1NtCQ9bdDMDjfXFVnoOifXIl8W22eRMoZ
LzCz+0sI0R0LenXCPT566de21F0NDkIK7NaQ1l5BMX4PA+RsN3cZlzyruA1woPKI
aBR2LQGNrBfDVGMATtUm89RpWAftb8FmXqYUsM7zAzTO6dViitiB7OFlw7Ax15YH
MTj5zGECgYEA35ocEEMfyahBN70bjyiqOHlzKwFjDl9DsUf8xqHsNhYAL+GrOK9A
8lN5ByzcnbV3TJtU4WYbPgQJld8gXFx4h3eS+SkA/ASkAdtgHfdMImZ1v7k3TIPf
DXOCsHzELsQY6OgiI572Nwzx/Tl+0dFwaOfLjU9iEmmqL667j1Y4NiMCgYEA0Xch
9K/vwZ1I9gM3ySvG40R2TRriC9Bf8jwrEWeRsWNvBcqtMMrgwAMsMCKDugSZR7n6
o3WZV6mpvYVLFx0b93v07i7EpFP27kMw3gLNBKX62snR9JbqwAMK7tktgLds5pKM
DvLHuAQ9XMMXMLX7WlSyhmtFeU7IDulTSHHqdakCgYEAywITCpy2xpKRK7bwx4gH
C6EQc/IdahYJ0nHmSL0IRY6x+sbrelp7H8ezcVVEs5bmylGYvc/DWgm2XjCnI9P8
xhlFAhw9PZJFCT6QRISaxfy6WSgi0cBEieTeubd9MmxtpT/khuyy5AZHyj0iLAL4
CPayMwjopIj0r7f3p8qC3HsCgYBmq2kmYVI4aZrIkv02CtIatYTy+DlSJxnQRvOp
PUWpWB6kDRrk7pxJIYT4NwKwG+7xvFQA6PR3hn7fmUUcGDWMEeMVGDFkho9ja+W4
/FB3dc/Gi+PwakS4RwWF20e1brLfNXeXICMKrHFTVYC5bIm+VgOHZW8RLa9bt7wN
p2CPuQKBgQCjW+rCODmMzEues/I143mYMDdZ1IschtWGiXBNrpkHm/DcZSutbacm
5C7Kwv44pfA90NHDTjuaIgRgfeUTawkrl8uPXEuj80mW72agf5oS8lJzD+2jibCj
Q4K52G+0LaTxHLZxufil28Rgja01c0mTcuLbhKtCgHl5EHP19wOKEg==
-----END RSA PRIVATE KEY-----

View File

@ -24,14 +24,13 @@ set (TEST_TARGET_NAMES
# IXWebSocketBroadcastTest ## FIXME was depending on cobra / take a broadcast server from ws
IXStrCaseCompareTest
IXExponentialBackoffTest
IXWebSocketCloseTest
)
# Some unittest don't work on windows yet
# Windows without TLS does not have hmac yet
if (UNIX)
list(APPEND TEST_TARGET_NAMES
IXWebSocketCloseTest
# Fail on Windows in CI probably because the pathing is wrong and
# some resource files cannot be found
IXHttpServerTest

View File

@ -19,13 +19,9 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
std::string errMsg;
struct addrinfo* res;
res = dnsLookup->resolve(errMsg, [] { return false; });
auto res = dnsLookup->resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res != nullptr);
dnsLookup->release(res);
}
SECTION("Test resolving a non-existing hostname")
@ -33,7 +29,7 @@ TEST_CASE("dns", "[net]")
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
std::string errMsg;
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
auto res = dnsLookup->resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr);
}
@ -44,7 +40,7 @@ TEST_CASE("dns", "[net]")
std::string errMsg;
// The callback returning true means we are requesting cancellation
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
auto res = dnsLookup->resolve(errMsg, [] { return true; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res == nullptr);
}

View File

@ -7,7 +7,9 @@
#include "catch.hpp"
#include <cstdint>
#include <iostream>
#include <ixwebsocket/IXGetFreePort.h>
#include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXHttpServer.h>
using namespace ix;
@ -95,6 +97,52 @@ TEST_CASE("http_client", "[http]")
}
#endif
#if defined(IXWEBSOCKET_USE_TLS) && !defined(IXWEBSOCKET_USE_SECURE_TRANSPORT)
SECTION("Disable hostname validation")
{
static auto test_cert_with_wrong_name = [](bool validate_hostname)
{
int port = getFreePort();
ix::HttpServer server(port, "127.0.0.1");
SocketTLSOptions tlsOptionsServer;
tlsOptionsServer.tls = true;
tlsOptionsServer.caFile = "NONE";
tlsOptionsServer.certFile = "./.certs/wrong-name-server-crt.pem";
tlsOptionsServer.keyFile = "./.certs/wrong-name-server-key.pem";
server.setTLSOptions(tlsOptionsServer);
auto res = server.listen();
REQUIRE(res.first);
server.start();
HttpClient httpClient;
SocketTLSOptions tlsOptionsClient;
tlsOptionsClient.caFile = "./.certs/trusted-ca-crt.pem";
tlsOptionsClient.disable_hostname_validation = validate_hostname;
httpClient.setTLSOptions(tlsOptionsClient);
std::string url("https://localhost:" + std::to_string(port));
auto args = httpClient.createRequest(url);
args->connectTimeout = 10;
args->transferTimeout = 10;
auto response = httpClient.get(url, args);
std::cerr << "Status: " << response->statusCode << std::endl;
std::cerr << "Error code: " << (int) response->errorCode << std::endl;
std::cerr << "Error message: " << response->errorMsg << std::endl;
server.stop();
return std::make_tuple(response->errorCode, response->statusCode);
};
REQUIRE(test_cert_with_wrong_name(false) ==
std::make_tuple(HttpErrorCode::CannotConnect, 0));
REQUIRE(test_cert_with_wrong_name(true) == std::make_tuple(HttpErrorCode::Ok, 404));
}
#endif
SECTION("Async API, one call")
{
bool async = true;

View File

@ -77,24 +77,6 @@ namespace
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
}
// Assume the file exists
std::string readBytes(const std::string& path)
{
std::vector<uint8_t> memblock;
std::ifstream file(path);
file.seekg(0, file.end);
std::streamoff size = file.tellg();
file.seekg(0, file.beg);
memblock.reserve((size_t) size);
memblock.insert(
memblock.begin(), std::istream_iterator<char>(file), std::istream_iterator<char>());
std::string bytes(memblock.begin(), memblock.end());
return bytes;
}
std::string truncate(const std::string& str, size_t n)
{
if (str.size() < n)
@ -107,12 +89,6 @@ namespace
}
}
bool fileExists(const std::string& fileName)
{
std::ifstream infile(fileName);
return infile.good();
}
std::string extractFilename(const std::string& path)
{
std::string::size_type idx;
@ -916,9 +892,8 @@ namespace ix
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
std::string errMsg;
struct addrinfo* res;
res = dnsLookup->resolve(errMsg, [] { return false; });
auto res = dnsLookup->resolve(errMsg, [] { return false; });
auto addr = res->ai_addr;
@ -2486,10 +2461,8 @@ int main(int argc, char** argv)
bool verbose = false;
bool save = false;
bool quiet = false;
bool fluentd = false;
bool compress = false;
bool compressRequest = false;
bool stress = false;
bool disableAutomaticReconnection = false;
bool disablePerMessageDeflate = false;
bool greetings = false;
@ -2505,7 +2478,6 @@ int main(int argc, char** argv)
int transferTimeout = 1800;
int maxRedirects = 5;
int delayMs = -1;
int count = 1;
int msgCount = 1000 * 1000;
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
int pingIntervalSecs = 30;
@ -2529,6 +2501,7 @@ int main(int argc, char** argv)
"A (comma/space/colon) separated list of ciphers to use for TLS");
app->add_flag("--tls", tlsOptions.tls, "Enable TLS (server only)");
app->add_flag("--verify_none", verifyNone, "Disable peer cert verification");
app->add_flag("--disable-hostname-validation", tlsOptions.disable_hostname_validation, "Disable validation of certificates' hostnames");
};
app.add_flag("--version", version, "Print ws version");