Compare commits

..

30 Commits

Author SHA1 Message Date
4a2a8e7b36 revert fake ci change 2023-02-25 13:57:28 -08:00
f073203ac1 Use a deque instead of a vector to avoid an O(n^2) time complexity problem 2023-02-25 13:56:23 -08:00
1866d94550 bump ci 2023-02-25 13:53:01 -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
6d8495bd73 Update CHANGELOG.md 2022-04-23 22:53:36 -07:00
b8563eddd1 11.4.1 2022-04-23 22:52:32 -07:00
46bd2aa4a1 vcpkg zlib dep fix (#385)
* vcpkg zlib dep fix

* Use cmake.in file instead of write file directly

Co-authored-by: Cheney-Wang <v-xincwa@microsoft.com>
2022-04-23 18:16:13 -07:00
4420bc70b5 Revert "Export static symbols when building ws with shared library (#370)" (#383)
This reverts commit a3d2fa4b7e.
2022-04-12 08:55:43 -07:00
20921f341a Update README.md 2022-03-28 22:04:27 -07:00
2829c62ef9 Fix error handling after calling X509_NAME_get_index_by_NID
This should fix #376
2022-03-27 19:14:40 -07:00
a3d2fa4b7e Export static symbols when building ws with shared library (#370) 2022-03-19 11:41:40 -07:00
49 changed files with 753 additions and 375 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 (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON) set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF) set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_EXPORT_COMPILE_COMMANDS yes)
option (BUILD_DEMO OFF) option (BUILD_DEMO OFF)
@ -66,6 +67,7 @@ set( IXWEBSOCKET_SOURCES
) )
set( IXWEBSOCKET_HEADERS set( IXWEBSOCKET_HEADERS
ixwebsocket/IXBase64.h
ixwebsocket/IXBench.h ixwebsocket/IXBench.h
ixwebsocket/IXCancellationRequest.h ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h ixwebsocket/IXConnectionState.h
@ -234,11 +236,7 @@ endif()
option(USE_ZLIB "Enable zlib support" TRUE) option(USE_ZLIB "Enable zlib support" TRUE)
if (USE_ZLIB) if (USE_ZLIB)
# This ZLIB_FOUND check is to help find a cmake manually configured zlib find_package(ZLIB REQUIRED)
if (NOT ZLIB_FOUND)
find_package(ZLIB REQUIRED)
endif()
target_include_directories(ixwebsocket PUBLIC $<BUILD_INTERFACE:${ZLIB_INCLUDE_DIRS}>)
target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB) target_link_libraries(ixwebsocket PRIVATE ZLIB::ZLIB)
target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB) target_compile_definitions(ixwebsocket PUBLIC IXWEBSOCKET_USE_ZLIB)
@ -253,7 +251,7 @@ if (WIN32)
endif() endif()
endif() endif()
if (UNIX) if (UNIX AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads) find_package(Threads)
target_link_libraries(ixwebsocket PRIVATE Threads::Threads) target_link_libraries(ixwebsocket PRIVATE Threads::Threads)
@ -289,10 +287,14 @@ if (IXWEBSOCKET_INSTALL)
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ixwebsocket/ 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")
install(EXPORT ixwebsocket install(EXPORT ixwebsocket
FILE ixwebsocket-config.cmake FILE ixwebsocket-targets.cmake
NAMESPACE ixwebsocket:: NAMESPACE ixwebsocket::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket) DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ixwebsocket
)
endif() endif()
if (USE_WS OR USE_TEST) if (USE_WS OR USE_TEST)

View File

@ -2,9 +2,7 @@
IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing. IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use and support everything you'll likely need for websocket dev (SSL, deflate compression, compiles on most platforms, etc...). HTTP client and server code is also available, but it hasn't received as much testing.
It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Note that the MinGW compiler is not supported at this point. Two important design goals are simplicity and correctness. It is been used on big mobile video game titles sending and receiving tons of messages since 2017 (iOS and Android). It was tested on macOS, iOS, Linux, Android, Windows and FreeBSD. Two important design goals are simplicity and correctness.
A bad security bug affecting users compiling with SSL enabled and OpenSSL as the backend was just fixed in newly released version 11.0.0. Please upgrade ! (more details in the [https://github.com/machinezone/IXWebSocket/pull/250](PR).
```cpp ```cpp
/* /*
@ -37,6 +35,7 @@ int main()
// Connect to a server with encryption // Connect to a server with encryption
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration // 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"); std::string url("wss://echo.websocket.org");
webSocket.setUrl(url); webSocket.setUrl(url);

View File

@ -2,6 +2,16 @@
All changes to this project will be documented in this file. 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
## [11.4.0] - 2022-01-05 ## [11.4.0] - 2022-01-05
(Windows) Use wsa select event, which should lead to a much better behavior on Windows in general, and also when sending large payloads (#342) (Windows) Use wsa select event, which should lead to a much better behavior on Windows in general, and also when sending large payloads (#342)

View File

@ -54,7 +54,7 @@ To use the installed package within a cmake project, use the following:
# include headers # include headers
include_directories(${IXWEBSOCKET_INCLUDE_DIR}) 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. // Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters. // 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.setOnConnectionCallback(
[&server](std::weak_ptr<WebSocket> webSocket, [&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. // Run a server on localhost at a given port.
// Bound host name, max connections and listen backlog can also be passed in as parameters. // 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) { server.setOnClientMessageCallback([](std::shared_ptr<ix::ConnectionState> connectionState, ix::WebSocket & webSocket, const ix::WebSocketMessagePtr & msg) {
// The ConnectionState object contains information about the connection, // 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: For a server, specifying `caFile` implies that:
1. You require clients to present a certificate 1. You require clients to present a certificate
1. It must be signed by one of the trusted roots in the file 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

@ -0,0 +1,9 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
if (@USE_ZLIB@)
find_dependency(ZLIB)
endif()
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 #pragma once
#include <chrono> #include <chrono>
#include <stdint.h> #include <cstdint>
#include <string> #include <string>
namespace ix namespace ix

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
#include "IXUserAgent.h" #include "IXUserAgent.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include <assert.h> #include <assert.h>
#include <cstdint>
#include <cstring> #include <cstring>
#include <iomanip> #include <iomanip>
#include <random> #include <random>
@ -139,8 +140,9 @@ namespace ix
std::string protocol, host, path, query; std::string protocol, host, path, query;
int port; 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; std::stringstream ss;
ss << "Cannot parse url: " << url; ss << "Cannot parse url: " << url;
@ -173,7 +175,12 @@ namespace ix
// Build request string // Build request string
std::stringstream ss; std::stringstream ss;
ss << verb << " " << path << " HTTP/1.1\r\n"; 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 #ifdef IXWEBSOCKET_USE_ZLIB
if (args->compress && !args->onChunkCallback) if (args->compress && !args->onChunkCallback)

View File

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

View File

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

View File

@ -6,6 +6,8 @@
#pragma once #pragma once
#include <cstdint>
#ifdef _WIN32 #ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -205,7 +205,9 @@ namespace ix
_sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket); _sslContext, SocketAppleSSL::readFromSocket, SocketAppleSSL::writeToSocket);
SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd); SSLSetConnection(_sslContext, (SSLConnectionRef)(long) _sockfd);
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12); 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()) if (_tlsOptions.isPeerVerifyDisabled())
{ {

View File

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

View File

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

View File

@ -301,7 +301,11 @@ namespace ix
} }
bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl, bool SocketOpenSSL::openSSLCheckServerCert(SSL* ssl,
#if OPENSSL_VERSION_NUMBER < 0x10100000L
const std::string& hostname, const std::string& hostname,
#else
const std::string& /* hostname */,
#endif
std::string& errMsg) std::string& errMsg)
{ {
X509* server_cert = SSL_get_peer_certificate(ssl); X509* server_cert = SSL_get_peer_certificate(ssl);
@ -339,12 +343,12 @@ namespace ix
{ {
int cn_pos = X509_NAME_get_index_by_NID( int cn_pos = X509_NAME_get_index_by_NID(
X509_get_subject_name((X509*) server_cert), NID_commonName, -1); X509_get_subject_name((X509*) server_cert), NID_commonName, -1);
if (cn_pos) if (cn_pos >= 0)
{ {
X509_NAME_ENTRY* cn_entry = X509_NAME_ENTRY* cn_entry =
X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos); X509_NAME_get_entry(X509_get_subject_name((X509*) server_cert), cn_pos);
if (cn_entry) if (cn_entry != nullptr)
{ {
ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
char* cn = (char*) ASN1_STRING_data(cn_asn1); char* cn = (char*) ASN1_STRING_data(cn_asn1);
@ -390,6 +394,11 @@ namespace ix
int connect_result = SSL_connect(_ssl_connection); int connect_result = SSL_connect(_ssl_connection);
if (connect_result == 1) if (connect_result == 1)
{ {
if (_tlsOptions.disable_hostname_validation)
{
return true;
}
return openSSLCheckServerCert(_ssl_connection, host, errMsg); return openSSLCheckServerCert(_ssl_connection, host, errMsg);
} }
int reason = SSL_get_error(_ssl_connection, connect_result); 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 // (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 // 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.) // below is enabled for all versions prior to 1.1.0.)
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection); if (!_tlsOptions.disable_hostname_validation)
X509_VERIFY_PARAM_set1_host(param, host.c_str(), 0); {
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl_connection);
X509_VERIFY_PARAM_set1_host(param, host.c_str(), host.size());
}
#endif #endif
handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested); handshakeSuccessful = openSSLClientHandshake(host, errMsg, isCancellationRequested);
} }

View File

@ -268,7 +268,10 @@ namespace ix
// Set the socket to non blocking mode, so that accept calls are not blocking // Set the socket to non blocking mode, so that accept calls are not blocking
SocketConnect::configure(_serverFd); 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 (;;) for (;;)
{ {
@ -425,7 +428,10 @@ namespace ix
void SocketServer::runGC() 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 (;;) for (;;)
{ {

View File

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

View File

@ -333,6 +333,19 @@ namespace
return Result; 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
namespace ix namespace ix
@ -343,6 +356,18 @@ namespace ix
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port) 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); clParseURL res = clParseURL::ParseURL(url);
@ -356,23 +381,12 @@ namespace ix
path = res.m_Path; path = res.m_Path;
query = res.m_Query; query = res.m_Query;
const auto protocolPort = getProtocolPort(protocol);
if (!res.GetPort(&port)) if (!res.GetPort(&port))
{ {
if (protocol == "ws" || protocol == "http") port = protocolPort;
{
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;
}
} }
isProtocolDefaultPort = port == protocolPort;
if (path.empty()) if (path.empty())
{ {

View File

@ -19,5 +19,13 @@ namespace ix
std::string& path, std::string& path,
std::string& query, std::string& query,
int& port); 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 } // namespace ix

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "IXCancellationRequest.h" #include "IXCancellationRequest.h"
#include "IXHttp.h"
#include "IXSocket.h" #include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketInitResult.h" #include "IXWebSocketInitResult.h"
@ -35,7 +36,9 @@ namespace ix
int port, int port,
int timeoutSecs); int timeoutSecs);
WebSocketInitResult serverHandshake(int timeoutSecs, bool enablePerMessageDeflate); WebSocketInitResult serverHandshake(int timeoutSecs,
bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
private: private:
std::string genRandomString(const int len); std::string genRandomString(const int len);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,6 @@
#include <cstdarg> #include <cstdarg>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <thread> #include <thread>
@ -54,7 +53,6 @@
namespace ix namespace ix
{ {
const std::string WebSocketTransport::kPingMessage("ixwebsocket::heartbeat");
const int WebSocketTransport::kDefaultPingIntervalSecs(-1); const int WebSocketTransport::kDefaultPingIntervalSecs(-1);
const bool WebSocketTransport::kDefaultEnablePong(true); const bool WebSocketTransport::kDefaultEnablePong(true);
const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300); const int WebSocketTransport::kClosingMaximumWaitingDelayInMs(300);
@ -73,6 +71,9 @@ namespace ix
, _closingTimePoint(std::chrono::steady_clock::now()) , _closingTimePoint(std::chrono::steady_clock::now())
, _enablePong(kDefaultEnablePong) , _enablePong(kDefaultEnablePong)
, _pingIntervalSecs(kDefaultPingIntervalSecs) , _pingIntervalSecs(kDefaultPingIntervalSecs)
, _setCustomMessage(false)
, _kPingMessage("ixwebsocket::heartbeat")
, _pingType(SendMessageKind::Ping)
, _pongReceived(false) , _pongReceived(false)
, _pingCount(0) , _pingCount(0)
, _lastSendPingTimePoint(std::chrono::steady_clock::now()) , _lastSendPingTimePoint(std::chrono::steady_clock::now())
@ -170,7 +171,8 @@ namespace ix
// Server // Server
WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult WebSocketTransport::connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate) bool enablePerMessageDeflate,
HttpRequestPtr request)
{ {
std::lock_guard<std::mutex> lock(_socketMutex); std::lock_guard<std::mutex> lock(_socketMutex);
@ -187,7 +189,8 @@ namespace ix
_perMessageDeflateOptions, _perMessageDeflateOptions,
_enablePerMessageDeflate); _enablePerMessageDeflate);
auto result = webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate); auto result =
webSocketHandshake.serverHandshake(timeoutSecs, enablePerMessageDeflate, request);
if (result.success) if (result.success)
{ {
setReadyState(ReadyState::OPEN); setReadyState(ReadyState::OPEN);
@ -248,13 +251,51 @@ namespace ix
return now - _lastSendPingTimePoint > std::chrono::seconds(_pingIntervalSecs); 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; _pongReceived = false;
std::stringstream ss; std::stringstream ss;
ss << kPingMessage << "::" << _pingIntervalSecs << "s"
<< "::" << _pingCount++; ss << _kPingMessage;
return sendPing(ss.str()); 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() bool WebSocketTransport::closingDelayExceeded()
@ -270,7 +311,9 @@ namespace ix
{ {
if (pingIntervalExceeded()) 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 // ping response (PONG) exceeds the maximum delay, close the connection
close(WebSocketCloseConstants::kInternalErrorCode, close(WebSocketCloseConstants::kInternalErrorCode,
@ -278,7 +321,7 @@ namespace ix
} }
else else
{ {
sendHeartBeat(); sendHeartBeat(_pingType);
} }
} }
} }

View File

@ -18,15 +18,17 @@
#include "IXWebSocketHttpHeaders.h" #include "IXWebSocketHttpHeaders.h"
#include "IXWebSocketPerMessageDeflate.h" #include "IXWebSocketPerMessageDeflate.h"
#include "IXWebSocketPerMessageDeflateOptions.h" #include "IXWebSocketPerMessageDeflateOptions.h"
#include "IXWebSocketSendInfo.h"
#include "IXWebSocketSendData.h" #include "IXWebSocketSendData.h"
#include "IXWebSocketSendInfo.h"
#include <atomic> #include <atomic>
#include <cstdint>
#include <functional> #include <functional>
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <vector> #include <vector>
#include <deque>
namespace ix namespace ix
{ {
@ -86,7 +88,8 @@ namespace ix
// Server // Server
WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket, WebSocketInitResult connectToSocket(std::unique_ptr<Socket> socket,
int timeoutSecs, int timeoutSecs,
bool enablePerMessageDeflate); bool enablePerMessageDeflate,
HttpRequestPtr request = nullptr);
PollResult poll(); PollResult poll();
WebSocketSendInfo sendBinary(const IXWebSocketSendData& message, WebSocketSendInfo sendBinary(const IXWebSocketSendData& message,
@ -108,8 +111,12 @@ namespace ix
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback); void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
size_t bufferedAmount() const; size_t bufferedAmount() const;
// set ping heartbeat message
void setPingMessage(const std::string& message, SendMessageKind pingType);
// internal // internal
WebSocketSendInfo sendHeartBeat(); // send any type of ping packet, not only 'ping' type
WebSocketSendInfo sendHeartBeat(SendMessageKind pingType);
private: private:
std::string _url; std::string _url;
@ -150,7 +157,7 @@ namespace ix
// Contains all messages that were fetched in the last socket read. // Contains all messages that were fetched in the last socket read.
// This could be a mix of control messages (Close, Ping, etc...) and // This could be a mix of control messages (Close, Ping, etc...) and
// data messages. That buffer is resized // data messages. That buffer is resized
std::vector<uint8_t> _rxbuf; std::deque<uint8_t> _rxbuf;
// Contains all messages that are waiting to be sent // Contains all messages that are waiting to be sent
std::vector<uint8_t> _txbuf; std::vector<uint8_t> _txbuf;
@ -214,7 +221,10 @@ namespace ix
std::atomic<bool> _pongReceived; std::atomic<bool> _pongReceived;
static const int kDefaultPingIntervalSecs; static const int kDefaultPingIntervalSecs;
static const std::string kPingMessage;
bool _setCustomMessage;
std::string _kPingMessage;
SendMessageKind _pingType;
std::atomic<uint64_t> _pingCount; std::atomic<uint64_t> _pingCount;
// We record when ping are being sent so that we can know when to send the next one // 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 #pragma once
#define IX_WEBSOCKET_VERSION "11.4.0" #define IX_WEBSOCKET_VERSION "11.4.3"

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

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

View File

@ -7,7 +7,9 @@
#include "catch.hpp" #include "catch.hpp"
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <ixwebsocket/IXGetFreePort.h>
#include <ixwebsocket/IXHttpClient.h> #include <ixwebsocket/IXHttpClient.h>
#include <ixwebsocket/IXHttpServer.h>
using namespace ix; using namespace ix;
@ -95,6 +97,52 @@ TEST_CASE("http_client", "[http]")
} }
#endif #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") SECTION("Async API, one call")
{ {
bool async = true; bool async = true;

View File

@ -77,24 +77,6 @@ namespace
return std::make_pair(res.first, std::string(vec.begin(), vec.end())); 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) std::string truncate(const std::string& str, size_t n)
{ {
if (str.size() < 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 extractFilename(const std::string& path)
{ {
std::string::size_type idx; std::string::size_type idx;
@ -916,9 +892,8 @@ namespace ix
auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80); auto dnsLookup = std::make_shared<DNSLookup>(hostname, 80);
std::string errMsg; std::string errMsg;
struct addrinfo* res;
res = dnsLookup->resolve(errMsg, [] { return false; }); auto res = dnsLookup->resolve(errMsg, [] { return false; });
auto addr = res->ai_addr; auto addr = res->ai_addr;
@ -2486,10 +2461,8 @@ int main(int argc, char** argv)
bool verbose = false; bool verbose = false;
bool save = false; bool save = false;
bool quiet = false; bool quiet = false;
bool fluentd = false;
bool compress = false; bool compress = false;
bool compressRequest = false; bool compressRequest = false;
bool stress = false;
bool disableAutomaticReconnection = false; bool disableAutomaticReconnection = false;
bool disablePerMessageDeflate = false; bool disablePerMessageDeflate = false;
bool greetings = false; bool greetings = false;
@ -2505,7 +2478,6 @@ int main(int argc, char** argv)
int transferTimeout = 1800; int transferTimeout = 1800;
int maxRedirects = 5; int maxRedirects = 5;
int delayMs = -1; int delayMs = -1;
int count = 1;
int msgCount = 1000 * 1000; int msgCount = 1000 * 1000;
uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds uint32_t maxWaitBetweenReconnectionRetries = 10 * 1000; // 10 seconds
int pingIntervalSecs = 30; 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"); "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("--tls", tlsOptions.tls, "Enable TLS (server only)");
app->add_flag("--verify_none", verifyNone, "Disable peer cert verification"); 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"); app.add_flag("--version", version, "Print ws version");