Compare commits
212 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4256e2a68d | |||
| 69dcd70c5d | |||
| 1b120e05b8 | |||
| db1c9e383e | |||
| 2b5730c815 | |||
| 1d635f37d5 | |||
| ea66c8fa60 | |||
| 397cee1d36 | |||
| af5d1cbb55 | |||
| 18e8988862 | |||
| 6f9c3f0ffb | |||
| 7e1018e071 | |||
| 91bff96571 | |||
| 07e219f6b3 | |||
| bba1a26cdf | |||
| c4829c4676 | |||
| 768db18978 | |||
| 2d0d937616 | |||
| b341911fab | |||
| cbec732641 | |||
| b1a34edbbe | |||
| 59df9f1ecb | |||
| a7893472a2 | |||
| 4884fd205e | |||
| 165ea7cdfd | |||
| 488326477d | |||
| 240bd8cf49 | |||
| b6b8e426a7 | |||
| e7d519fee7 | |||
| 9fb6e42c6f | |||
| 621994d06e | |||
| a65bc2ab16 | |||
| 4d51179819 | |||
| 5e9779058e | |||
| 4ef2548888 | |||
| c7eff4ad06 | |||
| 6a2c9a7dbe | |||
| 28cd2bade2 | |||
| 729a1ea7dc | |||
| db7e879578 | |||
| bbb76a7e13 | |||
| e1f9d37ffa | |||
| cba37d9504 | |||
| b505f37c47 | |||
| 93eceede6b | |||
| ea3bdc79f0 | |||
| 5756244611 | |||
| e2d346e9dd | |||
| 5d11fd38c8 | |||
| cbb65d6290 | |||
| e3b886e9c4 | |||
| 876832ccbd | |||
| 76da380e88 | |||
| 5f08c76956 | |||
| 9ae19c4318 | |||
| dc85a444ac | |||
| 61b5fd278e | |||
| 0b8a03b191 | |||
| 23ea07806b | |||
| a28b59d173 | |||
| c47ac0dd8f | |||
| 58822c75e7 | |||
| 8b5d0d7114 | |||
| 2fbf0e49ca | |||
| 7b94de9d68 | |||
| 103789d527 | |||
| e5b722f71b | |||
| cfebe0cc83 | |||
| f69f971194 | |||
| 55abc27a64 | |||
| ef4dd2647a | |||
| dfa3bddb5c | |||
| 4cecd16f8f | |||
| a830a8796e | |||
| 85590bf4b6 | |||
| 97ac808c6b | |||
| 71589669fb | |||
| 06b441ba22 | |||
| 29d862b495 | |||
| 3c84577785 | |||
| bffff33cab | |||
| 182ee4cad6 | |||
| b74d82f111 | |||
| 7166d87f82 | |||
| 73abe5eaf5 | |||
| 1be4271d14 | |||
| 4d3b9f633d | |||
| 11be279005 | |||
| 2ca6bb6835 | |||
| b47cb43419 | |||
| 9e0825127d | |||
| 6d156430f5 | |||
| 7e6299a3b6 | |||
| 166492ac9b | |||
| 4200eeae57 | |||
| 2e3672288d | |||
| 8d743c5264 | |||
| 1bff15e048 | |||
| 0ad0129177 | |||
| 3d4ecc4501 | |||
| ddf992a03c | |||
| f0117838f0 | |||
| ed6f75a478 | |||
| e49959eba7 | |||
| 62c3cf6fdb | |||
| 9aa3f60aea | |||
| 526ed247ee | |||
| ea95b7c751 | |||
| 56cf893f70 | |||
| e211bc4aa8 | |||
| c1fa82b4b9 | |||
| b7ff7ed2c0 | |||
| d7b7cafb96 | |||
| 12ce63471c | |||
| 52b653ac66 | |||
| f0ea66e1c0 | |||
| 83ee8fe0f2 | |||
| eb77736503 | |||
| 71bec42d34 | |||
| fe7e83c580 | |||
| db3f857e8d | |||
| 1841b28bb0 | |||
| e8d9d33d21 | |||
| 5df59f863f | |||
| 9ebfa13931 | |||
| b5fba830b9 | |||
| 1a19651a06 | |||
| 7e20967d83 | |||
| 15f4f37c55 | |||
| 00e232db1c | |||
| 7a58f6ee74 | |||
| e71602c769 | |||
| 6dc6a47eab | |||
| dd5da0f2ac | |||
| 37c0a193fa | |||
| 4d70380d11 | |||
| f3f86e4e91 | |||
| 99c1e62c9e | |||
| 8f8d9e79bc | |||
| 3c49512db2 | |||
| 79877d9a7f | |||
| 68c5c68184 | |||
| 7b185d3e08 | |||
| 4e778eb242 | |||
| 1e055ba477 | |||
| 24c57c9dde | |||
| c8b3c778b2 | |||
| ef2f01d5bf | |||
| 4e9f164e4a | |||
| 18b64f6fb5 | |||
| c865455a94 | |||
| f7dcceecfe | |||
| c7ae809729 | |||
| d7368170ad | |||
| 000e8d6b79 | |||
| 2943994a98 | |||
| 6b1a569f90 | |||
| 39caac8a14 | |||
| 042301a0db | |||
| adc247b44e | |||
| 91e403e66b | |||
| 948e56c9e2 | |||
| 5f96dac423 | |||
| f4f1339aa6 | |||
| e37ae7002d | |||
| b21821dc26 | |||
| 5264a7dc80 | |||
| d525721cc9 | |||
| 01936f6efd | |||
| 18ff1c652d | |||
| 4b69efb91d | |||
| c04be63c60 | |||
| ad37b990b1 | |||
| 5b54c823b5 | |||
| 8b42b51219 | |||
| 10cc967442 | |||
| 5eb09a24bf | |||
| 2a45556cbc | |||
| 6b6cd42b6b | |||
| b40a390522 | |||
| 42f761cceb | |||
| 6c443ee5c5 | |||
| c8965c8b6a | |||
| a2c9ae50d7 | |||
| d8e0b70424 | |||
| 88954f5bff | |||
| 19220484c6 | |||
| 5f05bbebac | |||
| 43d368f9a0 | |||
| 5350ebdcf2 | |||
| 9c5a52226a | |||
| bfc4c21574 | |||
| e1a4a1d37c | |||
| 3436608f30 | |||
| 3a77248222 | |||
| 8ac11111d1 | |||
| 4cf96cf51b | |||
| 4292e10c12 | |||
| 41c8c84761 | |||
| f0600152f7 | |||
| f9ca44b474 | |||
| 6f729c5038 | |||
| 5bcf001af0 | |||
| 2ced3151f7 | |||
| bd01be61d2 | |||
| c880faa5ff | |||
| 0db1e7c331 | |||
| c0410e43aa | |||
| 12d32c7cfc | |||
| b581e05664 | |||
| 20dcf82069 | |||
| bdfc739bad |
@@ -1,46 +0,0 @@
|
|||||||
# https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormatStyleOptions.html
|
|
||||||
|
|
||||||
---
|
|
||||||
Language: Cpp
|
|
||||||
|
|
||||||
BasedOnStyle: WebKit
|
|
||||||
|
|
||||||
AlignAfterOpenBracket: Align
|
|
||||||
AlignOperands: true
|
|
||||||
AlignTrailingComments: true
|
|
||||||
AllowAllParametersOfDeclarationOnNextLine: true
|
|
||||||
AllowShortBlocksOnASingleLine: false
|
|
||||||
AllowShortCaseLabelsOnASingleLine: true
|
|
||||||
AllowShortFunctionsOnASingleLine: InlineOnly
|
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
|
||||||
AllowShortLoopsOnASingleLine: false
|
|
||||||
AlwaysBreakTemplateDeclarations: true
|
|
||||||
BinPackArguments: false
|
|
||||||
BinPackParameters: false
|
|
||||||
BreakBeforeBinaryOperators: None
|
|
||||||
BreakBeforeBraces: Allman
|
|
||||||
BreakConstructorInitializersBeforeComma: true
|
|
||||||
ColumnLimit: 100
|
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
|
||||||
Cpp11BracedListStyle: true
|
|
||||||
FixNamespaceComments: true
|
|
||||||
IncludeBlocks: Regroup
|
|
||||||
IncludeCategories:
|
|
||||||
- Regex: '^["<](stdafx|pch)\.h[">]$'
|
|
||||||
Priority: -1
|
|
||||||
- Regex: '^<Windows\.h>$'
|
|
||||||
Priority: 3
|
|
||||||
- Regex: '^<(WinIoCtl|winhttp|Shellapi)\.h>$'
|
|
||||||
Priority: 4
|
|
||||||
- Regex: '.*'
|
|
||||||
Priority: 2
|
|
||||||
IndentCaseLabels: true
|
|
||||||
IndentWidth: 4
|
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
|
||||||
MaxEmptyLinesToKeep: 2
|
|
||||||
NamespaceIndentation: All
|
|
||||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
|
||||||
PointerAlignment: Left
|
|
||||||
SpaceAfterTemplateKeyword: false
|
|
||||||
Standard: Cpp11
|
|
||||||
UseTab: Never
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
build
|
|
||||||
CMakeCache.txt
|
|
||||||
ws/CMakeCache.txt
|
|
||||||
test/build
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
build
|
|
||||||
*.pyc
|
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
language: bash
|
|
||||||
|
|
||||||
# See https://github.com/amaiorano/vectrexy/blob/master/.travis.yml
|
|
||||||
# for ideas on installing vcpkg
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# macOS
|
|
||||||
- os: osx
|
|
||||||
compiler: clang
|
|
||||||
script:
|
|
||||||
- python test/run.py
|
|
||||||
- make ws
|
|
||||||
|
|
||||||
# # Linux
|
|
||||||
# - os: linux
|
|
||||||
# dist: xenial
|
|
||||||
# script:
|
|
||||||
# - python test/run.py
|
|
||||||
# - make ws
|
|
||||||
# env:
|
|
||||||
# - CC=gcc
|
|
||||||
# - CXX=g++
|
|
||||||
|
|
||||||
# Clang + Linux disabled for now
|
|
||||||
- os: linux
|
|
||||||
dist: xenial
|
|
||||||
script: python test/run.py
|
|
||||||
env:
|
|
||||||
- CC=clang
|
|
||||||
- CXX=clang++
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
- os: windows
|
|
||||||
env:
|
|
||||||
- CMAKE_PATH="/c/Program Files/CMake/bin"
|
|
||||||
script:
|
|
||||||
- export PATH=$CMAKE_PATH:$PATH
|
|
||||||
- cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .
|
|
||||||
- cmake --build --parallel .
|
|
||||||
- python test/run.py
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="/img/favicon.ico">
|
||||||
|
<title>IXWebSocket</title>
|
||||||
|
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="/css/base.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/.">IXWebSocket</a>
|
||||||
|
<!-- Expander button -->
|
||||||
|
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-collapse">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Expanded navigation -->
|
||||||
|
<div id="navbar-collapse" class="navbar-collapse collapse">
|
||||||
|
<!-- Main navigation -->
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/." class="nav-link">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/CHANGELOG/" class="nav-link">Changelog</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/build/" class="nav-link">Build</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/design/" class="nav-link">Design</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/packages/" class="nav-link">Packages</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/performance/" class="nav-link">Performance</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/usage/" class="nav-link">Examples</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="/ws/" class="nav-link">Ws</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#" class="nav-link" data-toggle="modal" data-target="#mkdocs_search_modal">
|
||||||
|
<i class="fa fa-search"></i> Search
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div id="main-content" class="span12">
|
||||||
|
<h1 id="404-page-not-found" style="text-align: center">404</h1>
|
||||||
|
<p style="text-align: center"><strong>Page not found</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
<p>Documentation built with <a href="https://www.mkdocs.org/">MkDocs</a>.</p>
|
||||||
|
</footer>
|
||||||
|
<script src="/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var base_url = "/",
|
||||||
|
shortcuts = {"help": 191, "next": 78, "previous": 80, "search": 83};
|
||||||
|
</script>
|
||||||
|
<script src="/js/base.js"></script>
|
||||||
|
<script src="/search/main.js"></script>
|
||||||
|
|
||||||
|
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="searchModalLabel">Search</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>From here you can search these documents. Enter your search terms below.</p>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="search" class="form-control" placeholder="Search..." id="mkdocs-search-query" title="Type search term here">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="mkdocs-search-results" data-no-results-text="No results found"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><div class="modal" id="mkdocs_keyboard_modal" tabindex="-1" role="dialog" aria-labelledby="keyboardModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="keyboardModalLabel">Keyboard Shortcuts</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Keys</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="help shortcut"><kbd>?</kbd></td>
|
||||||
|
<td>Open this help</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="next shortcut"><kbd>n</kbd></td>
|
||||||
|
<td>Next page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="prev shortcut"><kbd>p</kbd></td>
|
||||||
|
<td>Previous page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="search shortcut"><kbd>s</kbd></td>
|
||||||
|
<td>Search</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
-181
@@ -1,181 +0,0 @@
|
|||||||
#
|
|
||||||
# Author: Benjamin Sergeant
|
|
||||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.4.1)
|
|
||||||
project(ixwebsocket C CXX)
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
set (CXX_STANDARD_REQUIRED ON)
|
|
||||||
set (CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
# -Wshorten-64-to-32 does not work with clang
|
|
||||||
if (NOT WIN32)
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set( IXWEBSOCKET_SOURCES
|
|
||||||
ixwebsocket/IXSocket.cpp
|
|
||||||
ixwebsocket/IXSocketServer.cpp
|
|
||||||
ixwebsocket/IXSocketConnect.cpp
|
|
||||||
ixwebsocket/IXSocketFactory.cpp
|
|
||||||
ixwebsocket/IXDNSLookup.cpp
|
|
||||||
ixwebsocket/IXCancellationRequest.cpp
|
|
||||||
ixwebsocket/IXNetSystem.cpp
|
|
||||||
ixwebsocket/IXWebSocket.cpp
|
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
|
||||||
ixwebsocket/IXWebSocketHandshake.cpp
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
|
||||||
ixwebsocket/IXHttpClient.cpp
|
|
||||||
ixwebsocket/IXUrlParser.cpp
|
|
||||||
ixwebsocket/LUrlParser.cpp
|
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
|
||||||
ixwebsocket/IXConnectionState.cpp
|
|
||||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
|
||||||
ixwebsocket/IXWebSocketMessageQueue.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
|
||||||
ixwebsocket/IXSocket.h
|
|
||||||
ixwebsocket/IXSocketServer.h
|
|
||||||
ixwebsocket/IXSocketConnect.h
|
|
||||||
ixwebsocket/IXSocketFactory.h
|
|
||||||
ixwebsocket/IXSetThreadName.h
|
|
||||||
ixwebsocket/IXDNSLookup.h
|
|
||||||
ixwebsocket/IXCancellationRequest.h
|
|
||||||
ixwebsocket/IXNetSystem.h
|
|
||||||
ixwebsocket/IXProgressCallback.h
|
|
||||||
ixwebsocket/IXWebSocket.h
|
|
||||||
ixwebsocket/IXWebSocketServer.h
|
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
|
||||||
ixwebsocket/IXWebSocketHandshake.h
|
|
||||||
ixwebsocket/IXWebSocketSendInfo.h
|
|
||||||
ixwebsocket/IXWebSocketErrorInfo.h
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
|
||||||
ixwebsocket/libwshandshake.hpp
|
|
||||||
ixwebsocket/IXHttpClient.h
|
|
||||||
ixwebsocket/IXUrlParser.h
|
|
||||||
ixwebsocket/LUrlParser.h
|
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
|
||||||
ixwebsocket/IXSelectInterruptFactory.h
|
|
||||||
ixwebsocket/IXConnectionState.h
|
|
||||||
ixwebsocket/IXWebSocketCloseConstants.h
|
|
||||||
ixwebsocket/IXWebSocketMessageQueue.h
|
|
||||||
)
|
|
||||||
|
|
||||||
if (UNIX)
|
|
||||||
# Linux, Mac, iOS, Android
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.cpp )
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptPipe.h )
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Platform specific code
|
|
||||||
if (APPLE)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
|
|
||||||
elseif (WIN32)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
|
||||||
else()
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
set(USE_MBED_TLS TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(USE_OPEN_SSL FALSE)
|
|
||||||
if (USE_TLS)
|
|
||||||
add_definitions(-DIXWEBSOCKET_USE_TLS)
|
|
||||||
|
|
||||||
if (USE_MBED_TLS)
|
|
||||||
add_definitions(-DIXWEBSOCKET_USE_MBED_TLS)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketMbedTLS.h)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketMbedTLS.cpp)
|
|
||||||
elseif (APPLE)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketAppleSSL.h)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketAppleSSL.cpp)
|
|
||||||
elseif (WIN32)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketSChannel.h)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketSChannel.cpp)
|
|
||||||
else()
|
|
||||||
add_definitions(-DIXWEBSOCKET_USE_OPEN_SSL)
|
|
||||||
set(USE_OPEN_SSL TRUE)
|
|
||||||
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSocketOpenSSL.h)
|
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSocketOpenSSL.cpp)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library( ixwebsocket STATIC
|
|
||||||
${IXWEBSOCKET_SOURCES}
|
|
||||||
${IXWEBSOCKET_HEADERS}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
|
|
||||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_OPEN_SSL)
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
|
||||||
message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
|
|
||||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
|
||||||
target_link_libraries(ixwebsocket ${OPENSSL_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_MBED_TLS)
|
|
||||||
set (ENABLE_PROGRAMS OFF)
|
|
||||||
add_subdirectory(third_party/mbedtls)
|
|
||||||
include_directories(third_party/mbedtls/include)
|
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket mbedtls)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WIN32)
|
|
||||||
add_subdirectory(third_party/zlib)
|
|
||||||
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
|
||||||
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
|
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
|
||||||
|
|
||||||
else()
|
|
||||||
# gcc/Linux needs -pthread
|
|
||||||
find_package(Threads)
|
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket
|
|
||||||
z ${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
|
||||||
.
|
|
||||||
)
|
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
|
||||||
# Build with Multiple Processes
|
|
||||||
target_compile_options(ixwebsocket PRIVATE /MP)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
|
||||||
|
|
||||||
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")
|
|
||||||
|
|
||||||
install(TARGETS ixwebsocket
|
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib
|
|
||||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ixwebsocket/
|
|
||||||
)
|
|
||||||
|
|
||||||
if (USE_WS)
|
|
||||||
add_subdirectory(ws)
|
|
||||||
endif()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
2.2.1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
docker/Dockerfile.ubuntu_artful
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived
|
|
||||||
from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,441 +0,0 @@
|
|||||||
# General
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
|
|
||||||
|
|
||||||
* macOS
|
|
||||||
* iOS
|
|
||||||
* Linux
|
|
||||||
* Android
|
|
||||||
* Windows
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
|
|
||||||
|
|
||||||
Here is what the client API looks like.
|
|
||||||
|
|
||||||
```
|
|
||||||
ix::WebSocket webSocket;
|
|
||||||
|
|
||||||
std::string url("ws://localhost:8080/");
|
|
||||||
webSocket.setUrl(url);
|
|
||||||
|
|
||||||
// Optional heart beat, sent every 45 seconds when there is not any traffic
|
|
||||||
// to make sure that load balancers do not kill an idle connection.
|
|
||||||
webSocket.setHeartBeatPeriod(45);
|
|
||||||
|
|
||||||
// Setup a callback to be fired when a message or an event (open, close, error) is received
|
|
||||||
webSocket.setOnMessageCallback(
|
|
||||||
[](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
if (messageType == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
std::cout << str << std::endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now that our callback is setup, we can start our background thread and receive messages
|
|
||||||
webSocket.start();
|
|
||||||
|
|
||||||
// Send a message to the server (default to BINARY mode)
|
|
||||||
webSocket.send("hello world");
|
|
||||||
|
|
||||||
// The message can be sent in TEXT mode
|
|
||||||
webSocket.sendText("hello again");
|
|
||||||
|
|
||||||
// ... finally ...
|
|
||||||
|
|
||||||
// Stop the connection
|
|
||||||
webSocket.stop()
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is what the server API looks like. Note that server support is very recent and subject to changes.
|
|
||||||
|
|
||||||
```
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
|
||||||
[&server](std::shared_ptr<WebSocket> webSocket,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState)
|
|
||||||
{
|
|
||||||
webSocket->setOnMessageCallback(
|
|
||||||
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
if (messageType == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
std::cerr << "New connection" << std::endl;
|
|
||||||
|
|
||||||
// A connection state object is available, and has a default id
|
|
||||||
// You can subclass ConnectionState and pass an alternate factory
|
|
||||||
// to override it. It is useful if you want to store custom
|
|
||||||
// attributes per connection (authenticated bool flag, attributes, etc...)
|
|
||||||
std::cerr << "id: " << connectionState->getId() << std::endl;
|
|
||||||
|
|
||||||
// The uri the client did connect to.
|
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
|
||||||
|
|
||||||
std::cerr << "Headers:" << std::endl;
|
|
||||||
for (auto it : openInfo.headers)
|
|
||||||
{
|
|
||||||
std::cerr << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocketMessageType::Message)
|
|
||||||
{
|
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
|
||||||
webSocket->send(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
// Error handling
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the server in the background. Server can be stoped by calling server.stop()
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
// Block until server.stop() is called.
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is what the HTTP client API looks like. Note that HTTP client support is very recent and subject to changes.
|
|
||||||
|
|
||||||
```
|
|
||||||
//
|
|
||||||
// Preparation
|
|
||||||
//
|
|
||||||
HttpClient httpClient;
|
|
||||||
HttpRequestArgsPtr args = httpClient.createRequest();
|
|
||||||
|
|
||||||
// Custom headers can be set
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
headers["Foo"] = "bar";
|
|
||||||
args->extraHeaders = headers;
|
|
||||||
|
|
||||||
// Timeout options
|
|
||||||
args->connectTimeout = connectTimeout;
|
|
||||||
args->transferTimeout = transferTimeout;
|
|
||||||
|
|
||||||
// Redirect options
|
|
||||||
args->followRedirects = followRedirects;
|
|
||||||
args->maxRedirects = maxRedirects;
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
args->compress = compress; // Enable gzip compression
|
|
||||||
args->verbose = verbose;
|
|
||||||
args->logger = [](const std::string& msg)
|
|
||||||
{
|
|
||||||
std::cout << msg;
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Synchronous Request
|
|
||||||
//
|
|
||||||
HttpResponsePtr out;
|
|
||||||
std::string url = "https://www.google.com";
|
|
||||||
|
|
||||||
// HEAD request
|
|
||||||
out = httpClient.head(url, args);
|
|
||||||
|
|
||||||
// GET request
|
|
||||||
out = httpClient.get(url, args);
|
|
||||||
|
|
||||||
// POST request with parameters
|
|
||||||
HttpParameters httpParameters;
|
|
||||||
httpParameters["foo"] = "bar";
|
|
||||||
out = httpClient.post(url, httpParameters, args);
|
|
||||||
|
|
||||||
// POST request with a body
|
|
||||||
out = httpClient.post(url, std::string("foo=bar"), args);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Result
|
|
||||||
//
|
|
||||||
auto errorCode = response->errorCode; // Can be HttpErrorCode::Ok, HttpErrorCode::UrlMalformed, etc...
|
|
||||||
auto errorCode = response->errorCode; // 200, 404, etc...
|
|
||||||
auto responseHeaders = response->headers; // All the headers in a special case-insensitive unordered_map of (string, string)
|
|
||||||
auto payload = response->payload; // All the bytes from the response as an std::string
|
|
||||||
auto errorMsg = response->errorMsg; // Descriptive error message in case of failure
|
|
||||||
auto uploadSize = response->uploadSize; // Byte count of uploaded data
|
|
||||||
auto downloadSize = response->downloadSize; // Byte count of downloaded data
|
|
||||||
|
|
||||||
//
|
|
||||||
// Asynchronous Request
|
|
||||||
//
|
|
||||||
bool async = true;
|
|
||||||
HttpClient httpClient(async);
|
|
||||||
auto args = httpClient.createRequest(url, HttpClient::kGet);
|
|
||||||
|
|
||||||
// Push the request to a queue,
|
|
||||||
bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
|
||||||
{
|
|
||||||
// This callback execute in a background thread. Make sure you uses appropriate protection such as mutex
|
|
||||||
auto statusCode = response->statusCode; // acess results
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// ok will be false if your httpClient is not async
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
|
|
||||||
|
|
||||||
```
|
|
||||||
mkdir build # make a build dir so that you can build out of tree.
|
|
||||||
cd build
|
|
||||||
cmake ..
|
|
||||||
make -j
|
|
||||||
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
|
|
||||||
```
|
|
||||||
|
|
||||||
Headers and a static library will be installed to the target dir.
|
|
||||||
|
|
||||||
A [conan](https://conan.io/) file is available at [conan-IXWebSocket](https://github.com/Zinnion/conan-IXWebSocket).
|
|
||||||
|
|
||||||
There is a unittest which can be executed by typing `make test`.
|
|
||||||
|
|
||||||
There is a Dockerfile for running some code on Linux. To use docker-compose you must make a docker container first.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ make docker
|
|
||||||
...
|
|
||||||
$ docker compose up &
|
|
||||||
...
|
|
||||||
$ docker exec -it ixwebsocket_ws_1 bash
|
|
||||||
app@ca2340eb9106:~$ ws --help
|
|
||||||
ws is a websocket tool
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally you can build and install the `ws command line tool` with Homebrew. The homebrew version might be slightly out of date.
|
|
||||||
|
|
||||||
```
|
|
||||||
brew tap bsergean/IXWebSocket
|
|
||||||
brew install IXWebSocket
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation details
|
|
||||||
|
|
||||||
### Per Message Deflate compression.
|
|
||||||
|
|
||||||
The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.
|
|
||||||
|
|
||||||
### TLS/SSL
|
|
||||||
|
|
||||||
Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL is used on Android and Linux, mbedTLS is used on Windows.
|
|
||||||
|
|
||||||
### Polling and background thread work
|
|
||||||
|
|
||||||
No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select [system](http://man7.org/linux/man-pages/man2/select.2.html) call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section [select law](https://linux.die.net/man/2/select_tut). Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is [possible](https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid) since system socket implementations allow concurrent read/writes. However concurrent writes need to be protected with mutex.
|
|
||||||
|
|
||||||
### Automatic reconnection
|
|
||||||
|
|
||||||
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
|
|
||||||
|
|
||||||
### Large messages
|
|
||||||
|
|
||||||
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
* On Windows TLS is not setup yet to validate certificates.
|
|
||||||
* There is no convenient way to embed a ca cert.
|
|
||||||
* No utf-8 validation is made when sending TEXT message with sendText()
|
|
||||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
|
|
||||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
|
|
||||||
|
|
||||||
## C++ code organization
|
|
||||||
|
|
||||||
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
|
|
||||||
|
|
||||||
```
|
|
||||||
+-----------------------+ --- Public
|
|
||||||
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
|
||||||
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
|
||||||
| |
|
|
||||||
+-----------------------+
|
|
||||||
| |
|
|
||||||
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
|
||||||
| | Each connection is handled in a new OS thread.
|
|
||||||
| |
|
|
||||||
+-----------------------+ --- Private
|
|
||||||
| |
|
|
||||||
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
|
||||||
| |
|
|
||||||
+-----------------------+
|
|
||||||
| |
|
|
||||||
| IXWebSocketHandshake | Establish the connection between client and server.
|
|
||||||
| |
|
|
||||||
+-----------------------+
|
|
||||||
| |
|
|
||||||
| IXWebSocket | ws:// Unencrypted Socket handler
|
|
||||||
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
|
|
||||||
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
|
|
||||||
| | Can be used on macOS too.
|
|
||||||
+-----------------------+
|
|
||||||
| |
|
|
||||||
| IXSocketConnect | Connect to the remote host (client).
|
|
||||||
| |
|
|
||||||
+-----------------------+
|
|
||||||
| |
|
|
||||||
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
|
|
||||||
| |
|
|
||||||
+-----------------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
### Sending messages
|
|
||||||
|
|
||||||
`websocket.send("foo")` will send a message.
|
|
||||||
|
|
||||||
If the connection was closed and sending failed, the return value will be set to false.
|
|
||||||
|
|
||||||
### ReadyState
|
|
||||||
|
|
||||||
`getReadyState()` returns the state of the connection. There are 4 possible states.
|
|
||||||
|
|
||||||
1. ReadyState::Connecting - The connection is not yet open.
|
|
||||||
2. ReadyState::Open - The connection is open and ready to communicate.
|
|
||||||
3. ReadyState::Closing - The connection is in the process of closing.
|
|
||||||
4. ReadyState::Closed - The connection is closed or could not be opened.
|
|
||||||
|
|
||||||
### Open and Close notifications
|
|
||||||
|
|
||||||
The onMessage event will be fired when the connection is opened or closed. This is similar to the [Javascript browser API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), which has `open` and `close` events notification that can be registered with the browser `addEventListener`.
|
|
||||||
|
|
||||||
```
|
|
||||||
webSocket.setOnMessageCallback(
|
|
||||||
[](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
if (messageType == ix::WebSocketMessageType::Open)
|
|
||||||
{
|
|
||||||
std::cout << "send greetings" << std::endl;
|
|
||||||
|
|
||||||
// Headers can be inspected (pairs of string/string)
|
|
||||||
std::cout << "Handshake Headers:" << std::endl;
|
|
||||||
for (auto it : headers)
|
|
||||||
{
|
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocketMessageType::Close)
|
|
||||||
{
|
|
||||||
std::cout << "disconnected" << std::endl;
|
|
||||||
|
|
||||||
// The server can send an explicit code and reason for closing.
|
|
||||||
// This data can be accessed through the closeInfo object.
|
|
||||||
std::cout << closeInfo.code << std::endl;
|
|
||||||
std::cout << closeInfo.reason << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error notification
|
|
||||||
|
|
||||||
A message will be fired when there is an error with the connection. The message type will be `ix::WebSocketMessageType::Error`. Multiple fields will be available on the event to describe the error.
|
|
||||||
|
|
||||||
```
|
|
||||||
webSocket.setOnMessageCallback(
|
|
||||||
[](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
if (messageType == ix::WebSocketMessageType::Error)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Error: " << error.reason << std::endl;
|
|
||||||
ss << "#retries: " << event.retries << std::endl;
|
|
||||||
ss << "Wait time(ms): " << event.wait_time << std::endl;
|
|
||||||
ss << "HTTP Status: " << event.http_status << std::endl;
|
|
||||||
std::cout << ss.str() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### start, stop
|
|
||||||
|
|
||||||
1. `websocket.start()` connect to the remote server and starts the message receiving background thread.
|
|
||||||
2. `websocket.stop()` disconnect from the remote server and closes the background thread.
|
|
||||||
|
|
||||||
### Configuring the remote url
|
|
||||||
|
|
||||||
The url can be set and queried after a websocket object has been created. You will have to call `stop` and `start` if you want to disconnect and connect to that new url.
|
|
||||||
|
|
||||||
```
|
|
||||||
std::string url("wss://example.com");
|
|
||||||
websocket.configure(url);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ping/Pong support
|
|
||||||
|
|
||||||
Ping/pong messages are used to implement keep-alive. 2 message types exists to identify ping and pong messages. Note that when a ping message is received, a pong is instantly send back as requested by the WebSocket spec.
|
|
||||||
|
|
||||||
```
|
|
||||||
webSocket.setOnMessageCallback(
|
|
||||||
[](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
if (messageType == ix::WebSocketMessageType::Ping ||
|
|
||||||
messageType == ix::WebSocketMessageType::Pong)
|
|
||||||
{
|
|
||||||
std::cout << "pong data: " << str << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
A ping message can be sent to the server, with an optional data string.
|
|
||||||
|
|
||||||
```
|
|
||||||
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Heartbeat.
|
|
||||||
|
|
||||||
You can configure an optional heart beat / keep-alive, sent every 45 seconds
|
|
||||||
when there is no any traffic to make sure that load balancers do not kill an
|
|
||||||
idle connection.
|
|
||||||
|
|
||||||
```
|
|
||||||
webSocket.setHeartBeatPeriod(45);
|
|
||||||
```
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
image:
|
|
||||||
- Visual Studio 2017
|
|
||||||
|
|
||||||
install:
|
|
||||||
- ls -al
|
|
||||||
- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
|
||||||
- cd test
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake -G"NMake Makefiles" ..
|
|
||||||
- nmake
|
|
||||||
- ixwebsocket_unittest.exe
|
|
||||||
|
|
||||||
build: off
|
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="../img/favicon.ico">
|
||||||
|
<title>Build - IXWebSocket</title>
|
||||||
|
<link href="../css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="../css/base.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="..">IXWebSocket</a>
|
||||||
|
<!-- Expander button -->
|
||||||
|
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-collapse">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Expanded navigation -->
|
||||||
|
<div id="navbar-collapse" class="navbar-collapse collapse">
|
||||||
|
<!-- Main navigation -->
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="navitem">
|
||||||
|
<a href=".." class="nav-link">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../CHANGELOG/" class="nav-link">Changelog</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem active">
|
||||||
|
<a href="./" class="nav-link">Build</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../design/" class="nav-link">Design</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../packages/" class="nav-link">Packages</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../performance/" class="nav-link">Performance</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../usage/" class="nav-link">Examples</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../ws/" class="nav-link">Ws</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#" class="nav-link" data-toggle="modal" data-target="#mkdocs_search_modal">
|
||||||
|
<i class="fa fa-search"></i> Search
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="prev" href="../CHANGELOG/" class="nav-link">
|
||||||
|
<i class="fa fa-arrow-left"></i> Previous
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="next" href="../design/" class="nav-link">
|
||||||
|
Next <i class="fa fa-arrow-right"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"><div class="navbar-light navbar-expand-md bs-sidebar hidden-print affix" role="complementary">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#toc-collapse" title="Table of Contents">
|
||||||
|
<span class="fa fa-angle-down"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="toc-collapse" class="navbar-collapse collapse card bg-secondary">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#build" class="nav-link">Build</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
<div class="col-md-9" role="main">
|
||||||
|
|
||||||
|
<h2 id="build">Build</h2>
|
||||||
|
<h3 id="cmake">CMake</h3>
|
||||||
|
<p>CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.</p>
|
||||||
|
<pre><code>mkdir build # make a build dir so that you can build out of tree.
|
||||||
|
cd build
|
||||||
|
cmake -DUSE_TLS=1 ..
|
||||||
|
make -j
|
||||||
|
make install # will install to /usr/local on Unix, on macOS it is a good idea to sudo chown -R `whoami`:staff /usr/local
|
||||||
|
</code></pre>
|
||||||
|
<p>Headers and a static library will be installed to the target dir.
|
||||||
|
There is a unittest which can be executed by typing <code>make test</code>.</p>
|
||||||
|
<p>Options for building:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>-DBUILD_SHARED_LIBS=ON</code> will build the unittest as a shared libary instead of a static library, which is the default</li>
|
||||||
|
<li><code>-DUSE_ZLIB=1</code> will enable zlib support, required for http client + server + websocket per message deflate extension</li>
|
||||||
|
<li><code>-DUSE_TLS=1</code> will enable TLS support</li>
|
||||||
|
<li><code>-DUSE_OPEN_SSL=1</code> will use <a href="https://www.openssl.org/">openssl</a> for the TLS support (default on Linux and Windows). When using a custom version of openssl (say a prebuilt version, odd runtime problems can happens, as in #319, and special cmake trickery will be required (see this <a href="https://github.com/machinezone/IXWebSocket/issues/175#issuecomment-620231032">comment</a>)</li>
|
||||||
|
<li><code>-DUSE_MBED_TLS=1</code> will use <a href="https://tls.mbed.org/">mbedlts</a> for the TLS support</li>
|
||||||
|
<li><code>-DUSE_WS=1</code> will build the ws interactive command line tool</li>
|
||||||
|
<li><code>-DUSE_TEST=1</code> will build the unittest</li>
|
||||||
|
</ul>
|
||||||
|
<p>If you are on Windows, look at the <a href="https://github.com/machinezone/IXWebSocket/blob/master/appveyor.yml">appveyor</a> file (not maintained much though) or rather the <a href="https://github.com/machinezone/IXWebSocket/blob/master/.github/workflows/unittest_windows.yml">github actions</a> which have instructions for building dependencies.</p>
|
||||||
|
<p>It is also possible to externally include the project, so that everything is fetched over the wire when you build like so:</p>
|
||||||
|
<pre><code> ExternalProject_Add(
|
||||||
|
IXWebSocket
|
||||||
|
GIT_REPOSITORY https://github.com/machinezone/IXWebSocket.git
|
||||||
|
...
|
||||||
|
)
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="vcpkg">vcpkg</h3>
|
||||||
|
<p>It is possible to get IXWebSocket through Microsoft <a href="https://github.com/microsoft/vcpkg">vcpkg</a>.</p>
|
||||||
|
<pre><code>vcpkg install ixwebsocket
|
||||||
|
</code></pre>
|
||||||
|
<p>To use the installed package within a cmake project, use the following:</p>
|
||||||
|
<pre><code class="language-cmake"> set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "") # this is super important in order for cmake to include the vcpkg search/lib paths!
|
||||||
|
|
||||||
|
# find library and its headers
|
||||||
|
find_path(IXWEBSOCKET_INCLUDE_DIR ixwebsocket/IXWebSocket.h)
|
||||||
|
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
||||||
|
# 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 NOTFOUND
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
<h3 id="conan">Conan</h3>
|
||||||
|
<p><a href="https://bintray.com/conan/conan-center/ixwebsocket%3A_/_latestVersion"> <img alt="Download" src="https://api.bintray.com/packages/conan/conan-center/ixwebsocket%3A_/images/download.svg" /> </a></p>
|
||||||
|
<p>Conan is currently supported through a recipe in <a href="https://github.com/conan-io/conan-center-index/tree/master/recipes/ixwebsocket">Conan Center</a> (<a href="https://bintray.com/conan/conan-center/ixwebsocket%3A_">Bintray entry</a>).</p>
|
||||||
|
<p>Package reference</p>
|
||||||
|
<ul>
|
||||||
|
<li>Conan 1.21.0 and up: <code>ixwebsocket/7.9.2</code></li>
|
||||||
|
<li>Earlier versions: <code>ixwebsocket/7.9.2@_/_</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Note that the version listed here might not be the latest one. See Bintray or the recipe itself for the latest version. If you're migrating from the previous, custom Bintray remote, note that the package reference <em>has</em> to be lower-case.</p>
|
||||||
|
<h3 id="docker">Docker</h3>
|
||||||
|
<p>There is a Dockerfile for running the unittest on Linux, and to run the <code>ws</code> tool. It is also available on the docker registry.</p>
|
||||||
|
<pre><code>docker run docker.pkg.github.com/machinezone/ixwebsocket/ws:latest --help
|
||||||
|
</code></pre>
|
||||||
|
<p>To use docker-compose you must make a docker container first.</p>
|
||||||
|
<pre><code>$ make docker
|
||||||
|
...
|
||||||
|
$ docker compose up &
|
||||||
|
...
|
||||||
|
$ docker exec -it ixwebsocket_ws_1 bash
|
||||||
|
app@ca2340eb9106:~$ ws --help
|
||||||
|
ws is a websocket tool
|
||||||
|
...
|
||||||
|
</code></pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
<p>Documentation built with <a href="https://www.mkdocs.org/">MkDocs</a>.</p>
|
||||||
|
</footer>
|
||||||
|
<script src="../js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var base_url = "..",
|
||||||
|
shortcuts = {"help": 191, "next": 78, "previous": 80, "search": 83};
|
||||||
|
</script>
|
||||||
|
<script src="../js/base.js"></script>
|
||||||
|
<script src="../search/main.js"></script>
|
||||||
|
|
||||||
|
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="searchModalLabel">Search</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>From here you can search these documents. Enter your search terms below.</p>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="search" class="form-control" placeholder="Search..." id="mkdocs-search-query" title="Type search term here">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="mkdocs-search-results" data-no-results-text="No results found"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><div class="modal" id="mkdocs_keyboard_modal" tabindex="-1" role="dialog" aria-labelledby="keyboardModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="keyboardModalLabel">Keyboard Shortcuts</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Keys</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="help shortcut"><kbd>?</kbd></td>
|
||||||
|
<td>Open this help</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="next shortcut"><kbd>n</kbd></td>
|
||||||
|
<td>Next page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="prev shortcut"><kbd>p</kbd></td>
|
||||||
|
<td>Previous page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="search shortcut"><kbd>s</kbd></td>
|
||||||
|
<td>Search</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+325
@@ -0,0 +1,325 @@
|
|||||||
|
html {
|
||||||
|
/* csslint ignore:start */
|
||||||
|
/* The nav header is 3.5rem high, plus 20px for the margin-top of the
|
||||||
|
main container. */
|
||||||
|
scroll-padding-top: calc(3.5rem + 20px);
|
||||||
|
/* csslint ignore:end */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replacement for `body { background-attachment: fixed; }`, which has
|
||||||
|
performance issues when scrolling on large displays. See #1394. */
|
||||||
|
body::before {
|
||||||
|
content: ' ';
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
background: url(../img/grid.png) repeat-x;
|
||||||
|
will-change: transform;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > .container {
|
||||||
|
margin-top: 20px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar.fixed-top { /* csslint allow: adjoining-classes */
|
||||||
|
/* csslint ignore:start */
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
/* csslint ignore:end */
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-links {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-md-9 img {
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px;
|
||||||
|
line-height: 1.428571429;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px auto 30px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #444;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3, h4, h5, h6 {
|
||||||
|
color: #444;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, .rst-content tt {
|
||||||
|
max-width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: solid 1px #e1e4e5;
|
||||||
|
color: #333;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.code-large, .rst-content tt.code-large {
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 2px 5px;
|
||||||
|
background: #fff;
|
||||||
|
border: solid 1px #e1e4e5;
|
||||||
|
color: #333;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
display: block;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 90%;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
|
||||||
|
box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
a code {
|
||||||
|
color: #2FA4E7;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover code, a:focus code {
|
||||||
|
color: #157AB5;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Side navigation
|
||||||
|
*
|
||||||
|
* Scrollspy and affixed enhanced navigation to highlight sections and secondary
|
||||||
|
* sections of docs content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.bs-sidebar.affix { /* csslint allow: adjoining-classes */
|
||||||
|
/* csslint ignore:start */
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
/* csslint ignore:end */
|
||||||
|
/* The nav header is 3.5rem high, plus 20px for the margin-top of the
|
||||||
|
main container. */
|
||||||
|
top: calc(3.5rem + 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-sidebar.card { /* csslint allow: adjoining-classes */
|
||||||
|
padding: 0;
|
||||||
|
max-height: 90%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle (vertically flip) sidebar collapse icon */
|
||||||
|
.bs-sidebar .navbar-toggler span {
|
||||||
|
-moz-transform: scale(1, -1);
|
||||||
|
-webkit-transform: scale(1, -1);
|
||||||
|
-o-transform: scale(1, -1);
|
||||||
|
-ms-transform: scale(1, -1);
|
||||||
|
transform: scale(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-sidebar .navbar-toggler.collapsed span { /* csslint allow: adjoining-classes */
|
||||||
|
-moz-transform: scale(1, 1);
|
||||||
|
-webkit-transform: scale(1, 1);
|
||||||
|
-o-transform: scale(1, 1);
|
||||||
|
-ms-transform: scale(1, 1);
|
||||||
|
transform: scale(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First level of nav */
|
||||||
|
.bs-sidebar > .navbar-collapse > .nav {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All levels of nav */
|
||||||
|
.bs-sidebar .nav > li > a {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 20px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.bs-sidebar .nav > li > a:hover,
|
||||||
|
.bs-sidebar .nav > li > a:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
border-right: 1px solid;
|
||||||
|
}
|
||||||
|
.bs-sidebar .nav > li > a.active,
|
||||||
|
.bs-sidebar .nav > li > a.active:hover,
|
||||||
|
.bs-sidebar .nav > li > a.active:focus {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: transparent;
|
||||||
|
border-right: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-sidebar .nav .nav .nav {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-sidebar .nav > li > a {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-sidebar .nav .nav > li > a {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerlink {
|
||||||
|
font-family: FontAwesome;
|
||||||
|
font-size: 14px;
|
||||||
|
display: none;
|
||||||
|
padding-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:hover .headerlink, h2:hover .headerlink, h3:hover .headerlink, h4:hover .headerlink, h5:hover .headerlink, h6:hover .headerlink {
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition, details {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition.note, details.note { /* csslint allow: adjoining-classes */
|
||||||
|
color: #2e6b89;
|
||||||
|
background-color: #e2f0f7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition.warning, details.warning { /* csslint allow: adjoining-classes */
|
||||||
|
color: #7a6032;
|
||||||
|
background-color: #fffae5;
|
||||||
|
border-color: #fbeed5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition.danger, details.danger { /* csslint allow: adjoining-classes */
|
||||||
|
color: #7f3130;
|
||||||
|
background-color: #fde3e3;
|
||||||
|
border-color: #eed3d7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition-title, summary {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonition>p:last-child, details>p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.navbar-collapse.show { /* csslint allow: adjoining-classes */
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 3.5rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.open { /* csslint allow: adjoining-classes */
|
||||||
|
color: #fff;
|
||||||
|
background-color: #2FA4E7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu > .dropdown-menu {
|
||||||
|
margin: 0 0 0 1.5rem;
|
||||||
|
padding: 0;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu > a::after {
|
||||||
|
display: block;
|
||||||
|
content: " ";
|
||||||
|
float: right;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-color: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 0 5px 5px;
|
||||||
|
border-left-color: #ccc;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-right: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu:hover > a::after {
|
||||||
|
border-left-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.dropdown-menu {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 3.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu > .dropdown-menu {
|
||||||
|
/* csslint ignore:start */
|
||||||
|
position: fixed !important;
|
||||||
|
/* csslint ignore:end */
|
||||||
|
margin-top: -9px;
|
||||||
|
margin-left: -2px;
|
||||||
|
border-width: 1px;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu.pull-left { /* csslint allow: adjoining-classes */
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-submenu.pull-left > .dropdown-menu { /* csslint allow: adjoining-classes */
|
||||||
|
left: -100%;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Remove sidebar when print */
|
||||||
|
.col-md-3 { display: none; }
|
||||||
|
}
|
||||||
Vendored
+12
File diff suppressed because one or more lines are too long
Vendored
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,247 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="../img/favicon.ico">
|
||||||
|
<title>Design - IXWebSocket</title>
|
||||||
|
<link href="../css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="../css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="../css/base.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="..">IXWebSocket</a>
|
||||||
|
<!-- Expander button -->
|
||||||
|
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-collapse">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Expanded navigation -->
|
||||||
|
<div id="navbar-collapse" class="navbar-collapse collapse">
|
||||||
|
<!-- Main navigation -->
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="navitem">
|
||||||
|
<a href=".." class="nav-link">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../CHANGELOG/" class="nav-link">Changelog</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../build/" class="nav-link">Build</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem active">
|
||||||
|
<a href="./" class="nav-link">Design</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../packages/" class="nav-link">Packages</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../performance/" class="nav-link">Performance</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../usage/" class="nav-link">Examples</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="../ws/" class="nav-link">Ws</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#" class="nav-link" data-toggle="modal" data-target="#mkdocs_search_modal">
|
||||||
|
<i class="fa fa-search"></i> Search
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="prev" href="../build/" class="nav-link">
|
||||||
|
<i class="fa fa-arrow-left"></i> Previous
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="next" href="../packages/" class="nav-link">
|
||||||
|
Next <i class="fa fa-arrow-right"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"><div class="navbar-light navbar-expand-md bs-sidebar hidden-print affix" role="complementary">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#toc-collapse" title="Table of Contents">
|
||||||
|
<span class="fa fa-angle-down"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="toc-collapse" class="navbar-collapse collapse card bg-secondary">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#implementation-details" class="nav-link">Implementation details</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#limitations" class="nav-link">Limitations</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#c-code-organization" class="nav-link">C++ code organization</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
<div class="col-md-9" role="main">
|
||||||
|
|
||||||
|
<h2 id="implementation-details">Implementation details</h2>
|
||||||
|
<h3 id="per-message-deflate-compression">Per Message Deflate compression.</h3>
|
||||||
|
<p>The per message deflate compression option is supported. It can lead to very nice bandbwith savings (20x !) if your messages are similar, which is often the case for example for chat applications. All features of the spec should be supported.</p>
|
||||||
|
<h3 id="tlsssl">TLS/SSL</h3>
|
||||||
|
<p>Connections can be optionally secured and encrypted with TLS/SSL when using a wss:// endpoint, or using normal un-encrypted socket with ws:// endpoints. AppleSSL is used on iOS and macOS, OpenSSL and mbedTLS can be used on Android, Linux and Windows.</p>
|
||||||
|
<p>If you are using OpenSSL, try to be on a version higher than 1.1.x as there there are thread safety problems with 1.0.x.</p>
|
||||||
|
<h3 id="polling-and-background-thread-work">Polling and background thread work</h3>
|
||||||
|
<p>No manual polling to fetch data is required. Data is sent and received instantly by using a background thread for receiving data and the select <a href="http://man7.org/linux/man-pages/man2/select.2.html">system</a> call to be notified by the OS of incoming data. No timeout is used for select so that the background thread is only woken up when data is available, to optimize battery life. This is also the recommended way of using select according to the select tutorial, section <a href="https://linux.die.net/man/2/select_tut">select law</a>. Read and Writes to the socket are non blocking. Data is sent right away and not enqueued by writing directly to the socket, which is <a href="https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid">possible</a> since system socket implementations allow concurrent read/writes.</p>
|
||||||
|
<h3 id="automatic-reconnection">Automatic reconnection</h3>
|
||||||
|
<p>If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds. This behavior can be disabled.</p>
|
||||||
|
<h3 id="large-messages">Large messages</h3>
|
||||||
|
<p>Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmentation</a>. Messages up to 1G were sent and received succesfully.</p>
|
||||||
|
<h3 id="testing">Testing</h3>
|
||||||
|
<p>The library has an interactive tool which is handy for testing compatibility ith other libraries. We have tested our client against Python, Erlang, Node.js, and C++ websocket server libraries.</p>
|
||||||
|
<p>The unittest tries to be comprehensive, and has been running on multiple platforms, with different sanitizers such as a thread sanitizer to catch data races or the undefined behavior sanitizer.</p>
|
||||||
|
<p>The regression test is running after each commit on github actions for multiple configurations.</p>
|
||||||
|
<h2 id="limitations">Limitations</h2>
|
||||||
|
<ul>
|
||||||
|
<li>On some configuration (mostly Android) certificate validation needs to be setup so that SocketTLSOptions.caFile point to a pem file, such as the one distributed by Firefox. Unless that setup is done connecting to a wss endpoint will display an error. With mbedtls the message will contain <code>error in handshake : X509 - Certificate verification failed, e.g. CRL, CA or signature check failed</code>.</li>
|
||||||
|
<li>Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using <code>recv</code> and <code>send</code> error codes. <a href="https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program">Here</a> is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.</li>
|
||||||
|
<li>The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="c-code-organization">C++ code organization</h2>
|
||||||
|
<p>Here is a simplistic diagram which explains how the code is structured in term of class/modules.</p>
|
||||||
|
<pre><code>+-----------------------+ --- Public
|
||||||
|
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
||||||
|
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
||||||
|
| |
|
||||||
|
+-----------------------+
|
||||||
|
| |
|
||||||
|
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
||||||
|
| | Each connection is handled in a new OS thread.
|
||||||
|
| |
|
||||||
|
+-----------------------+ --- Private
|
||||||
|
| |
|
||||||
|
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
||||||
|
| |
|
||||||
|
+-----------------------+
|
||||||
|
| |
|
||||||
|
| IXWebSocketHandshake | Establish the connection between client and server.
|
||||||
|
| |
|
||||||
|
+-----------------------+
|
||||||
|
| |
|
||||||
|
| IXWebSocket | ws:// Unencrypted Socket handler
|
||||||
|
| IXWebSocketAppleSSL | wss:// TLS encrypted Socket AppleSSL handler. Used on iOS and macOS
|
||||||
|
| IXWebSocketOpenSSL | wss:// TLS encrypted Socket OpenSSL handler. Used on Android and Linux
|
||||||
|
| | Can be used on macOS too.
|
||||||
|
+-----------------------+
|
||||||
|
| |
|
||||||
|
| IXSocketConnect | Connect to the remote host (client).
|
||||||
|
| |
|
||||||
|
+-----------------------+
|
||||||
|
| |
|
||||||
|
| IXDNSLookup | Does DNS resolution asynchronously so that it can be interrupted.
|
||||||
|
| |
|
||||||
|
+-----------------------+
|
||||||
|
</code></pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
<p>Documentation built with <a href="https://www.mkdocs.org/">MkDocs</a>.</p>
|
||||||
|
</footer>
|
||||||
|
<script src="../js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var base_url = "..",
|
||||||
|
shortcuts = {"help": 191, "next": 78, "previous": 80, "search": 83};
|
||||||
|
</script>
|
||||||
|
<script src="../js/base.js"></script>
|
||||||
|
<script src="../search/main.js"></script>
|
||||||
|
|
||||||
|
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="searchModalLabel">Search</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>From here you can search these documents. Enter your search terms below.</p>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="search" class="form-control" placeholder="Search..." id="mkdocs-search-query" title="Type search term here">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="mkdocs-search-results" data-no-results-text="No results found"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><div class="modal" id="mkdocs_keyboard_modal" tabindex="-1" role="dialog" aria-labelledby="keyboardModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="keyboardModalLabel">Keyboard Shortcuts</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Keys</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="help shortcut"><kbd>?</kbd></td>
|
||||||
|
<td>Open this help</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="next shortcut"><kbd>n</kbd></td>
|
||||||
|
<td>Next page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="prev shortcut"><kbd>p</kbd></td>
|
||||||
|
<td>Previous page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="search shortcut"><kbd>s</kbd></td>
|
||||||
|
<td>Search</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
|
||||||
snake:
|
|
||||||
image: bsergean/ws:build
|
|
||||||
entrypoint: ws snake --port 8765 --host 0.0.0.0 --redis_hosts redis1
|
|
||||||
ports:
|
|
||||||
- "8765:8765"
|
|
||||||
networks:
|
|
||||||
- ws-net
|
|
||||||
depends_on:
|
|
||||||
- redis1
|
|
||||||
|
|
||||||
ws:
|
|
||||||
security_opt:
|
|
||||||
- seccomp:unconfined
|
|
||||||
cap_add:
|
|
||||||
- SYS_PTRACE
|
|
||||||
stdin_open: true
|
|
||||||
tty: true
|
|
||||||
image: bsergean/ws:build
|
|
||||||
entrypoint: bash
|
|
||||||
networks:
|
|
||||||
- ws-net
|
|
||||||
depends_on:
|
|
||||||
- redis1
|
|
||||||
|
|
||||||
redis1:
|
|
||||||
image: redis:alpine
|
|
||||||
networks:
|
|
||||||
- ws-net
|
|
||||||
|
|
||||||
statsd:
|
|
||||||
image: jaconel/statsd
|
|
||||||
ports:
|
|
||||||
- "8125:8125"
|
|
||||||
environment:
|
|
||||||
- STATSD_DUMP_MSG=true
|
|
||||||
- GRAPHITE_HOST=127.0.0.1
|
|
||||||
networks:
|
|
||||||
- ws-net
|
|
||||||
|
|
||||||
networks:
|
|
||||||
ws-net:
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
FROM alpine as build
|
|
||||||
|
|
||||||
RUN apk add --no-cache gcc g++ musl-dev linux-headers cmake openssl-dev
|
|
||||||
RUN apk add --no-cache make
|
|
||||||
RUN apk add --no-cache zlib-dev
|
|
||||||
|
|
||||||
RUN addgroup -S app && adduser -S -G app app
|
|
||||||
RUN chown -R app:app /opt
|
|
||||||
RUN chown -R app:app /usr/local
|
|
||||||
|
|
||||||
# There is a bug in CMake where we cannot build from the root top folder
|
|
||||||
# So we build from /opt
|
|
||||||
COPY --chown=app:app . /opt
|
|
||||||
WORKDIR /opt
|
|
||||||
|
|
||||||
USER app
|
|
||||||
RUN [ "make" ]
|
|
||||||
|
|
||||||
FROM alpine as runtime
|
|
||||||
|
|
||||||
RUN apk add --no-cache libstdc++
|
|
||||||
|
|
||||||
RUN addgroup -S app && adduser -S -G app app
|
|
||||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
|
||||||
RUN chmod +x /usr/local/bin/ws
|
|
||||||
RUN ldd /usr/local/bin/ws
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM debian:buster as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install wget
|
|
||||||
RUN mkdir -p /tmp/cmake
|
|
||||||
WORKDIR /tmp/cmake
|
|
||||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
|
|
||||||
RUN apt-get -y install g++
|
|
||||||
RUN apt-get -y install libssl-dev
|
|
||||||
RUN apt-get -y install libz-dev
|
|
||||||
RUN apt-get -y install make
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
RUN ["make"]
|
|
||||||
|
|
||||||
# Runtime
|
|
||||||
FROM debian:buster as runtime
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
# Runtime
|
|
||||||
RUN apt-get install -y libssl1.1
|
|
||||||
RUN apt-get install -y ca-certificates
|
|
||||||
RUN ["update-ca-certificates"]
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
RUN apt-get install -y strace
|
|
||||||
RUN apt-get install -y procps
|
|
||||||
RUN apt-get install -y htop
|
|
||||||
|
|
||||||
RUN adduser --disabled-password --gecos '' app
|
|
||||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
|
||||||
RUN chmod +x /usr/local/bin/ws
|
|
||||||
RUN ldd /usr/local/bin/ws
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
COPY --chown=app:app ws/snake/appsConfig.json .
|
|
||||||
COPY --chown=app:app ws/cobraMetricsSample.json .
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
FROM fedora:30 as build
|
|
||||||
|
|
||||||
RUN yum install -y gcc-g++
|
|
||||||
RUN yum install -y cmake
|
|
||||||
RUN yum install -y make
|
|
||||||
RUN yum install -y openssl-devel
|
|
||||||
|
|
||||||
RUN yum install -y wget
|
|
||||||
RUN mkdir -p /tmp/cmake
|
|
||||||
WORKDIR /tmp/cmake
|
|
||||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
RUN yum install -y python
|
|
||||||
RUN yum install -y libtsan
|
|
||||||
RUN yum install -y zlib-devel
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
# RUN ["make", "test"]
|
|
||||||
RUN ["make"]
|
|
||||||
|
|
||||||
# Runtime
|
|
||||||
FROM fedora:30 as runtime
|
|
||||||
|
|
||||||
RUN yum install -y libtsan
|
|
||||||
|
|
||||||
RUN groupadd app && useradd -g app app
|
|
||||||
COPY --chown=app:app --from=build /usr/local/bin/ws /usr/local/bin/ws
|
|
||||||
RUN chmod +x /usr/local/bin/ws
|
|
||||||
RUN ldd /usr/local/bin/ws
|
|
||||||
|
|
||||||
# Now run in usermode
|
|
||||||
USER app
|
|
||||||
WORKDIR /home/app
|
|
||||||
|
|
||||||
COPY --chown=app:app ws/snake/appsConfig.json .
|
|
||||||
COPY --chown=app:app ws/cobraMetricsSample.json .
|
|
||||||
|
|
||||||
ENTRYPOINT ["ws"]
|
|
||||||
CMD ["--help"]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM ubuntu:bionic as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install wget
|
|
||||||
RUN mkdir -p /tmp/cmake
|
|
||||||
WORKDIR /tmp/cmake
|
|
||||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
|
|
||||||
RUN apt-get -y install g++
|
|
||||||
RUN apt-get -y install libssl-dev
|
|
||||||
RUN apt-get -y install libz-dev
|
|
||||||
RUN apt-get -y install make
|
|
||||||
RUN apt-get -y install python
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
RUN ["make", "ws"]
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Build time
|
|
||||||
FROM ubuntu:xenial as build
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install wget
|
|
||||||
RUN mkdir -p /tmp/cmake
|
|
||||||
WORKDIR /tmp/cmake
|
|
||||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
|
|
||||||
|
|
||||||
RUN apt-get -y install g++
|
|
||||||
RUN apt-get -y install libssl-dev
|
|
||||||
RUN apt-get -y install libz-dev
|
|
||||||
RUN apt-get -y install make
|
|
||||||
RUN apt-get -y install python
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
|
||||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
|
||||||
|
|
||||||
# RUN ["make"]
|
|
||||||
RUN ["make", "test"]
|
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
+375
@@ -0,0 +1,375 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="None">
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="img/favicon.ico">
|
||||||
|
<title>IXWebSocket</title>
|
||||||
|
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
<link href="css/base.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="homepage">
|
||||||
|
<div class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href=".">IXWebSocket</a>
|
||||||
|
<!-- Expander button -->
|
||||||
|
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#navbar-collapse">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Expanded navigation -->
|
||||||
|
<div id="navbar-collapse" class="navbar-collapse collapse">
|
||||||
|
<!-- Main navigation -->
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="navitem active">
|
||||||
|
<a href="." class="nav-link">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="CHANGELOG/" class="nav-link">Changelog</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="build/" class="nav-link">Build</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="design/" class="nav-link">Design</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="packages/" class="nav-link">Packages</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="performance/" class="nav-link">Performance</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="usage/" class="nav-link">Examples</a>
|
||||||
|
</li>
|
||||||
|
<li class="navitem">
|
||||||
|
<a href="ws/" class="nav-link">Ws</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#" class="nav-link" data-toggle="modal" data-target="#mkdocs_search_modal">
|
||||||
|
<i class="fa fa-search"></i> Search
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="prev" class="nav-link disabled">
|
||||||
|
<i class="fa fa-arrow-left"></i> Previous
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a rel="next" href="CHANGELOG/" class="nav-link">
|
||||||
|
Next <i class="fa fa-arrow-right"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"><div class="navbar-light navbar-expand-md bs-sidebar hidden-print affix" role="complementary">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#toc-collapse" title="Table of Contents">
|
||||||
|
<span class="fa fa-angle-down"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="toc-collapse" class="navbar-collapse collapse card bg-secondary">
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#hello-world" class="nav-link">Hello world</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#users" class="nav-link">Users</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#alternative-libraries" class="nav-link">Alternative libraries</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item" data-level="2"><a href="#continuous-integration" class="nav-link">Continuous Integration</a>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
<div class="col-md-9" role="main">
|
||||||
|
|
||||||
|
<h2 id="hello-world">Hello world</h2>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <a href="PR">https://github.com/machinezone/IXWebSocket/pull/250</a>.</p>
|
||||||
|
<pre><code class="language-cpp">/*
|
||||||
|
* main.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2020 Machine Zone, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Super simple standalone example. See ws folder, unittest and doc/usage.md for more.
|
||||||
|
*
|
||||||
|
* On macOS
|
||||||
|
* $ mkdir -p build ; (cd build ; cmake -DUSE_TLS=1 .. ; make -j ; make install)
|
||||||
|
* $ clang++ --std=c++11 --stdlib=libc++ main.cpp -lixwebsocket -lz -framework Security -framework Foundation
|
||||||
|
* $ ./a.out
|
||||||
|
*
|
||||||
|
* Or use cmake -DBUILD_DEMO=ON option for other platforms
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXUserAgent.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// Required on Windows
|
||||||
|
ix::initNetSystem();
|
||||||
|
|
||||||
|
// Our websocket object
|
||||||
|
ix::WebSocket webSocket;
|
||||||
|
|
||||||
|
// Connect to a server with encryption
|
||||||
|
// See https://machinezone.github.io/IXWebSocket/usage/#tls-support-and-configuration
|
||||||
|
std::string url("wss://echo.websocket.org");
|
||||||
|
webSocket.setUrl(url);
|
||||||
|
|
||||||
|
std::cout << "Connecting to " << url << "..." << std::endl;
|
||||||
|
|
||||||
|
// Setup a callback to be fired (in a background thread, watch out for race conditions !)
|
||||||
|
// when a message or an event (open, close, error) is received
|
||||||
|
webSocket.setOnMessageCallback([](const ix::WebSocketMessagePtr& msg)
|
||||||
|
{
|
||||||
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
|
{
|
||||||
|
std::cout << "received message: " << msg->str << std::endl;
|
||||||
|
std::cout << "> " << std::flush;
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
|
{
|
||||||
|
std::cout << "Connection established" << std::endl;
|
||||||
|
std::cout << "> " << std::flush;
|
||||||
|
}
|
||||||
|
else if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
|
{
|
||||||
|
// Maybe SSL is not configured properly
|
||||||
|
std::cout << "Connection error: " << msg->errorInfo.reason << std::endl;
|
||||||
|
std::cout << "> " << std::flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now that our callback is setup, we can start our background thread and receive messages
|
||||||
|
webSocket.start();
|
||||||
|
|
||||||
|
// Send a message to the server (default to TEXT mode)
|
||||||
|
webSocket.send("hello world");
|
||||||
|
|
||||||
|
// Display a prompt
|
||||||
|
std::cout << "> " << std::flush;
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
// Read text from the console and send messages in text mode.
|
||||||
|
// Exit with Ctrl-D on Unix or Ctrl-Z on Windows.
|
||||||
|
while (std::getline(std::cin, text))
|
||||||
|
{
|
||||||
|
webSocket.send(text);
|
||||||
|
std::cout << "> " << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
<p>Interested? Go read the <a href="https://machinezone.github.io/IXWebSocket/">docs</a>! If things don't work as expected, please create an issue on GitHub, or even better a pull request if you know how to fix your problem.</p>
|
||||||
|
<p>IXWebSocket is actively being developed, check out the <a href="https://machinezone.github.io/IXWebSocket/CHANGELOG/">changelog</a> to know what's cooking. If you are looking for a real time messaging service (the chat-like 'server' your websocket code will talk to) with many features such as history, backed by Redis, look at <a href="https://github.com/machinezone/cobra">cobra</a>.</p>
|
||||||
|
<p>IXWebSocket client code is autobahn compliant beginning with the 6.0.0 version. See the current <a href="https://bsergean.github.io/autobahn/reports/clients/index.html">test results</a>. Some tests are still failing in the server code.</p>
|
||||||
|
<p>Starting with the 11.0.8 release, IXWebSocket should be fully C++11 compatible.</p>
|
||||||
|
<h2 id="users">Users</h2>
|
||||||
|
<p>If your company or project is using this library, feel free to open an issue or PR to amend this list.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://www.mz.com">Machine Zone</a></li>
|
||||||
|
<li><a href="https://gitlab.com/HCInk/tokio">Tokio</a>, a discord library focused on audio playback with node bindings.</li>
|
||||||
|
<li><a href="https://github.com/tostc/libDiscordBot/tree/master">libDiscordBot</a>, an easy to use Discord-bot framework.</li>
|
||||||
|
<li><a href="https://github.com/norrbotten/gwebsocket">gwebsocket</a>, a websocket (lua) module for Garry's Mod</li>
|
||||||
|
<li><a href="https://github.com/DisCPP/DisCPP">DisCPP</a>, a simple but feature rich Discord API wrapper</li>
|
||||||
|
<li><a href="https://github.com/luccanunes/discord.cpp">discord.cpp</a>, a discord library for making bots</li>
|
||||||
|
<li><a href="http://teleportconnect.com/">Teleport</a>, Teleport is your own personal remote robot avatar</li>
|
||||||
|
</ul>
|
||||||
|
<h2 id="alternative-libraries">Alternative libraries</h2>
|
||||||
|
<p>There are plenty of great websocket libraries out there, which might work for you. Here are a couple of serious ones.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://github.com/zaphoyd/websocketpp">websocketpp</a> - C++</li>
|
||||||
|
<li><a href="https://github.com/boostorg/beast">beast</a> - C++</li>
|
||||||
|
<li><a href="https://libwebsockets.org/">libwebsockets</a> - C</li>
|
||||||
|
<li><a href="https://github.com/uNetworking/uWebSockets">µWebSockets</a> - C</li>
|
||||||
|
<li><a href="https://github.com/tatsuhiro-t/wslay">wslay</a> - C</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/bsergean/uvweb">uvweb</a> is a library written by the IXWebSocket author which is built on top of <a href="https://github.com/skypjack/uvw">uvw</a>, which is a C++ wrapper for <a href="https://libuv.org/">libuv</a>. It has more dependencies and does not support SSL at this point, but it can be used to open multiple connections within a single OS thread thanks to libuv.</p>
|
||||||
|
<p>To check the performance of a websocket library, you can look at the <a href="https://github.com/bsergean/autoroute">autoroute</a> project.</p>
|
||||||
|
<h2 id="continuous-integration">Continuous Integration</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>OS</th>
|
||||||
|
<th>TLS</th>
|
||||||
|
<th>Sanitizer</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Linux</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
<td>None</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/linux/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>macOS</td>
|
||||||
|
<td>Secure Transport</td>
|
||||||
|
<td>Thread Sanitizer</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_sectransport/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>macOS</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
<td>Thread Sanitizer</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_openssl/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>macOS</td>
|
||||||
|
<td>MbedTLS</td>
|
||||||
|
<td>Thread Sanitizer</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/mac_tsan_mbedtls/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Windows</td>
|
||||||
|
<td>Disabled</td>
|
||||||
|
<td>None</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/windows/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>UWP</td>
|
||||||
|
<td>Disabled</td>
|
||||||
|
<td>None</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/uwp/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Linux</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
<td>Address Sanitizer</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/linux_asan/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Mingw</td>
|
||||||
|
<td>Disabled</td>
|
||||||
|
<td>None</td>
|
||||||
|
<td><a href="https://github.com/machinezone/IXWebSocket"><img alt="Build2" src="https://github.com/machinezone/IXWebSocket/workflows/unittest_windows_gcc/badge.svg" /></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<ul>
|
||||||
|
<li>ASAN fails on Linux because of a known problem, we need a </li>
|
||||||
|
<li>Some tests are disabled on Windows/UWP because of a pathing problem</li>
|
||||||
|
<li>TLS and ZLIB are disabled on Windows/UWP because enabling make the CI run takes a lot of time, for setting up vcpkg.</li>
|
||||||
|
</ul></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
<p>Documentation built with <a href="https://www.mkdocs.org/">MkDocs</a>.</p>
|
||||||
|
</footer>
|
||||||
|
<script src="js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="js/bootstrap.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var base_url = ".",
|
||||||
|
shortcuts = {"help": 191, "next": 78, "previous": 80, "search": 83};
|
||||||
|
</script>
|
||||||
|
<script src="js/base.js"></script>
|
||||||
|
<script src="search/main.js"></script>
|
||||||
|
|
||||||
|
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="searchModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="searchModalLabel">Search</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>From here you can search these documents. Enter your search terms below.</p>
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="search" class="form-control" placeholder="Search..." id="mkdocs-search-query" title="Type search term here">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div id="mkdocs-search-results" data-no-results-text="No results found"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><div class="modal" id="mkdocs_keyboard_modal" tabindex="-1" role="dialog" aria-labelledby="keyboardModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="keyboardModalLabel">Keyboard Shortcuts</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%;">Keys</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="help shortcut"><kbd>?</kbd></td>
|
||||||
|
<td>Open this help</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="next shortcut"><kbd>n</kbd></td>
|
||||||
|
<td>Next page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="prev shortcut"><kbd>p</kbd></td>
|
||||||
|
<td>Previous page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="search shortcut"><kbd>s</kbd></td>
|
||||||
|
<td>Search</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
MkDocs version : 1.5.3
|
||||||
|
Build Date UTC : 2024-03-28 05:13:08.815010+00:00
|
||||||
|
-->
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCancellationRequest.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
CancellationRequest makeCancellationRequestWithTimeout(int secs,
|
|
||||||
std::atomic<bool>& requestInitCancellation)
|
|
||||||
{
|
|
||||||
auto start = std::chrono::system_clock::now();
|
|
||||||
auto timeout = std::chrono::seconds(secs);
|
|
||||||
|
|
||||||
auto isCancellationRequested = [&requestInitCancellation, start, timeout]() -> bool
|
|
||||||
{
|
|
||||||
// Was an explicit cancellation requested ?
|
|
||||||
if (requestInitCancellation) return true;
|
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
if ((now - start) > timeout) return true;
|
|
||||||
|
|
||||||
// No cancellation request
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return isCancellationRequested;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXCancellationRequest.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
using CancellationRequest = std::function<bool()>;
|
|
||||||
|
|
||||||
CancellationRequest makeCancellationRequestWithTimeout(
|
|
||||||
int seconds, std::atomic<bool>& requestInitCancellation);
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXConnectionState.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXConnectionState.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
std::atomic<uint64_t> ConnectionState::_globalId(0);
|
|
||||||
|
|
||||||
ConnectionState::ConnectionState() : _terminated(false)
|
|
||||||
{
|
|
||||||
computeId();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionState::computeId()
|
|
||||||
{
|
|
||||||
_id = std::to_string(_globalId++);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& ConnectionState::getId() const
|
|
||||||
{
|
|
||||||
return _id;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
|
|
||||||
{
|
|
||||||
return std::make_shared<ConnectionState>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConnectionState::isTerminated() const
|
|
||||||
{
|
|
||||||
return _terminated;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionState::setTerminated()
|
|
||||||
{
|
|
||||||
_terminated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXConnectionState.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class ConnectionState
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ConnectionState();
|
|
||||||
virtual ~ConnectionState() = default;
|
|
||||||
|
|
||||||
virtual void computeId();
|
|
||||||
virtual const std::string& getId() const;
|
|
||||||
|
|
||||||
void setTerminated();
|
|
||||||
bool isTerminated() const;
|
|
||||||
|
|
||||||
static std::shared_ptr<ConnectionState> createConnectionState();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::atomic<bool> _terminated;
|
|
||||||
std::string _id;
|
|
||||||
|
|
||||||
static std::atomic<uint64_t> _globalId;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXDNSLookup.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXDNSLookup.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const int64_t DNSLookup::kDefaultWait = 10; // ms
|
|
||||||
|
|
||||||
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
|
||||||
std::set<uint64_t> DNSLookup::_activeJobs;
|
|
||||||
std::mutex DNSLookup::_activeJobsMutex;
|
|
||||||
|
|
||||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
|
||||||
_port(port),
|
|
||||||
_wait(wait),
|
|
||||||
_res(nullptr),
|
|
||||||
_done(false),
|
|
||||||
_id(_nextId++)
|
|
||||||
{
|
|
||||||
setHostname(hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
DNSLookup::~DNSLookup()
|
|
||||||
{
|
|
||||||
// Remove this job from the active jobs list
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
_activeJobs.erase(_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg)
|
|
||||||
{
|
|
||||||
struct addrinfo hints;
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
|
||||||
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
|
|
||||||
hints.ai_family = AF_UNSPEC;
|
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
|
||||||
|
|
||||||
std::string sport = std::to_string(port);
|
|
||||||
|
|
||||||
struct addrinfo* res;
|
|
||||||
int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(),
|
|
||||||
&hints, &res);
|
|
||||||
if (getaddrinfo_result)
|
|
||||||
{
|
|
||||||
errMsg = gai_strerror(getaddrinfo_result);
|
|
||||||
res = nullptr;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolve(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested,
|
|
||||||
bool blocking)
|
|
||||||
{
|
|
||||||
return blocking ? resolveBlocking(errMsg, isCancellationRequested)
|
|
||||||
: resolveAsync(errMsg, isCancellationRequested);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveBlocking(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
errMsg = "no error";
|
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the background thread terminated ?
|
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
|
||||||
{
|
|
||||||
errMsg = "cancellation requested";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getAddrInfo(getHostname(), _port, errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::resolveAsync(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
errMsg = "no error";
|
|
||||||
|
|
||||||
// Can only be called once, otherwise we would have to manage a pool
|
|
||||||
// of background thread which is overkill for our usage.
|
|
||||||
if (_done)
|
|
||||||
{
|
|
||||||
return nullptr; // programming error, create a second DNSLookup instance
|
|
||||||
// if you need a second lookup.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record job in the active Job set
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
_activeJobs.insert(_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Good resource on thread forced termination
|
|
||||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
|
||||||
//
|
|
||||||
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
|
|
||||||
_thread.detach();
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
||||||
|
|
||||||
while (!_done)
|
|
||||||
{
|
|
||||||
// Wait for 10 milliseconds on the condition variable, to see
|
|
||||||
// if the bg thread has terminated.
|
|
||||||
if (_condition.wait_for(lock, std::chrono::milliseconds(_wait)) == std::cv_status::no_timeout)
|
|
||||||
{
|
|
||||||
// Background thread has terminated, so we can break of this loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Were we cancelled ?
|
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
|
||||||
{
|
|
||||||
errMsg = "cancellation requested";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the bg terminated ?
|
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
|
||||||
{
|
|
||||||
errMsg = "cancellation requested";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
errMsg = getErrMsg();
|
|
||||||
return getRes();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
|
||||||
{
|
|
||||||
// We don't want to read or write into members variables of an object that could be
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// if this isn't an active job, and the control thread is gone
|
|
||||||
// there is nothing to do, and we don't want to touch the defunct
|
|
||||||
// object data structure such as _errMsg or _condition
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
if (_activeJobs.count(id) == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy result into the member variables
|
|
||||||
setRes(res);
|
|
||||||
setErrMsg(errMsg);
|
|
||||||
|
|
||||||
_condition.notify_one();
|
|
||||||
_done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::setHostname(const std::string& hostname)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
|
||||||
_hostname = hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& DNSLookup::getHostname()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_hostnameMutex);
|
|
||||||
return _hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::setErrMsg(const std::string& errMsg)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
|
||||||
_errMsg = errMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& DNSLookup::getErrMsg()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_errMsgMutex);
|
|
||||||
return _errMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DNSLookup::setRes(struct addrinfo* addr)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
|
||||||
_res = addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getRes()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_resMutex);
|
|
||||||
return _res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXDNSLookup.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo
|
|
||||||
* Does this in a background thread so that it can be cancelled, since
|
|
||||||
* getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
struct addrinfo;
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class DNSLookup
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
|
||||||
~DNSLookup();
|
|
||||||
|
|
||||||
struct addrinfo* resolve(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested,
|
|
||||||
bool blocking = false);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct addrinfo* resolveAsync(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
struct addrinfo* resolveBlocking(std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
static struct addrinfo* getAddrInfo(const std::string& hostname,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg);
|
|
||||||
|
|
||||||
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
|
||||||
|
|
||||||
void setHostname(const std::string& hostname);
|
|
||||||
const std::string& getHostname();
|
|
||||||
|
|
||||||
void setErrMsg(const std::string& errMsg);
|
|
||||||
const std::string& getErrMsg();
|
|
||||||
|
|
||||||
void setRes(struct addrinfo* addr);
|
|
||||||
struct addrinfo* getRes();
|
|
||||||
|
|
||||||
std::string _hostname;
|
|
||||||
std::mutex _hostnameMutex;
|
|
||||||
int _port;
|
|
||||||
|
|
||||||
int64_t _wait;
|
|
||||||
|
|
||||||
struct addrinfo* _res;
|
|
||||||
std::mutex _resMutex;
|
|
||||||
|
|
||||||
std::string _errMsg;
|
|
||||||
std::mutex _errMsgMutex;
|
|
||||||
|
|
||||||
std::atomic<bool> _done;
|
|
||||||
std::thread _thread;
|
|
||||||
std::condition_variable _condition;
|
|
||||||
std::mutex _conditionVariableMutex;
|
|
||||||
|
|
||||||
uint64_t _id;
|
|
||||||
static std::atomic<uint64_t> _nextId;
|
|
||||||
static std::set<uint64_t> _activeJobs;
|
|
||||||
static std::mutex _activeJobsMutex;
|
|
||||||
|
|
||||||
const static int64_t kDefaultWait;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,570 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXHttpClient.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXHttpClient.h"
|
|
||||||
#include "IXUrlParser.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXSocketFactory.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const std::string HttpClient::kPost = "POST";
|
|
||||||
const std::string HttpClient::kGet = "GET";
|
|
||||||
const std::string HttpClient::kHead = "HEAD";
|
|
||||||
const std::string HttpClient::kDel = "DEL";
|
|
||||||
const std::string HttpClient::kPut = "PUT";
|
|
||||||
|
|
||||||
HttpClient::HttpClient(bool async) : _async(async), _stop(false)
|
|
||||||
{
|
|
||||||
if (!_async) return;
|
|
||||||
|
|
||||||
_thread = std::thread(&HttpClient::run, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpClient::~HttpClient()
|
|
||||||
{
|
|
||||||
if (!_thread.joinable()) return;
|
|
||||||
|
|
||||||
_stop = true;
|
|
||||||
_condition.notify_one();
|
|
||||||
_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpRequestArgsPtr HttpClient::createRequest(const std::string& url,
|
|
||||||
const std::string& verb)
|
|
||||||
{
|
|
||||||
auto request = std::make_shared<HttpRequestArgs>();
|
|
||||||
request->url = url;
|
|
||||||
request->verb = verb;
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpClient::performRequest(HttpRequestArgsPtr args,
|
|
||||||
const OnResponseCallback& onResponseCallback)
|
|
||||||
{
|
|
||||||
if (!_async) return false;
|
|
||||||
|
|
||||||
// Enqueue the task
|
|
||||||
{
|
|
||||||
// acquire lock
|
|
||||||
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
||||||
|
|
||||||
// add the task
|
|
||||||
_queue.push(std::make_pair(args, onResponseCallback));
|
|
||||||
} // release lock
|
|
||||||
|
|
||||||
// wake up one thread
|
|
||||||
_condition.notify_one();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClient::run()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
HttpRequestArgsPtr args;
|
|
||||||
OnResponseCallback onResponseCallback;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(_queueMutex);
|
|
||||||
|
|
||||||
while (!_stop && _queue.empty())
|
|
||||||
{
|
|
||||||
_condition.wait(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
auto p = _queue.front();
|
|
||||||
_queue.pop();
|
|
||||||
|
|
||||||
args = p.first;
|
|
||||||
onResponseCallback = p.second;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
HttpResponsePtr response = request(args->url, args->verb, args->body, args);
|
|
||||||
onResponseCallback(response);
|
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::request(
|
|
||||||
const std::string& url,
|
|
||||||
const std::string& verb,
|
|
||||||
const std::string& body,
|
|
||||||
HttpRequestArgsPtr args,
|
|
||||||
int redirects)
|
|
||||||
{
|
|
||||||
// We only have one socket connection, so we cannot
|
|
||||||
// make multiple requests concurrently.
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
uint64_t uploadSize = 0;
|
|
||||||
uint64_t downloadSize = 0;
|
|
||||||
int code = 0;
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
std::string payload;
|
|
||||||
|
|
||||||
std::string protocol, host, path, query;
|
|
||||||
int port;
|
|
||||||
|
|
||||||
if (!UrlParser::parse(url, protocol, host, path, query, port))
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Cannot parse url: " << url;
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed,
|
|
||||||
headers, payload, ss.str(),
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tls = protocol == "https";
|
|
||||||
std::string errorMsg;
|
|
||||||
_socket = createSocket(tls, errorMsg);
|
|
||||||
|
|
||||||
if (!_socket)
|
|
||||||
{
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build request string
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << verb << " " << path << " HTTP/1.1\r\n";
|
|
||||||
ss << "Host: " << host << "\r\n";
|
|
||||||
|
|
||||||
if (args->compress)
|
|
||||||
{
|
|
||||||
ss << "Accept-Encoding: gzip" << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append extra headers
|
|
||||||
for (auto&& it : args->extraHeaders)
|
|
||||||
{
|
|
||||||
ss << it.first << ": " << it.second << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a default Accept header if none is present
|
|
||||||
if (headers.find("Accept") == headers.end())
|
|
||||||
{
|
|
||||||
ss << "Accept: */*" << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a default User agent if none is present
|
|
||||||
if (headers.find("User-Agent") == headers.end())
|
|
||||||
{
|
|
||||||
ss << "User-Agent: ixwebsocket" << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verb == kPost || verb == kPut)
|
|
||||||
{
|
|
||||||
ss << "Content-Length: " << body.size() << "\r\n";
|
|
||||||
|
|
||||||
// Set default Content-Type if unspecified
|
|
||||||
if (args->extraHeaders.find("Content-Type") == args->extraHeaders.end())
|
|
||||||
{
|
|
||||||
ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
|
|
||||||
}
|
|
||||||
ss << "\r\n";
|
|
||||||
ss << body;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss << "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string req(ss.str());
|
|
||||||
std::string errMsg;
|
|
||||||
std::atomic<bool> requestInitCancellation(false);
|
|
||||||
|
|
||||||
// Make a cancellation object dealing with connection timeout
|
|
||||||
auto isCancellationRequested =
|
|
||||||
makeCancellationRequestWithTimeout(args->connectTimeout, requestInitCancellation);
|
|
||||||
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotConnect,
|
|
||||||
headers, payload, ss.str(),
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a new cancellation object dealing with transfer timeout
|
|
||||||
isCancellationRequested =
|
|
||||||
makeCancellationRequestWithTimeout(args->transferTimeout, requestInitCancellation);
|
|
||||||
|
|
||||||
if (args->verbose)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Sending " << verb << " request "
|
|
||||||
<< "to " << host << ":" << port << std::endl
|
|
||||||
<< "request size: " << req.size() << " bytes" << std::endl
|
|
||||||
<< "=============" << std::endl
|
|
||||||
<< req
|
|
||||||
<< "=============" << std::endl
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
log(ss.str(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
|
||||||
{
|
|
||||||
std::string errorMsg("Cannot send request");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::SendError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadSize = req.size();
|
|
||||||
|
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
||||||
auto lineValid = lineResult.first;
|
|
||||||
auto line = lineResult.second;
|
|
||||||
|
|
||||||
if (!lineValid)
|
|
||||||
{
|
|
||||||
std::string errorMsg("Cannot retrieve status line");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadStatusLine,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args->verbose)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Status line " << line;
|
|
||||||
log(ss.str(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
|
||||||
{
|
|
||||||
std::string errorMsg("Cannot parse response code from status line");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingStatus,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
|
||||||
auto headersValid = result.first;
|
|
||||||
headers = result.second;
|
|
||||||
|
|
||||||
if (!headersValid)
|
|
||||||
{
|
|
||||||
std::string errorMsg("Cannot parse http headers");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::HeaderParsingError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect ?
|
|
||||||
if ((code >= 301 && code <= 308) && args->followRedirects)
|
|
||||||
{
|
|
||||||
if (headers.find("Location") == headers.end())
|
|
||||||
{
|
|
||||||
std::string errorMsg("Missing location header for redirect");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingLocation,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redirects >= args->maxRedirects)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Too many redirects: " << redirects;
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::TooManyRedirects,
|
|
||||||
headers, payload, ss.str(),
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recurse
|
|
||||||
std::string location = headers["Location"];
|
|
||||||
return request(location, verb, body, args, redirects+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verb == "HEAD")
|
|
||||||
{
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
|
||||||
headers, payload, std::string(),
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse response:
|
|
||||||
if (headers.find("Content-Length") != headers.end())
|
|
||||||
{
|
|
||||||
ssize_t contentLength = -1;
|
|
||||||
ss.str("");
|
|
||||||
ss << headers["Content-Length"];
|
|
||||||
ss >> contentLength;
|
|
||||||
|
|
||||||
payload.reserve(contentLength);
|
|
||||||
|
|
||||||
auto chunkResult = _socket->readBytes(contentLength,
|
|
||||||
args->onProgressCallback,
|
|
||||||
isCancellationRequested);
|
|
||||||
if (!chunkResult.first)
|
|
||||||
{
|
|
||||||
errorMsg = "Cannot read chunk";
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
payload += chunkResult.second;
|
|
||||||
}
|
|
||||||
else if (headers.find("Transfer-Encoding") != headers.end() &&
|
|
||||||
headers["Transfer-Encoding"] == "chunked")
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
lineResult = _socket->readLine(isCancellationRequested);
|
|
||||||
line = lineResult.second;
|
|
||||||
|
|
||||||
if (!lineResult.first)
|
|
||||||
{
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t chunkSize;
|
|
||||||
ss.str("");
|
|
||||||
ss << std::hex << line;
|
|
||||||
ss >> chunkSize;
|
|
||||||
|
|
||||||
if (args->verbose)
|
|
||||||
{
|
|
||||||
std::stringstream oss;
|
|
||||||
oss << "Reading " << chunkSize << " bytes"
|
|
||||||
<< std::endl;
|
|
||||||
log(oss.str(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.reserve(payload.size() + (size_t) chunkSize);
|
|
||||||
|
|
||||||
// Read a chunk
|
|
||||||
auto chunkResult = _socket->readBytes((size_t) chunkSize,
|
|
||||||
args->onProgressCallback,
|
|
||||||
isCancellationRequested);
|
|
||||||
if (!chunkResult.first)
|
|
||||||
{
|
|
||||||
errorMsg = "Cannot read chunk";
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
payload += chunkResult.second;
|
|
||||||
|
|
||||||
// Read the line that terminates the chunk (\r\n)
|
|
||||||
lineResult = _socket->readLine(isCancellationRequested);
|
|
||||||
|
|
||||||
if (!lineResult.first)
|
|
||||||
{
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunkSize == 0) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (code == 204)
|
|
||||||
{
|
|
||||||
; // 204 is NoContent response code
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::string errorMsg("Cannot read http body");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadBody,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadSize = payload.size();
|
|
||||||
|
|
||||||
// If the content was compressed with gzip, decode it
|
|
||||||
if (headers["Content-Encoding"] == "gzip")
|
|
||||||
{
|
|
||||||
std::string decompressedPayload;
|
|
||||||
if (!gzipInflate(payload, decompressedPayload))
|
|
||||||
{
|
|
||||||
std::string errorMsg("Error decompressing payload");
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Gzip,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
payload = decompressedPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
|
||||||
headers, payload, std::string(),
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::get(const std::string& url,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kGet, std::string(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::head(const std::string& url,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kHead, std::string(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::del(const std::string& url,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kDel, std::string(), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::post(const std::string& url,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kPost, serializeHttpParameters(httpParameters), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::post(const std::string& url,
|
|
||||||
const std::string& body,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kPost, body, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kPut, serializeHttpParameters(httpParameters), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponsePtr HttpClient::put(const std::string& url,
|
|
||||||
const std::string& body,
|
|
||||||
const HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
return request(url, kPut, body, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HttpClient::urlEncode(const std::string& value)
|
|
||||||
{
|
|
||||||
std::ostringstream escaped;
|
|
||||||
escaped.fill('0');
|
|
||||||
escaped << std::hex;
|
|
||||||
|
|
||||||
for (std::string::const_iterator i = value.begin(), n = value.end();
|
|
||||||
i != n; ++i)
|
|
||||||
{
|
|
||||||
std::string::value_type c = (*i);
|
|
||||||
|
|
||||||
// Keep alphanumeric and other accepted characters intact
|
|
||||||
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
|
|
||||||
{
|
|
||||||
escaped << c;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any other characters are percent-encoded
|
|
||||||
escaped << std::uppercase;
|
|
||||||
escaped << '%' << std::setw(2) << int((unsigned char) c);
|
|
||||||
escaped << std::nouppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
return escaped.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
size_t count = httpParameters.size();
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
for (auto&& it : httpParameters)
|
|
||||||
{
|
|
||||||
ss << urlEncode(it.first)
|
|
||||||
<< "="
|
|
||||||
<< urlEncode(it.second);
|
|
||||||
|
|
||||||
if (i++ < (count-1))
|
|
||||||
{
|
|
||||||
ss << "&";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpClient::gzipInflate(
|
|
||||||
const std::string& in,
|
|
||||||
std::string& out)
|
|
||||||
{
|
|
||||||
z_stream inflateState;
|
|
||||||
std::memset(&inflateState, 0, sizeof(inflateState));
|
|
||||||
|
|
||||||
inflateState.zalloc = Z_NULL;
|
|
||||||
inflateState.zfree = Z_NULL;
|
|
||||||
inflateState.opaque = Z_NULL;
|
|
||||||
inflateState.avail_in = 0;
|
|
||||||
inflateState.next_in = Z_NULL;
|
|
||||||
|
|
||||||
if (inflateInit2(&inflateState, 16+MAX_WBITS) != Z_OK)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inflateState.avail_in = (uInt) in.size();
|
|
||||||
inflateState.next_in = (unsigned char *)(const_cast<char *>(in.data()));
|
|
||||||
|
|
||||||
const int kBufferSize = 1 << 14;
|
|
||||||
|
|
||||||
std::unique_ptr<unsigned char[]> compressBuffer =
|
|
||||||
std::make_unique<unsigned char[]>(kBufferSize);
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
inflateState.avail_out = (uInt) kBufferSize;
|
|
||||||
inflateState.next_out = compressBuffer.get();
|
|
||||||
|
|
||||||
int ret = inflate(&inflateState, Z_SYNC_FLUSH);
|
|
||||||
|
|
||||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
|
||||||
{
|
|
||||||
inflateEnd(&inflateState);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
out.append(
|
|
||||||
reinterpret_cast<char *>(compressBuffer.get()),
|
|
||||||
kBufferSize - inflateState.avail_out
|
|
||||||
);
|
|
||||||
} while (inflateState.avail_out == 0);
|
|
||||||
|
|
||||||
inflateEnd(&inflateState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClient::log(const std::string& msg,
|
|
||||||
HttpRequestArgsPtr args)
|
|
||||||
{
|
|
||||||
if (args->logger)
|
|
||||||
{
|
|
||||||
args->logger(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXHttpClient.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
enum class HttpErrorCode : int
|
|
||||||
{
|
|
||||||
Ok = 0,
|
|
||||||
CannotConnect = 1,
|
|
||||||
Timeout = 2,
|
|
||||||
Gzip = 3,
|
|
||||||
UrlMalformed = 4,
|
|
||||||
CannotCreateSocket = 5,
|
|
||||||
SendError = 6,
|
|
||||||
ReadError = 7,
|
|
||||||
CannotReadStatusLine = 8,
|
|
||||||
MissingStatus = 9,
|
|
||||||
HeaderParsingError = 10,
|
|
||||||
MissingLocation = 11,
|
|
||||||
TooManyRedirects = 12,
|
|
||||||
ChunkReadError = 13,
|
|
||||||
CannotReadBody = 14,
|
|
||||||
Invalid = 100
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpResponse
|
|
||||||
{
|
|
||||||
int statusCode;
|
|
||||||
HttpErrorCode errorCode;
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
std::string payload;
|
|
||||||
std::string errorMsg;
|
|
||||||
uint64_t uploadSize;
|
|
||||||
uint64_t downloadSize;
|
|
||||||
|
|
||||||
HttpResponse(int s = 0,
|
|
||||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
|
||||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
|
||||||
const std::string& p = std::string(),
|
|
||||||
const std::string& e = std::string(),
|
|
||||||
uint64_t u = 0,
|
|
||||||
uint64_t d = 0)
|
|
||||||
: statusCode(s)
|
|
||||||
, errorCode(c)
|
|
||||||
, headers(h)
|
|
||||||
, payload(p)
|
|
||||||
, errorMsg(e)
|
|
||||||
, uploadSize(u)
|
|
||||||
, downloadSize(d)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
|
||||||
using HttpParameters = std::map<std::string, std::string>;
|
|
||||||
using Logger = std::function<void(const std::string&)>;
|
|
||||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
|
||||||
|
|
||||||
struct HttpRequestArgs
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
std::string verb;
|
|
||||||
WebSocketHttpHeaders extraHeaders;
|
|
||||||
std::string body;
|
|
||||||
int connectTimeout;
|
|
||||||
int transferTimeout;
|
|
||||||
bool followRedirects;
|
|
||||||
int maxRedirects;
|
|
||||||
bool verbose;
|
|
||||||
bool compress;
|
|
||||||
Logger logger;
|
|
||||||
OnProgressCallback onProgressCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
|
||||||
|
|
||||||
class HttpClient
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HttpClient(bool async = false);
|
|
||||||
~HttpClient();
|
|
||||||
|
|
||||||
HttpResponsePtr get(const std::string& url, HttpRequestArgsPtr args);
|
|
||||||
HttpResponsePtr head(const std::string& url, HttpRequestArgsPtr args);
|
|
||||||
HttpResponsePtr del(const std::string& url, HttpRequestArgsPtr args);
|
|
||||||
|
|
||||||
HttpResponsePtr post(const std::string& url,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
HttpRequestArgsPtr args);
|
|
||||||
HttpResponsePtr post(const std::string& url,
|
|
||||||
const std::string& body,
|
|
||||||
HttpRequestArgsPtr args);
|
|
||||||
|
|
||||||
HttpResponsePtr put(const std::string& url,
|
|
||||||
const HttpParameters& httpParameters,
|
|
||||||
HttpRequestArgsPtr args);
|
|
||||||
HttpResponsePtr put(const std::string& url,
|
|
||||||
const std::string& body,
|
|
||||||
HttpRequestArgsPtr args);
|
|
||||||
|
|
||||||
HttpResponsePtr request(const std::string& url,
|
|
||||||
const std::string& verb,
|
|
||||||
const std::string& body,
|
|
||||||
HttpRequestArgsPtr args,
|
|
||||||
int redirects = 0);
|
|
||||||
|
|
||||||
// Async API
|
|
||||||
HttpRequestArgsPtr createRequest(const std::string& url = std::string(),
|
|
||||||
const std::string& verb = HttpClient::kGet);
|
|
||||||
|
|
||||||
bool performRequest(HttpRequestArgsPtr request,
|
|
||||||
const OnResponseCallback& onResponseCallback);
|
|
||||||
|
|
||||||
std::string serializeHttpParameters(const HttpParameters& httpParameters);
|
|
||||||
|
|
||||||
std::string urlEncode(const std::string& value);
|
|
||||||
|
|
||||||
const static std::string kPost;
|
|
||||||
const static std::string kGet;
|
|
||||||
const static std::string kHead;
|
|
||||||
const static std::string kDel;
|
|
||||||
const static std::string kPut;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void log(const std::string& msg, HttpRequestArgsPtr args);
|
|
||||||
|
|
||||||
bool gzipInflate(const std::string& in, std::string& out);
|
|
||||||
|
|
||||||
// Async API background thread runner
|
|
||||||
void run();
|
|
||||||
|
|
||||||
// Async API
|
|
||||||
bool _async;
|
|
||||||
std::queue<std::pair<HttpRequestArgsPtr, OnResponseCallback>> _queue;
|
|
||||||
mutable std::mutex _queueMutex;
|
|
||||||
std::condition_variable _condition;
|
|
||||||
std::atomic<bool> _stop;
|
|
||||||
std::thread _thread;
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> _socket;
|
|
||||||
std::mutex _mutex; // to protect accessing the _socket (only one socket per client)
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXNetSystem.cpp
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
bool initNetSystem()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
WORD wVersionRequested;
|
|
||||||
WSADATA wsaData;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
|
|
||||||
wVersionRequested = MAKEWORD(2, 2);
|
|
||||||
|
|
||||||
err = WSAStartup(wVersionRequested, &wsaData);
|
|
||||||
|
|
||||||
return err == 0;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool uninitNetSystem()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
int err = WSACleanup();
|
|
||||||
|
|
||||||
return err == 0;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXNetSystem.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <WS2tcpip.h>
|
|
||||||
#include <WinSock2.h>
|
|
||||||
#include <basetsd.h>
|
|
||||||
#include <io.h>
|
|
||||||
#include <ws2def.h>
|
|
||||||
#else
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
bool initNetSystem();
|
|
||||||
bool uninitNetSystem();
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXProgressCallback.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
using OnProgressCallback = std::function<bool(int current, int total)>;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterrupt.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SelectInterrupt::SelectInterrupt()
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectInterrupt::~SelectInterrupt()
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterrupt::init(std::string& /*errorMsg*/)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterrupt::notify(uint64_t /*value*/)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t SelectInterrupt::read()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterrupt::clear()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SelectInterrupt::getFd() const
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterrupt.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterrupt
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SelectInterrupt();
|
|
||||||
virtual ~SelectInterrupt();
|
|
||||||
|
|
||||||
virtual bool init(std::string& errorMsg);
|
|
||||||
|
|
||||||
virtual bool notify(uint64_t value);
|
|
||||||
virtual bool clear();
|
|
||||||
virtual uint64_t read();
|
|
||||||
virtual int getFd() const;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptEventFd.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// On Linux we use eventd to wake up select.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// Linux/Android has a special type of virtual files. select(2) will react
|
|
||||||
// when reading/writing to those files, unlike closing sockets.
|
|
||||||
//
|
|
||||||
// https://linux.die.net/man/2/eventfd
|
|
||||||
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
|
|
||||||
//
|
|
||||||
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
|
|
||||||
// is on Kernel 3.x
|
|
||||||
//
|
|
||||||
// cf Android/Kernel table here
|
|
||||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
|
|
||||||
//
|
|
||||||
// On macOS we use UNIX pipes to wake up select.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "IXSelectInterruptEventFd.h"
|
|
||||||
|
|
||||||
#include <sys/eventfd.h>
|
|
||||||
|
|
||||||
#include <unistd.h> // for write
|
|
||||||
#include <string.h> // for strerror
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SelectInterruptEventFd::SelectInterruptEventFd()
|
|
||||||
{
|
|
||||||
_eventfd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectInterruptEventFd::~SelectInterruptEventFd()
|
|
||||||
{
|
|
||||||
::close(_eventfd);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::init(std::string& errorMsg)
|
|
||||||
{
|
|
||||||
// calling init twice is a programming error
|
|
||||||
assert(_eventfd == -1);
|
|
||||||
|
|
||||||
_eventfd = eventfd(0, 0);
|
|
||||||
if (_eventfd < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptEventFd::init() failed in eventfd()"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_eventfd = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_eventfd, F_SETFL, O_NONBLOCK) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptEventFd::init() failed in fcntl() call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_eventfd = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::notify(uint64_t value)
|
|
||||||
{
|
|
||||||
int fd = _eventfd;
|
|
||||||
|
|
||||||
if (fd == -1) return false;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(fd, &value, sizeof(value)) == 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: return max uint64_t for errors ?
|
|
||||||
uint64_t SelectInterruptEventFd::read()
|
|
||||||
{
|
|
||||||
int fd = _eventfd;
|
|
||||||
|
|
||||||
uint64_t value = 0;
|
|
||||||
::read(fd, &value, sizeof(value));
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptEventFd::clear()
|
|
||||||
{
|
|
||||||
if (_eventfd == -1) return false;
|
|
||||||
|
|
||||||
// 0 is a special value ; select will not wake up
|
|
||||||
uint64_t value = 0;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(_eventfd, &value, sizeof(value)) == 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SelectInterruptEventFd::getFd() const
|
|
||||||
{
|
|
||||||
return _eventfd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptEventFd.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterruptEventFd final : public SelectInterrupt
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SelectInterruptEventFd();
|
|
||||||
virtual ~SelectInterruptEventFd();
|
|
||||||
|
|
||||||
bool init(std::string& errorMsg) final;
|
|
||||||
|
|
||||||
bool notify(uint64_t value) final;
|
|
||||||
bool clear() final;
|
|
||||||
uint64_t read() final;
|
|
||||||
int getFd() const final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int _eventfd;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptFactory.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSelectInterruptFactory.h"
|
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
# include <ixwebsocket/IXSelectInterruptPipe.h>
|
|
||||||
#else
|
|
||||||
# include <ixwebsocket/IXSelectInterrupt.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
std::shared_ptr<SelectInterrupt> createSelectInterrupt()
|
|
||||||
{
|
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
|
||||||
return std::make_shared<SelectInterruptPipe>();
|
|
||||||
#else
|
|
||||||
return std::make_shared<SelectInterrupt>();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptFactory.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterrupt;
|
|
||||||
std::shared_ptr<SelectInterrupt> createSelectInterrupt();
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptPipe.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// On macOS we use UNIX pipes to wake up select.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "IXSelectInterruptPipe.h"
|
|
||||||
|
|
||||||
#include <unistd.h> // for write
|
|
||||||
#include <string.h> // for strerror
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
// File descriptor at index 0 in _fildes is the read end of the pipe
|
|
||||||
// File descriptor at index 1 in _fildes is the write end of the pipe
|
|
||||||
const int SelectInterruptPipe::kPipeReadIndex = 0;
|
|
||||||
const int SelectInterruptPipe::kPipeWriteIndex = 1;
|
|
||||||
|
|
||||||
SelectInterruptPipe::SelectInterruptPipe()
|
|
||||||
{
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectInterruptPipe::~SelectInterruptPipe()
|
|
||||||
{
|
|
||||||
::close(_fildes[kPipeReadIndex]);
|
|
||||||
::close(_fildes[kPipeWriteIndex]);
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptPipe::init(std::string& errorMsg)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
|
||||||
|
|
||||||
// calling init twice is a programming error
|
|
||||||
assert(_fildes[kPipeReadIndex] == -1);
|
|
||||||
assert(_fildes[kPipeWriteIndex] == -1);
|
|
||||||
|
|
||||||
if (pipe(_fildes) < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptPipe::init() failed in pipe() call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef F_SETNOSIGPIPE
|
|
||||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
|
|
||||||
<< " : " << strerror(errno);
|
|
||||||
errorMsg = ss.str();
|
|
||||||
|
|
||||||
_fildes[kPipeReadIndex] = -1;
|
|
||||||
_fildes[kPipeWriteIndex] = -1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptPipe::notify(uint64_t value)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
|
||||||
|
|
||||||
int fd = _fildes[kPipeWriteIndex];
|
|
||||||
if (fd == -1) return false;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(fd, &value, sizeof(value)) == 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: return max uint64_t for errors ?
|
|
||||||
uint64_t SelectInterruptPipe::read()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
|
||||||
|
|
||||||
int fd = _fildes[kPipeReadIndex];
|
|
||||||
|
|
||||||
uint64_t value = 0;
|
|
||||||
::read(fd, &value, sizeof(value));
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SelectInterruptPipe::clear()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SelectInterruptPipe::getFd() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_fildesMutex);
|
|
||||||
|
|
||||||
return _fildes[kPipeReadIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSelectInterruptPipe.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSelectInterrupt.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterruptPipe final : public SelectInterrupt
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SelectInterruptPipe();
|
|
||||||
virtual ~SelectInterruptPipe();
|
|
||||||
|
|
||||||
bool init(std::string& errorMsg) final;
|
|
||||||
|
|
||||||
bool notify(uint64_t value) final;
|
|
||||||
bool clear() final;
|
|
||||||
uint64_t read() final;
|
|
||||||
int getFd() const final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Store file descriptors used by the communication pipe. Communication
|
|
||||||
// happens between a control thread and a background thread, which is
|
|
||||||
// blocked on select.
|
|
||||||
int _fildes[2];
|
|
||||||
mutable std::mutex _fildesMutex;
|
|
||||||
|
|
||||||
// Used to identify the read/write idx
|
|
||||||
static const int kPipeReadIndex;
|
|
||||||
static const int kPipeWriteIndex;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name);
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocket.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
#include "IXSelectInterrupt.h"
|
|
||||||
#include "IXSelectInterruptFactory.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#ifdef min
|
|
||||||
#undef min
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
|
||||||
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
|
|
||||||
const uint64_t Socket::kSendRequest = 1;
|
|
||||||
const uint64_t Socket::kCloseRequest = 2;
|
|
||||||
constexpr size_t Socket::kChunkSize;
|
|
||||||
|
|
||||||
Socket::Socket(int fd) :
|
|
||||||
_sockfd(fd),
|
|
||||||
_selectInterrupt(createSelectInterrupt())
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket::~Socket()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
PollResultType Socket::poll(int timeoutMs)
|
|
||||||
{
|
|
||||||
return isReadyToRead(timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
PollResultType Socket::select(bool readyToRead, int timeoutMs)
|
|
||||||
{
|
|
||||||
fd_set rfds;
|
|
||||||
fd_set wfds;
|
|
||||||
FD_ZERO(&rfds);
|
|
||||||
FD_ZERO(&wfds);
|
|
||||||
|
|
||||||
fd_set* fds = (readyToRead) ? &rfds : & wfds;
|
|
||||||
if (_sockfd != -1)
|
|
||||||
{
|
|
||||||
FD_SET(_sockfd, fds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// File descriptor used to interrupt select when needed
|
|
||||||
int interruptFd = _selectInterrupt->getFd();
|
|
||||||
if (interruptFd != -1)
|
|
||||||
{
|
|
||||||
FD_SET(interruptFd, fds);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = timeoutMs / 1000;
|
|
||||||
timeout.tv_usec = 1000 * (timeoutMs % 1000);
|
|
||||||
|
|
||||||
// Compute the highest fd.
|
|
||||||
int sockfd = _sockfd;
|
|
||||||
int nfds = (std::max)(sockfd, interruptFd);
|
|
||||||
|
|
||||||
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
|
|
||||||
(timeoutMs < 0) ? nullptr : &timeout);
|
|
||||||
|
|
||||||
PollResultType pollResult = PollResultType::ReadyForRead;
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::Error;
|
|
||||||
}
|
|
||||||
else if (ret == 0)
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::Timeout;
|
|
||||||
}
|
|
||||||
else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
|
|
||||||
{
|
|
||||||
uint64_t value = _selectInterrupt->read();
|
|
||||||
|
|
||||||
if (value == kSendRequest)
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::SendRequest;
|
|
||||||
}
|
|
||||||
else if (value == kCloseRequest)
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::CloseRequest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::ReadyForRead;
|
|
||||||
}
|
|
||||||
else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
|
|
||||||
{
|
|
||||||
pollResult = PollResultType::ReadyForWrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pollResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
PollResultType Socket::isReadyToRead(int timeoutMs)
|
|
||||||
{
|
|
||||||
if (_sockfd == -1)
|
|
||||||
{
|
|
||||||
return PollResultType::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readyToRead = true;
|
|
||||||
return select(readyToRead, timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
|
||||||
{
|
|
||||||
if (_sockfd == -1)
|
|
||||||
{
|
|
||||||
return PollResultType::Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool readyToRead = false;
|
|
||||||
return select(readyToRead, timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wake up from poll/select by writing to the pipe which is watched by select
|
|
||||||
bool Socket::wakeUpFromPoll(uint64_t wakeUpCode)
|
|
||||||
{
|
|
||||||
return _selectInterrupt->notify(wakeUpCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
|
||||||
|
|
||||||
if (!_selectInterrupt->clear()) return false;
|
|
||||||
|
|
||||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
return _sockfd != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Socket::close()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
|
||||||
|
|
||||||
if (_sockfd == -1) return;
|
|
||||||
|
|
||||||
closeSocket(_sockfd);
|
|
||||||
_sockfd = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t Socket::send(char* buffer, size_t length)
|
|
||||||
{
|
|
||||||
int flags = 0;
|
|
||||||
#ifdef MSG_NOSIGNAL
|
|
||||||
flags = MSG_NOSIGNAL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ::send(_sockfd, buffer, length, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t Socket::send(const std::string& buffer)
|
|
||||||
{
|
|
||||||
return send((char*)&buffer[0], buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t Socket::recv(void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
int flags = 0;
|
|
||||||
#ifdef MSG_NOSIGNAL
|
|
||||||
flags = MSG_NOSIGNAL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return ::recv(_sockfd, (char*) buffer, length, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Socket::getErrno()
|
|
||||||
{
|
|
||||||
int err;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
err = WSAGetLastError();
|
|
||||||
#else
|
|
||||||
err = errno;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::isWaitNeeded()
|
|
||||||
{
|
|
||||||
int err = getErrno();
|
|
||||||
|
|
||||||
if (err == EWOULDBLOCK || err == EAGAIN || err == EINPROGRESS)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Socket::closeSocket(int fd)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
closesocket(fd);
|
|
||||||
#else
|
|
||||||
::close(fd);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::init(std::string& errorMsg)
|
|
||||||
{
|
|
||||||
return _selectInterrupt->init(errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::writeBytes(const std::string& str,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
|
||||||
|
|
||||||
char* buffer = const_cast<char*>(str.c_str());
|
|
||||||
int len = (int) str.size();
|
|
||||||
|
|
||||||
ssize_t ret = send(buffer, len);
|
|
||||||
|
|
||||||
// We wrote some bytes, as needed, all good.
|
|
||||||
if (ret > 0)
|
|
||||||
{
|
|
||||||
return ret == len;
|
|
||||||
}
|
|
||||||
// There is possibly something to be writen, try again
|
|
||||||
else if (ret < 0 && Socket::isWaitNeeded())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// There was an error during the write, abort
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::readByte(void* buffer,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
|
||||||
|
|
||||||
ssize_t ret;
|
|
||||||
ret = recv(buffer, 1);
|
|
||||||
|
|
||||||
// We read one byte, as needed, all good.
|
|
||||||
if (ret == 1)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// There is possibly something to be read, try again
|
|
||||||
else if (ret < 0 && Socket::isWaitNeeded())
|
|
||||||
{
|
|
||||||
// Wait with a 1ms timeout until the socket is ready to read.
|
|
||||||
// This way we are not busy looping
|
|
||||||
if (isReadyToRead(1) == PollResultType::Error)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// There was an error during the read, abort
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::string> Socket::readLine(
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
char c;
|
|
||||||
std::string line;
|
|
||||||
line.reserve(64);
|
|
||||||
|
|
||||||
for (int i = 0; i < 2 || (line[i-2] != '\r' && line[i-1] != '\n'); ++i)
|
|
||||||
{
|
|
||||||
if (!readByte(&c, isCancellationRequested))
|
|
||||||
{
|
|
||||||
// Return what we were able to read
|
|
||||||
return std::make_pair(false, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
line += c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(true, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::string> Socket::readBytes(
|
|
||||||
size_t length,
|
|
||||||
const OnProgressCallback& onProgressCallback,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
if (_readBuffer.empty())
|
|
||||||
{
|
|
||||||
_readBuffer.resize(kChunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> output;
|
|
||||||
while (output.size() != length)
|
|
||||||
{
|
|
||||||
if (isCancellationRequested && isCancellationRequested())
|
|
||||||
{
|
|
||||||
return std::make_pair(false, std::string());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size = std::min(kChunkSize, length - output.size());
|
|
||||||
ssize_t ret = recv((char*)&_readBuffer[0], size);
|
|
||||||
|
|
||||||
if (ret <= 0 && !Socket::isWaitNeeded())
|
|
||||||
{
|
|
||||||
// Error
|
|
||||||
return std::make_pair(false, std::string());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
output.insert(output.end(),
|
|
||||||
_readBuffer.begin(),
|
|
||||||
_readBuffer.begin() + ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
|
|
||||||
|
|
||||||
// Wait with a 1ms timeout until the socket is ready to read.
|
|
||||||
// This way we are not busy looping
|
|
||||||
if (isReadyToRead(1) == PollResultType::Error)
|
|
||||||
{
|
|
||||||
return std::make_pair(false, std::string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(true, std::string(output.begin(),
|
|
||||||
output.end()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocket.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <BaseTsd.h>
|
|
||||||
typedef SSIZE_T ssize_t;
|
|
||||||
|
|
||||||
#undef EWOULDBLOCK
|
|
||||||
#undef EAGAIN
|
|
||||||
#undef EINPROGRESS
|
|
||||||
#undef EBADF
|
|
||||||
#undef EINVAL
|
|
||||||
|
|
||||||
// map to WSA error codes
|
|
||||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
|
||||||
#define EAGAIN WSATRY_AGAIN
|
|
||||||
#define EINPROGRESS WSAEINPROGRESS
|
|
||||||
#define EBADF WSAEBADF
|
|
||||||
#define EINVAL WSAEINVAL
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include "IXProgressCallback.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SelectInterrupt;
|
|
||||||
|
|
||||||
enum class PollResultType
|
|
||||||
{
|
|
||||||
ReadyForRead = 0,
|
|
||||||
ReadyForWrite = 1,
|
|
||||||
Timeout = 2,
|
|
||||||
Error = 3,
|
|
||||||
SendRequest = 4,
|
|
||||||
CloseRequest = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
class Socket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Socket(int fd = -1);
|
|
||||||
virtual ~Socket();
|
|
||||||
bool init(std::string& errorMsg);
|
|
||||||
|
|
||||||
// Functions to check whether there is activity on the socket
|
|
||||||
PollResultType poll(int timeoutMs = kDefaultPollTimeout);
|
|
||||||
bool wakeUpFromPoll(uint64_t wakeUpCode);
|
|
||||||
|
|
||||||
PollResultType isReadyToWrite(int timeoutMs);
|
|
||||||
PollResultType isReadyToRead(int timeoutMs);
|
|
||||||
|
|
||||||
// Virtual methods
|
|
||||||
virtual bool connect(const std::string& url,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
virtual void close();
|
|
||||||
|
|
||||||
virtual ssize_t send(char* buffer, size_t length);
|
|
||||||
virtual ssize_t send(const std::string& buffer);
|
|
||||||
virtual ssize_t recv(void* buffer, size_t length);
|
|
||||||
|
|
||||||
// Blocking and cancellable versions, working with socket that can be set
|
|
||||||
// to non blocking mode. Used during HTTP upgrade.
|
|
||||||
bool readByte(void* buffer, const CancellationRequest& isCancellationRequested);
|
|
||||||
bool writeBytes(const std::string& str, const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
|
|
||||||
std::pair<bool, std::string> readBytes(size_t length,
|
|
||||||
const OnProgressCallback& onProgressCallback,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
static int getErrno();
|
|
||||||
static bool isWaitNeeded();
|
|
||||||
static void closeSocket(int fd);
|
|
||||||
|
|
||||||
// Used as special codes for pipe communication
|
|
||||||
static const uint64_t kSendRequest;
|
|
||||||
static const uint64_t kCloseRequest;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::atomic<int> _sockfd;
|
|
||||||
std::mutex _socketMutex;
|
|
||||||
|
|
||||||
private:
|
|
||||||
PollResultType select(bool readyToRead, int timeoutMs);
|
|
||||||
|
|
||||||
static const int kDefaultPollTimeout;
|
|
||||||
static const int kDefaultPollNoTimeout;
|
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
|
||||||
std::vector<uint8_t> _readBuffer;
|
|
||||||
static constexpr size_t kChunkSize = 1 << 15;
|
|
||||||
|
|
||||||
std::shared_ptr<SelectInterrupt> _selectInterrupt;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketAppleSSL.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Adapted from Satori SDK Apple SSL code.
|
|
||||||
*/
|
|
||||||
#include "IXSocketAppleSSL.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#define socketerrno errno
|
|
||||||
|
|
||||||
#include <Security/SecureTransport.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len)
|
|
||||||
{
|
|
||||||
int fd = (int) (long) connection;
|
|
||||||
if (fd < 0)
|
|
||||||
return errSSLInternal;
|
|
||||||
|
|
||||||
assert(data != nullptr);
|
|
||||||
assert(len != nullptr);
|
|
||||||
|
|
||||||
size_t requested_sz = *len;
|
|
||||||
|
|
||||||
ssize_t status = read(fd, data, requested_sz);
|
|
||||||
|
|
||||||
if (status > 0)
|
|
||||||
{
|
|
||||||
*len = (size_t) status;
|
|
||||||
if (requested_sz > *len)
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
else
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
else if (0 == status)
|
|
||||||
{
|
|
||||||
*len = 0;
|
|
||||||
return errSSLClosedGraceful;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*len = 0;
|
|
||||||
switch (errno) {
|
|
||||||
case ENOENT:
|
|
||||||
return errSSLClosedGraceful;
|
|
||||||
|
|
||||||
case EAGAIN:
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
|
|
||||||
case ECONNRESET:
|
|
||||||
return errSSLClosedAbort;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errSecIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t *len)
|
|
||||||
{
|
|
||||||
int fd = (int) (long) connection;
|
|
||||||
if (fd < 0)
|
|
||||||
return errSSLInternal;
|
|
||||||
|
|
||||||
assert(data != nullptr);
|
|
||||||
assert(len != nullptr);
|
|
||||||
|
|
||||||
size_t to_write_sz = *len;
|
|
||||||
ssize_t status = write(fd, data, to_write_sz);
|
|
||||||
|
|
||||||
if (status > 0)
|
|
||||||
{
|
|
||||||
*len = (size_t) status;
|
|
||||||
if (to_write_sz > *len)
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
else
|
|
||||||
return noErr;
|
|
||||||
}
|
|
||||||
else if (0 == status)
|
|
||||||
{
|
|
||||||
*len = 0;
|
|
||||||
return errSSLClosedGraceful;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*len = 0;
|
|
||||||
if (EAGAIN == errno)
|
|
||||||
{
|
|
||||||
return errSSLWouldBlock;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return errSecIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getSSLErrorDescription(OSStatus status)
|
|
||||||
{
|
|
||||||
std::string errMsg("Unknown SSL error.");
|
|
||||||
|
|
||||||
CFErrorRef error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
CFStringRef message = CFErrorCopyDescription(error);
|
|
||||||
if (message)
|
|
||||||
{
|
|
||||||
char localBuffer[128];
|
|
||||||
Boolean success;
|
|
||||||
success = CFStringGetCString(message, localBuffer, 128,
|
|
||||||
CFStringGetSystemEncoding());
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
errMsg = localBuffer;
|
|
||||||
}
|
|
||||||
CFRelease(message);
|
|
||||||
}
|
|
||||||
CFRelease(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
|
|
||||||
_sslContext(nullptr)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketAppleSSL::~SocketAppleSSL()
|
|
||||||
{
|
|
||||||
SocketAppleSSL::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// No wait support
|
|
||||||
bool SocketAppleSSL::connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
OSStatus status;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
if (_sockfd == -1) return false;
|
|
||||||
|
|
||||||
_sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
|
|
||||||
|
|
||||||
SSLSetIOFuncs(_sslContext, read_from_socket, write_to_socket);
|
|
||||||
SSLSetConnection(_sslContext, (SSLConnectionRef) (long) _sockfd);
|
|
||||||
SSLSetProtocolVersionMin(_sslContext, kTLSProtocol12);
|
|
||||||
SSLSetPeerDomainName(_sslContext, host.c_str(), host.size());
|
|
||||||
|
|
||||||
do {
|
|
||||||
status = SSLHandshake(_sslContext);
|
|
||||||
} while (errSSLWouldBlock == status ||
|
|
||||||
errSSLServerAuthCompleted == status);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noErr != status)
|
|
||||||
{
|
|
||||||
errMsg = getSSLErrorDescription(status);
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketAppleSSL::close()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
if (_sslContext == nullptr) return;
|
|
||||||
|
|
||||||
SSLClose(_sslContext);
|
|
||||||
CFRelease(_sslContext);
|
|
||||||
_sslContext = nullptr;
|
|
||||||
|
|
||||||
Socket::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
ssize_t ret = 0;
|
|
||||||
OSStatus status;
|
|
||||||
do {
|
|
||||||
size_t processed = 0;
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
status = SSLWrite(_sslContext, buf, nbyte, &processed);
|
|
||||||
ret += processed;
|
|
||||||
buf += processed;
|
|
||||||
nbyte -= processed;
|
|
||||||
} while (nbyte > 0 && errSSLWouldBlock == status);
|
|
||||||
|
|
||||||
if (ret == 0 && errSSLClosedAbort != status)
|
|
||||||
ret = -1;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketAppleSSL::send(const std::string& buffer)
|
|
||||||
{
|
|
||||||
return send((char*)&buffer[0], buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// No wait support
|
|
||||||
ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
OSStatus status = errSSLWouldBlock;
|
|
||||||
while (errSSLWouldBlock == status)
|
|
||||||
{
|
|
||||||
size_t processed = 0;
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
status = SSLRead(_sslContext, buf, nbyte, &processed);
|
|
||||||
|
|
||||||
if (processed > 0)
|
|
||||||
return (ssize_t) processed;
|
|
||||||
|
|
||||||
// The connection was reset, inform the caller that this
|
|
||||||
// Socket should close
|
|
||||||
if (status == errSSLClosedGraceful ||
|
|
||||||
status == errSSLClosedNoNotify ||
|
|
||||||
status == errSSLClosedAbort)
|
|
||||||
{
|
|
||||||
errno = ECONNRESET;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status == errSSLWouldBlock)
|
|
||||||
{
|
|
||||||
errno = EWOULDBLOCK;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketAppleSSL.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include <Security/SecureTransport.h>
|
|
||||||
#include <Security/Security.h>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketAppleSSL final : public Socket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SocketAppleSSL(int fd = -1);
|
|
||||||
~SocketAppleSSL();
|
|
||||||
|
|
||||||
virtual bool connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested) final;
|
|
||||||
virtual void close() final;
|
|
||||||
|
|
||||||
virtual ssize_t send(char* buffer, size_t length) final;
|
|
||||||
virtual ssize_t send(const std::string& buffer) final;
|
|
||||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
SSLContextRef _sslContext;
|
|
||||||
mutable std::mutex _mutex; // AppleSSL routines are not thread-safe
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketConnect.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXDNSLookup.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
// Android needs extra headers for TCP_NODELAY and IPPROTO_TCP
|
|
||||||
#ifdef ANDROID
|
|
||||||
# include <linux/in.h>
|
|
||||||
# include <linux/tcp.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// This function can be cancelled every 50 ms
|
|
||||||
// This is important so that we don't block the main UI thread when shutting down a connection which is
|
|
||||||
// already trying to reconnect, and can be blocked waiting for ::connect to respond.
|
|
||||||
//
|
|
||||||
int SocketConnect::connectToAddress(const struct addrinfo *address,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
errMsg = "no error";
|
|
||||||
|
|
||||||
int fd = socket(address->ai_family,
|
|
||||||
address->ai_socktype,
|
|
||||||
address->ai_protocol);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
errMsg = "Cannot create a socket";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the socket to non blocking mode, so that slow responses cannot
|
|
||||||
// block us for too long
|
|
||||||
SocketConnect::configure(fd);
|
|
||||||
|
|
||||||
int res = ::connect(fd, address->ai_addr, address->ai_addrlen);
|
|
||||||
|
|
||||||
if (res == -1 && !Socket::isWaitNeeded())
|
|
||||||
{
|
|
||||||
errMsg = strerror(Socket::getErrno());
|
|
||||||
Socket::closeSocket(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
|
||||||
{
|
|
||||||
Socket::closeSocket(fd);
|
|
||||||
errMsg = "Cancelled";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Linux the timeout needs to be re-initialized everytime
|
|
||||||
// http://man7.org/linux/man-pages/man2/select.2.html
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = 0;
|
|
||||||
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
|
||||||
|
|
||||||
fd_set wfds;
|
|
||||||
fd_set efds;
|
|
||||||
|
|
||||||
FD_ZERO(&wfds);
|
|
||||||
FD_SET(fd, &wfds);
|
|
||||||
FD_ZERO(&efds);
|
|
||||||
FD_SET(fd, &efds);
|
|
||||||
|
|
||||||
// Use select to check the status of the new connection
|
|
||||||
res = select(fd + 1, nullptr, &wfds, &efds, &timeout);
|
|
||||||
|
|
||||||
if (res < 0 && (Socket::getErrno() == EBADF || Socket::getErrno() == EINVAL))
|
|
||||||
{
|
|
||||||
Socket::closeSocket(fd);
|
|
||||||
errMsg = std::string("Connect error, select error: ") + strerror(Socket::getErrno());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing was written to the socket, wait again.
|
|
||||||
if (!FD_ISSET(fd, &wfds)) continue;
|
|
||||||
|
|
||||||
// Something was written to the socket. Check for errors.
|
|
||||||
int optval = -1;
|
|
||||||
socklen_t optlen = sizeof(optval);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// On connect error, in async mode, windows will write to the exceptions fds
|
|
||||||
if (FD_ISSET(fd, &efds))
|
|
||||||
#else
|
|
||||||
// getsockopt() puts the errno value for connect into optval so 0
|
|
||||||
// means no-error.
|
|
||||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
|
|
||||||
optval != 0)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
Socket::closeSocket(fd);
|
|
||||||
errMsg = strerror(optval);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Success !
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket::closeSocket(fd);
|
|
||||||
errMsg = "connect timed out after 60 seconds";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SocketConnect::connect(const std::string& hostname,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// First do DNS resolution
|
|
||||||
//
|
|
||||||
DNSLookup dnsLookup(hostname, port);
|
|
||||||
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
|
|
||||||
if (res == nullptr)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sockfd = -1;
|
|
||||||
|
|
||||||
// iterate through the records to find a working peer
|
|
||||||
struct addrinfo *address;
|
|
||||||
for (address = res; address != nullptr; address = address->ai_next)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Second try to connect to the remote host
|
|
||||||
//
|
|
||||||
sockfd = connectToAddress(address, errMsg, isCancellationRequested);
|
|
||||||
if (sockfd != -1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
freeaddrinfo(res);
|
|
||||||
return sockfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: configure is a terrible name
|
|
||||||
void SocketConnect::configure(int sockfd)
|
|
||||||
{
|
|
||||||
// 1. disable Nagle's algorithm
|
|
||||||
int flag = 1;
|
|
||||||
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));
|
|
||||||
|
|
||||||
// 2. make socket non blocking
|
|
||||||
#ifdef _WIN32
|
|
||||||
unsigned long nonblocking = 1;
|
|
||||||
ioctlsocket(sockfd, FIONBIO, &nonblocking);
|
|
||||||
#else
|
|
||||||
fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
|
|
||||||
#ifdef SO_NOSIGPIPE
|
|
||||||
int value = 1;
|
|
||||||
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
|
|
||||||
(void *)&value, sizeof(value));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketConnect.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
struct addrinfo;
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketConnect
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static int connect(const std::string& hostname,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
|
|
||||||
static void configure(int sockfd);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int connectToAddress(const struct addrinfo* address,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested);
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketFactory.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocketFactory.h"
|
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
|
||||||
|
|
||||||
# ifdef IXWEBSOCKET_USE_MBED_TLS
|
|
||||||
# include <ixwebsocket/IXSocketMbedTLS.h>
|
|
||||||
# elif __APPLE__
|
|
||||||
# include <ixwebsocket/IXSocketAppleSSL.h>
|
|
||||||
# elif defined(_WIN32)
|
|
||||||
# include <ixwebsocket/IXSocketSChannel.h>
|
|
||||||
# elif defined(IXWEBSOCKET_USE_OPEN_SSL)
|
|
||||||
# include <ixwebsocket/IXSocketOpenSSL.h>
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
std::shared_ptr<Socket> createSocket(bool tls,
|
|
||||||
std::string& errorMsg)
|
|
||||||
{
|
|
||||||
errorMsg.clear();
|
|
||||||
std::shared_ptr<Socket> socket;
|
|
||||||
|
|
||||||
if (!tls)
|
|
||||||
{
|
|
||||||
socket = std::make_shared<Socket>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
|
||||||
# if defined(IXWEBSOCKET_USE_MBED_TLS)
|
|
||||||
socket = std::make_shared<SocketMbedTLS>();
|
|
||||||
# elif defined(__APPLE__)
|
|
||||||
socket = std::make_shared<SocketAppleSSL>();
|
|
||||||
# elif defined(_WIN32)
|
|
||||||
socket = std::make_shared<SocketSChannel>();
|
|
||||||
# else
|
|
||||||
socket = std::make_shared<SocketOpenSSL>();
|
|
||||||
# endif
|
|
||||||
#else
|
|
||||||
errorMsg = "TLS support is not enabled on this platform.";
|
|
||||||
return nullptr;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!socket->init(errorMsg))
|
|
||||||
{
|
|
||||||
socket.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> createSocket(int fd,
|
|
||||||
std::string& errorMsg)
|
|
||||||
{
|
|
||||||
errorMsg.clear();
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> socket = std::make_shared<Socket>(fd);
|
|
||||||
if (!socket->init(errorMsg))
|
|
||||||
{
|
|
||||||
socket.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
* IXSocketFactory.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class Socket;
|
|
||||||
std::shared_ptr<Socket> createSocket(bool tls, std::string& errorMsg);
|
|
||||||
|
|
||||||
std::shared_ptr<Socket> createSocket(int fd, std::string& errorMsg);
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketMbedTLS.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Some code taken from
|
|
||||||
* https://github.com/rottor12/WsClientLib/blob/master/lib/src/WsClientLib.cpp
|
|
||||||
* and mini_client.c example from mbedtls
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocketMbedTLS.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SocketMbedTLS::~SocketMbedTLS()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketMbedTLS::init(const std::string& host, std::string& errMsg)
|
|
||||||
{
|
|
||||||
mbedtls_ssl_init(&_ssl);
|
|
||||||
mbedtls_ssl_config_init(&_conf);
|
|
||||||
mbedtls_ctr_drbg_init(&_ctr_drbg);
|
|
||||||
|
|
||||||
const char *pers = "IXSocketMbedTLS";
|
|
||||||
|
|
||||||
mbedtls_entropy_init(&_entropy);
|
|
||||||
if (mbedtls_ctr_drbg_seed(&_ctr_drbg,
|
|
||||||
mbedtls_entropy_func,
|
|
||||||
&_entropy,
|
|
||||||
(const unsigned char *) pers,
|
|
||||||
strlen(pers)) != 0)
|
|
||||||
{
|
|
||||||
errMsg = "Setting entropy seed failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mbedtls_ssl_config_defaults(&_conf,
|
|
||||||
MBEDTLS_SSL_IS_CLIENT,
|
|
||||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
||||||
MBEDTLS_SSL_PRESET_DEFAULT ) != 0)
|
|
||||||
{
|
|
||||||
errMsg = "Setting config default failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mbedtls_ssl_conf_rng(&_conf, mbedtls_ctr_drbg_random, &_ctr_drbg);
|
|
||||||
|
|
||||||
// FIXME: cert verification is disabled
|
|
||||||
mbedtls_ssl_conf_authmode(&_conf, MBEDTLS_SSL_VERIFY_NONE);
|
|
||||||
|
|
||||||
if (mbedtls_ssl_setup(&_ssl, &_conf) != 0)
|
|
||||||
{
|
|
||||||
errMsg = "SSL setup failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mbedtls_ssl_set_hostname(&_ssl, host.c_str()) != 0)
|
|
||||||
{
|
|
||||||
errMsg = "SNI setup failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketMbedTLS::connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
if (_sockfd == -1) return false;
|
|
||||||
if (!init(host, errMsg)) return false;
|
|
||||||
|
|
||||||
mbedtls_ssl_set_bio(&_ssl, &_sockfd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
|
||||||
|
|
||||||
int res;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
res = mbedtls_ssl_handshake(&_ssl);
|
|
||||||
}
|
|
||||||
while (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE);
|
|
||||||
|
|
||||||
if (res != 0)
|
|
||||||
{
|
|
||||||
char buf[256];
|
|
||||||
mbedtls_strerror(res, buf, sizeof(buf));
|
|
||||||
|
|
||||||
errMsg = "error in handshake : ";
|
|
||||||
errMsg += buf;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketMbedTLS::close()
|
|
||||||
{
|
|
||||||
mbedtls_ssl_free(&_ssl);
|
|
||||||
mbedtls_ssl_config_free(&_conf);
|
|
||||||
mbedtls_ctr_drbg_free(&_ctr_drbg);
|
|
||||||
mbedtls_entropy_free(&_entropy);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketMbedTLS::send(char* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
ssize_t sent = 0;
|
|
||||||
|
|
||||||
while (nbyte > 0)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
ssize_t res = mbedtls_ssl_write(&_ssl, (unsigned char*) buf, nbyte);
|
|
||||||
|
|
||||||
if (res > 0) {
|
|
||||||
nbyte -= res;
|
|
||||||
sent += res;
|
|
||||||
} else if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE) {
|
|
||||||
errno = EWOULDBLOCK;
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketMbedTLS::send(const std::string& buffer)
|
|
||||||
{
|
|
||||||
return send((char*)&buffer[0], buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketMbedTLS::recv(void* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
ssize_t res = mbedtls_ssl_read(&_ssl, (unsigned char*) buf, (int) nbyte);
|
|
||||||
|
|
||||||
if (res > 0)
|
|
||||||
{
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res == MBEDTLS_ERR_SSL_WANT_READ || res == MBEDTLS_ERR_SSL_WANT_WRITE)
|
|
||||||
{
|
|
||||||
errno = EWOULDBLOCK;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketMbedTLS.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include <mbedtls/ctr_drbg.h>
|
|
||||||
#include <mbedtls/debug.h>
|
|
||||||
#include <mbedtls/entropy.h>
|
|
||||||
#include <mbedtls/error.h>
|
|
||||||
#include <mbedtls/net.h>
|
|
||||||
#include <mbedtls/platform.h>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketMbedTLS final : public Socket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SocketMbedTLS() = default;
|
|
||||||
~SocketMbedTLS();
|
|
||||||
|
|
||||||
virtual bool connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested) final;
|
|
||||||
virtual void close() final;
|
|
||||||
|
|
||||||
virtual ssize_t send(char* buffer, size_t length) final;
|
|
||||||
virtual ssize_t send(const std::string& buffer) final;
|
|
||||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
mbedtls_ssl_context _ssl;
|
|
||||||
mbedtls_ssl_config _conf;
|
|
||||||
mbedtls_entropy_context _entropy;
|
|
||||||
mbedtls_ctr_drbg_context _ctr_drbg;
|
|
||||||
|
|
||||||
std::mutex _mutex;
|
|
||||||
|
|
||||||
bool init(const std::string& host, std::string& errMsg);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketOpenSSL.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Adapted from Satori SDK OpenSSL code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocketOpenSSL.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include <openssl/x509v3.h>
|
|
||||||
|
|
||||||
#include <fnmatch.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#define socketerrno errno
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
|
||||||
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
|
||||||
|
|
||||||
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
|
||||||
_ssl_connection(nullptr),
|
|
||||||
_ssl_context(nullptr)
|
|
||||||
{
|
|
||||||
std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketOpenSSL::~SocketOpenSSL()
|
|
||||||
{
|
|
||||||
SocketOpenSSL::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketOpenSSL::openSSLInitialize()
|
|
||||||
{
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
||||||
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
|
|
||||||
#else
|
|
||||||
(void) OPENSSL_config(nullptr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
(void) OpenSSL_add_ssl_algorithms();
|
|
||||||
(void) SSL_load_error_strings();
|
|
||||||
|
|
||||||
_openSSLInitializationSuccessful = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SocketOpenSSL::getSSLError(int ret)
|
|
||||||
{
|
|
||||||
unsigned long e;
|
|
||||||
|
|
||||||
int err = SSL_get_error(_ssl_connection, ret);
|
|
||||||
|
|
||||||
if (err == SSL_ERROR_WANT_CONNECT || err == SSL_ERROR_WANT_ACCEPT)
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - connection failure";
|
|
||||||
}
|
|
||||||
else if (err == SSL_ERROR_WANT_X509_LOOKUP)
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - x509 error";
|
|
||||||
}
|
|
||||||
else if (err == SSL_ERROR_SYSCALL)
|
|
||||||
{
|
|
||||||
e = ERR_get_error();
|
|
||||||
if (e > 0)
|
|
||||||
{
|
|
||||||
std::string errMsg("OpenSSL failed - ");
|
|
||||||
errMsg += ERR_error_string(e, nullptr);
|
|
||||||
return errMsg;
|
|
||||||
}
|
|
||||||
else if (e == 0 && ret == 0)
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - received early EOF";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - underlying BIO reported an I/O error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (err == SSL_ERROR_SSL)
|
|
||||||
{
|
|
||||||
e = ERR_get_error();
|
|
||||||
std::string errMsg("OpenSSL failed - ");
|
|
||||||
errMsg += ERR_error_string(e, nullptr);
|
|
||||||
return errMsg;
|
|
||||||
}
|
|
||||||
else if (err == SSL_ERROR_NONE)
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - err none";
|
|
||||||
}
|
|
||||||
else if (err == SSL_ERROR_ZERO_RETURN)
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - err zero return";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "OpenSSL failed - unknown error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_CTX* SocketOpenSSL::openSSLCreateContext(std::string& errMsg)
|
|
||||||
{
|
|
||||||
const SSL_METHOD* method = SSLv23_client_method();
|
|
||||||
if (method == nullptr)
|
|
||||||
{
|
|
||||||
errMsg = "SSLv23_client_method failure";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
_ssl_method = method;
|
|
||||||
|
|
||||||
SSL_CTX* ctx = SSL_CTX_new(_ssl_method);
|
|
||||||
if (ctx)
|
|
||||||
{
|
|
||||||
// To skip verification, pass in SSL_VERIFY_NONE
|
|
||||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
|
|
||||||
[](int preverify, X509_STORE_CTX*) -> int
|
|
||||||
{
|
|
||||||
return preverify;
|
|
||||||
});
|
|
||||||
|
|
||||||
SSL_CTX_set_verify_depth(ctx, 4);
|
|
||||||
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether a hostname matches a pattern
|
|
||||||
*/
|
|
||||||
bool SocketOpenSSL::checkHost(const std::string& host, const char *pattern)
|
|
||||||
{
|
|
||||||
return fnmatch(pattern, host.c_str(), 0) != FNM_NOMATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketOpenSSL::openSSLCheckServerCert(SSL *ssl,
|
|
||||||
const std::string& hostname,
|
|
||||||
std::string& errMsg)
|
|
||||||
{
|
|
||||||
X509 *server_cert = SSL_get_peer_certificate(ssl);
|
|
||||||
if (server_cert == nullptr)
|
|
||||||
{
|
|
||||||
errMsg = "OpenSSL failed - peer didn't present a X509 certificate.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
||||||
// Check server name
|
|
||||||
bool hostname_verifies_ok = false;
|
|
||||||
STACK_OF(GENERAL_NAME) *san_names =
|
|
||||||
(STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert,
|
|
||||||
NID_subject_alt_name, NULL, NULL);
|
|
||||||
if (san_names)
|
|
||||||
{
|
|
||||||
for (int i=0; i<sk_GENERAL_NAME_num(san_names); i++)
|
|
||||||
{
|
|
||||||
const GENERAL_NAME *sk_name = sk_GENERAL_NAME_value(san_names, i);
|
|
||||||
if (sk_name->type == GEN_DNS)
|
|
||||||
{
|
|
||||||
char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName);
|
|
||||||
if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
|
|
||||||
checkHost(hostname, name))
|
|
||||||
{
|
|
||||||
hostname_verifies_ok = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
|
|
||||||
|
|
||||||
if (!hostname_verifies_ok)
|
|
||||||
{
|
|
||||||
int cn_pos = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert),
|
|
||||||
NID_commonName, -1);
|
|
||||||
if (cn_pos)
|
|
||||||
{
|
|
||||||
X509_NAME_ENTRY *cn_entry = X509_NAME_get_entry(
|
|
||||||
X509_get_subject_name((X509 *)server_cert), cn_pos);
|
|
||||||
|
|
||||||
if (cn_entry)
|
|
||||||
{
|
|
||||||
ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
|
|
||||||
char *cn = (char *)ASN1_STRING_data(cn_asn1);
|
|
||||||
|
|
||||||
if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) &&
|
|
||||||
checkHost(hostname, cn))
|
|
||||||
{
|
|
||||||
hostname_verifies_ok = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hostname_verifies_ok)
|
|
||||||
{
|
|
||||||
errMsg = "OpenSSL failed - certificate was issued for a different domain.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
X509_free(server_cert);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ERR_clear_error();
|
|
||||||
int connect_result = SSL_connect(_ssl_connection);
|
|
||||||
if (connect_result == 1)
|
|
||||||
{
|
|
||||||
return openSSLCheckServerCert(_ssl_connection, host, errMsg);
|
|
||||||
}
|
|
||||||
int reason = SSL_get_error(_ssl_connection, connect_result);
|
|
||||||
|
|
||||||
bool rc = false;
|
|
||||||
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
|
||||||
{
|
|
||||||
rc = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errMsg = getSSLError(connect_result);
|
|
||||||
rc = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rc)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketOpenSSL::connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
bool handshakeSuccessful = false;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
if (!_openSSLInitializationSuccessful)
|
|
||||||
{
|
|
||||||
errMsg = "OPENSSL_init_ssl failure";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
if (_sockfd == -1) return false;
|
|
||||||
|
|
||||||
_ssl_context = openSSLCreateContext(errMsg);
|
|
||||||
if (_ssl_context == nullptr)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ERR_clear_error();
|
|
||||||
int cert_load_result = SSL_CTX_set_default_verify_paths(_ssl_context);
|
|
||||||
if (cert_load_result == 0)
|
|
||||||
{
|
|
||||||
unsigned long ssl_err = ERR_get_error();
|
|
||||||
errMsg = "OpenSSL failed - SSL_CTX_default_verify_paths loading failed: ";
|
|
||||||
errMsg += ERR_error_string(ssl_err, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ssl_connection = SSL_new(_ssl_context);
|
|
||||||
if (_ssl_connection == nullptr)
|
|
||||||
{
|
|
||||||
errMsg = "OpenSSL failed to connect";
|
|
||||||
SSL_CTX_free(_ssl_context);
|
|
||||||
_ssl_context = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SSL_set_fd(_ssl_connection, _sockfd);
|
|
||||||
|
|
||||||
// SNI support
|
|
||||||
SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
||||||
// Support for server name verification
|
|
||||||
// (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);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
handshakeSuccessful = openSSLHandshake(host, errMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handshakeSuccessful)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketOpenSSL::close()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
if (_ssl_connection != nullptr)
|
|
||||||
{
|
|
||||||
SSL_free(_ssl_connection);
|
|
||||||
_ssl_connection = nullptr;
|
|
||||||
}
|
|
||||||
if (_ssl_context != nullptr)
|
|
||||||
{
|
|
||||||
SSL_CTX_free(_ssl_context);
|
|
||||||
_ssl_context = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
ssize_t sent = 0;
|
|
||||||
|
|
||||||
while (nbyte > 0)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ERR_clear_error();
|
|
||||||
ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
|
|
||||||
int reason = SSL_get_error(_ssl_connection, (int) write_result);
|
|
||||||
|
|
||||||
if (reason == SSL_ERROR_NONE) {
|
|
||||||
nbyte -= write_result;
|
|
||||||
sent += write_result;
|
|
||||||
} else if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE) {
|
|
||||||
errno = EWOULDBLOCK;
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketOpenSSL::send(const std::string& buffer)
|
|
||||||
{
|
|
||||||
return send((char*)&buffer[0], buffer.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_mutex);
|
|
||||||
|
|
||||||
if (_ssl_connection == nullptr || _ssl_context == nullptr)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ERR_clear_error();
|
|
||||||
ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
|
|
||||||
|
|
||||||
if (read_result > 0)
|
|
||||||
{
|
|
||||||
return read_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int reason = SSL_get_error(_ssl_connection, (int) read_result);
|
|
||||||
|
|
||||||
if (reason == SSL_ERROR_WANT_READ || reason == SSL_ERROR_WANT_WRITE)
|
|
||||||
{
|
|
||||||
errno = EWOULDBLOCK;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketOpenSSL.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <openssl/bio.h>
|
|
||||||
#include <openssl/conf.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/hmac.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketOpenSSL final : public Socket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SocketOpenSSL(int fd = -1);
|
|
||||||
~SocketOpenSSL();
|
|
||||||
|
|
||||||
virtual bool connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg,
|
|
||||||
const CancellationRequest& isCancellationRequested) final;
|
|
||||||
virtual void close() final;
|
|
||||||
|
|
||||||
virtual ssize_t send(char* buffer, size_t length) final;
|
|
||||||
virtual ssize_t send(const std::string& buffer) final;
|
|
||||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void openSSLInitialize();
|
|
||||||
std::string getSSLError(int ret);
|
|
||||||
SSL_CTX* openSSLCreateContext(std::string& errMsg);
|
|
||||||
bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
|
|
||||||
bool openSSLCheckServerCert(SSL* ssl, const std::string& hostname, std::string& errMsg);
|
|
||||||
bool checkHost(const std::string& host, const char* pattern);
|
|
||||||
|
|
||||||
SSL* _ssl_connection;
|
|
||||||
SSL_CTX* _ssl_context;
|
|
||||||
const SSL_METHOD* _ssl_method;
|
|
||||||
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
|
||||||
|
|
||||||
static std::once_flag _openSSLInitFlag;
|
|
||||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketSChannel.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* See https://docs.microsoft.com/en-us/windows/desktop/WinSock/using-secure-socket-extensions
|
|
||||||
*
|
|
||||||
* https://github.com/pauldotknopf/WindowsSDK7-Samples/blob/master/netds/winsock/securesocket/stcpclient/tcpclient.c
|
|
||||||
*
|
|
||||||
* This is the right example to look at:
|
|
||||||
* https://www.codeproject.com/Articles/1000189/A-Working-TCP-Client-and-Server-With-SSL
|
|
||||||
*/
|
|
||||||
#include "IXSocketSChannel.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <basetsd.h>
|
|
||||||
# include <WinSock2.h>
|
|
||||||
# include <ws2def.h>
|
|
||||||
# include <WS2tcpip.h>
|
|
||||||
# include <schannel.h>
|
|
||||||
# include <io.h>
|
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
|
|
||||||
#ifndef UNICODE
|
|
||||||
#define UNICODE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <mstcpip.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <rpc.h>
|
|
||||||
#include <ntdsapi.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <tchar.h>
|
|
||||||
|
|
||||||
#define RECV_DATA_BUF_SIZE 256
|
|
||||||
|
|
||||||
// Link with ws2_32.lib
|
|
||||||
#pragma comment(lib, "Ws2_32.lib")
|
|
||||||
|
|
||||||
// link with fwpuclnt.lib for Winsock secure socket extensions
|
|
||||||
#pragma comment(lib, "fwpuclnt.lib")
|
|
||||||
|
|
||||||
// link with ntdsapi.lib for DsMakeSpn function
|
|
||||||
#pragma comment(lib, "ntdsapi.lib")
|
|
||||||
|
|
||||||
// The following function assumes that Winsock
|
|
||||||
// has already been initialized
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#else
|
|
||||||
# error("This file should only be built on Windows")
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
SocketSChannel::SocketSChannel()
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketSChannel::~SocketSChannel()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SocketSChannel::connect(const std::string& host,
|
|
||||||
int port,
|
|
||||||
std::string& errMsg)
|
|
||||||
{
|
|
||||||
return Socket::connect(host, port, errMsg, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void SocketSChannel::secureSocket()
|
|
||||||
{
|
|
||||||
// there will be a lot to do here ...
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketSChannel::close()
|
|
||||||
{
|
|
||||||
Socket::close();
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketSChannel::send(char* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
return Socket::send(buf, nbyte);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketSChannel::send(const std::string& buffer)
|
|
||||||
{
|
|
||||||
return Socket::send(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t SocketSChannel::recv(void* buf, size_t nbyte)
|
|
||||||
{
|
|
||||||
return Socket::recv(buf, nbyte);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketSChannel.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSocket.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketSChannel final : public Socket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SocketSChannel();
|
|
||||||
~SocketSChannel();
|
|
||||||
|
|
||||||
virtual bool connect(const std::string& host, int port, std::string& errMsg) final;
|
|
||||||
virtual void close() final;
|
|
||||||
|
|
||||||
// The important override
|
|
||||||
virtual void secureSocket() final;
|
|
||||||
|
|
||||||
virtual ssize_t send(char* buffer, size_t length) final;
|
|
||||||
virtual ssize_t send(const std::string& buffer) final;
|
|
||||||
virtual ssize_t recv(void* buffer, size_t length) final;
|
|
||||||
|
|
||||||
private:
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketServer.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXSocketServer.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <future>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const int SocketServer::kDefaultPort(8080);
|
|
||||||
const std::string SocketServer::kDefaultHost("127.0.0.1");
|
|
||||||
const int SocketServer::kDefaultTcpBacklog(5);
|
|
||||||
const size_t SocketServer::kDefaultMaxConnections(32);
|
|
||||||
|
|
||||||
SocketServer::SocketServer(int port,
|
|
||||||
const std::string& host,
|
|
||||||
int backlog,
|
|
||||||
size_t maxConnections) :
|
|
||||||
_port(port),
|
|
||||||
_host(host),
|
|
||||||
_backlog(backlog),
|
|
||||||
_maxConnections(maxConnections),
|
|
||||||
_serverFd(-1),
|
|
||||||
_stop(false),
|
|
||||||
_stopGc(false),
|
|
||||||
_connectionStateFactory(&ConnectionState::createConnectionState)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketServer::~SocketServer()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::logError(const std::string& str)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_logMutex);
|
|
||||||
std::cerr << str << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::logInfo(const std::string& str)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_logMutex);
|
|
||||||
std::cout << str << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, std::string> SocketServer::listen()
|
|
||||||
{
|
|
||||||
struct sockaddr_in server; // server address information
|
|
||||||
|
|
||||||
// Get a socket for accepting connections.
|
|
||||||
if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::listen() error creating socket): "
|
|
||||||
<< strerror(Socket::getErrno());
|
|
||||||
|
|
||||||
return std::make_pair(false, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make that socket reusable. (allow restarting this server at will)
|
|
||||||
int enable = 1;
|
|
||||||
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR,
|
|
||||||
(char*) &enable, sizeof(enable)) < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
|
|
||||||
<< "at address " << _host << ":" << _port
|
|
||||||
<< " : " << strerror(Socket::getErrno());
|
|
||||||
|
|
||||||
Socket::closeSocket(_serverFd);
|
|
||||||
return std::make_pair(false, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind the socket to the server address.
|
|
||||||
server.sin_family = AF_INET;
|
|
||||||
server.sin_port = htons(_port);
|
|
||||||
|
|
||||||
// Using INADDR_ANY trigger a pop-up box as binding to any address is detected
|
|
||||||
// by the osx firewall. We need to codesign the binary with a self-signed cert
|
|
||||||
// to allow that, but this is a bit of a pain. (this is what node or python would do).
|
|
||||||
//
|
|
||||||
// Using INADDR_LOOPBACK also does not work ... while it should.
|
|
||||||
// We default to 127.0.0.1 (localhost)
|
|
||||||
//
|
|
||||||
server.sin_addr.s_addr = inet_addr(_host.c_str());
|
|
||||||
|
|
||||||
if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::listen() error calling bind "
|
|
||||||
<< "at address " << _host << ":" << _port
|
|
||||||
<< " : " << strerror(Socket::getErrno());
|
|
||||||
|
|
||||||
Socket::closeSocket(_serverFd);
|
|
||||||
return std::make_pair(false, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listen for connections. Specify the tcp backlog.
|
|
||||||
//
|
|
||||||
if (::listen(_serverFd, _backlog) < 0)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::listen() error calling listen "
|
|
||||||
<< "at address " << _host << ":" << _port
|
|
||||||
<< " : " << strerror(Socket::getErrno());
|
|
||||||
|
|
||||||
Socket::closeSocket(_serverFd);
|
|
||||||
return std::make_pair(false, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(true, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::start()
|
|
||||||
{
|
|
||||||
if (!_thread.joinable())
|
|
||||||
{
|
|
||||||
_thread = std::thread(&SocketServer::run, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_gcThread.joinable())
|
|
||||||
{
|
|
||||||
_gcThread = std::thread(&SocketServer::runGC, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::wait()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
||||||
_conditionVariable.wait(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::stopAcceptingConnections()
|
|
||||||
{
|
|
||||||
_stop = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::stop()
|
|
||||||
{
|
|
||||||
// Stop accepting connections, and close the 'accept' thread
|
|
||||||
if (_thread.joinable())
|
|
||||||
{
|
|
||||||
_stop = true;
|
|
||||||
_thread.join();
|
|
||||||
_stop = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join all threads and make sure that all connections are terminated
|
|
||||||
if (_gcThread.joinable())
|
|
||||||
{
|
|
||||||
_stopGc = true;
|
|
||||||
_gcThread.join();
|
|
||||||
_stopGc = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_conditionVariable.notify_one();
|
|
||||||
Socket::closeSocket(_serverFd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::setConnectionStateFactory(
|
|
||||||
const ConnectionStateFactory& connectionStateFactory)
|
|
||||||
{
|
|
||||||
_connectionStateFactory = connectionStateFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// join the threads for connections that have been closed
|
|
||||||
//
|
|
||||||
// When a connection is closed by a client, the connection state terminated
|
|
||||||
// field becomes true, and we can use that to know that we can join that thread
|
|
||||||
// and remove it from our _connectionsThreads data structure (a list).
|
|
||||||
//
|
|
||||||
void SocketServer::closeTerminatedThreads()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
|
||||||
auto it = _connectionsThreads.begin();
|
|
||||||
auto itEnd = _connectionsThreads.end();
|
|
||||||
|
|
||||||
while (it != itEnd)
|
|
||||||
{
|
|
||||||
auto& connectionState = it->first;
|
|
||||||
auto& thread = it->second;
|
|
||||||
|
|
||||||
if (!connectionState->isTerminated())
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thread.joinable()) thread.join();
|
|
||||||
it = _connectionsThreads.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::run()
|
|
||||||
{
|
|
||||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
|
||||||
SocketConnect::configure(_serverFd);
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// Use select to check whether a new connection is in progress
|
|
||||||
fd_set rfds;
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = 0;
|
|
||||||
timeout.tv_usec = 10 * 1000; // 10ms timeout
|
|
||||||
|
|
||||||
FD_ZERO(&rfds);
|
|
||||||
FD_SET(_serverFd, &rfds);
|
|
||||||
|
|
||||||
if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
|
|
||||||
(errno == EBADF || errno == EINVAL))
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::run() error in select: "
|
|
||||||
<< strerror(Socket::getErrno());
|
|
||||||
logError(ss.str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FD_ISSET(_serverFd, &rfds))
|
|
||||||
{
|
|
||||||
// We reached the select timeout, and no new connections are pending
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept a connection.
|
|
||||||
struct sockaddr_in client; // client address information
|
|
||||||
int clientFd; // socket connected to client
|
|
||||||
socklen_t addressLen = sizeof(client);
|
|
||||||
memset(&client, 0, sizeof(client));
|
|
||||||
|
|
||||||
if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
|
|
||||||
{
|
|
||||||
if (!Socket::isWaitNeeded())
|
|
||||||
{
|
|
||||||
// FIXME: that error should be propagated
|
|
||||||
int err = Socket::getErrno();
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::run() error accepting connection: "
|
|
||||||
<< err << ", " << strerror(err);
|
|
||||||
logError(ss.str());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getConnectedClientsCount() >= _maxConnections)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SocketServer::run() reached max connections = "
|
|
||||||
<< _maxConnections << ". "
|
|
||||||
<< "Not accepting connection";
|
|
||||||
logError(ss.str());
|
|
||||||
|
|
||||||
Socket::closeSocket(clientFd);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<ConnectionState> connectionState;
|
|
||||||
if (_connectionStateFactory)
|
|
||||||
{
|
|
||||||
connectionState = _connectionStateFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_stop) return;
|
|
||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
|
||||||
_connectionsThreads.push_back(std::make_pair(
|
|
||||||
connectionState,
|
|
||||||
std::thread(&SocketServer::handleConnection,
|
|
||||||
this,
|
|
||||||
clientFd,
|
|
||||||
connectionState)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SocketServer::getConnectionsThreadsCount()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_connectionsThreadsMutex);
|
|
||||||
return _connectionsThreads.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketServer::runGC()
|
|
||||||
{
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
// Garbage collection to shutdown/join threads for closed connections.
|
|
||||||
closeTerminatedThreads();
|
|
||||||
|
|
||||||
// We quit this thread if all connections are closed and we received
|
|
||||||
// a stop request by setting _stopGc to true.
|
|
||||||
if (_stopGc && getConnectionsThreadsCount() == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep a little bit then keep cleaning up
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSocketServer.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXConnectionState.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <functional>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <utility> // pair
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class SocketServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
|
|
||||||
|
|
||||||
// Each connection is handled by its own worker thread.
|
|
||||||
// We use a list as we only care about remove and append operations.
|
|
||||||
using ConnectionThreads =
|
|
||||||
std::list<std::pair<std::shared_ptr<ConnectionState>, std::thread>>;
|
|
||||||
|
|
||||||
SocketServer(int port = SocketServer::kDefaultPort,
|
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
|
||||||
virtual ~SocketServer();
|
|
||||||
virtual void stop();
|
|
||||||
|
|
||||||
// It is possible to override ConnectionState through inheritance
|
|
||||||
// this method allows user to change the factory by returning an object
|
|
||||||
// that inherits from ConnectionState but has its own methods.
|
|
||||||
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
|
|
||||||
|
|
||||||
const static int kDefaultPort;
|
|
||||||
const static std::string kDefaultHost;
|
|
||||||
const static int kDefaultTcpBacklog;
|
|
||||||
const static size_t kDefaultMaxConnections;
|
|
||||||
|
|
||||||
void start();
|
|
||||||
std::pair<bool, std::string> listen();
|
|
||||||
void wait();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Logging
|
|
||||||
void logError(const std::string& str);
|
|
||||||
void logInfo(const std::string& str);
|
|
||||||
|
|
||||||
void stopAcceptingConnections();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Member variables
|
|
||||||
int _port;
|
|
||||||
std::string _host;
|
|
||||||
int _backlog;
|
|
||||||
size_t _maxConnections;
|
|
||||||
|
|
||||||
// socket for accepting connections
|
|
||||||
int _serverFd;
|
|
||||||
|
|
||||||
std::mutex _logMutex;
|
|
||||||
|
|
||||||
// background thread to wait for incoming connections
|
|
||||||
std::atomic<bool> _stop;
|
|
||||||
std::thread _thread;
|
|
||||||
void run();
|
|
||||||
|
|
||||||
// background thread to cleanup (join) terminated threads
|
|
||||||
std::atomic<bool> _stopGc;
|
|
||||||
std::thread _gcThread;
|
|
||||||
void runGC();
|
|
||||||
|
|
||||||
// the list of (connectionState, threads) for each connections
|
|
||||||
ConnectionThreads _connectionsThreads;
|
|
||||||
std::mutex _connectionsThreadsMutex;
|
|
||||||
|
|
||||||
// used to have the main control thread for a server
|
|
||||||
// wait for a 'terminate' notification without busy polling
|
|
||||||
std::condition_variable _conditionVariable;
|
|
||||||
std::mutex _conditionVariableMutex;
|
|
||||||
|
|
||||||
// the factory to create ConnectionState objects
|
|
||||||
ConnectionStateFactory _connectionStateFactory;
|
|
||||||
|
|
||||||
virtual void handleConnection(int fd, std::shared_ptr<ConnectionState> connectionState) = 0;
|
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
|
||||||
|
|
||||||
// Returns true if all connection threads are joined
|
|
||||||
void closeTerminatedThreads();
|
|
||||||
size_t getConnectionsThreadsCount();
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXUrlParser.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXUrlParser.h"
|
|
||||||
#include "LUrlParser.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
bool UrlParser::parse(const std::string& url,
|
|
||||||
std::string& protocol,
|
|
||||||
std::string& host,
|
|
||||||
std::string& path,
|
|
||||||
std::string& query,
|
|
||||||
int& port)
|
|
||||||
{
|
|
||||||
LUrlParser::clParseURL res = LUrlParser::clParseURL::ParseURL(url);
|
|
||||||
|
|
||||||
if (!res.IsValid())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol = res.m_Scheme;
|
|
||||||
host = res.m_Host;
|
|
||||||
path = res.m_Path;
|
|
||||||
query = res.m_Query;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.empty())
|
|
||||||
{
|
|
||||||
path = "/";
|
|
||||||
}
|
|
||||||
else if (path[0] != '/')
|
|
||||||
{
|
|
||||||
path = '/' + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query.empty())
|
|
||||||
{
|
|
||||||
path += "?";
|
|
||||||
path += query;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXUrlParser.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class UrlParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static bool parse(const std::string& url,
|
|
||||||
std::string& protocol,
|
|
||||||
std::string& host,
|
|
||||||
std::string& path,
|
|
||||||
std::string& query,
|
|
||||||
int& port);
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,475 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocket.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocket.h"
|
|
||||||
#include "IXSetThreadName.h"
|
|
||||||
#include "IXWebSocketHandshake.h"
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
uint64_t calculateRetryWaitMilliseconds(uint32_t retry_count)
|
|
||||||
{
|
|
||||||
uint64_t wait_time;
|
|
||||||
|
|
||||||
if (retry_count <= 6)
|
|
||||||
{
|
|
||||||
// max wait_time is 6400 ms (2 ^ 6 = 64)
|
|
||||||
wait_time = ((uint64_t)std::pow(2, retry_count) * 100L);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wait_time = 10 * 1000; // 10 sec
|
|
||||||
}
|
|
||||||
|
|
||||||
return wait_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
|
|
||||||
const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
|
|
||||||
const int WebSocket::kDefaultPingIntervalSecs(-1);
|
|
||||||
const int WebSocket::kDefaultPingTimeoutSecs(-1);
|
|
||||||
const bool WebSocket::kDefaultEnablePong(true);
|
|
||||||
|
|
||||||
WebSocket::WebSocket() :
|
|
||||||
_onMessageCallback(OnMessageCallback()),
|
|
||||||
_stop(false),
|
|
||||||
_automaticReconnection(true),
|
|
||||||
_handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
|
|
||||||
_enablePong(kDefaultEnablePong),
|
|
||||||
_pingIntervalSecs(kDefaultPingIntervalSecs),
|
|
||||||
_pingTimeoutSecs(kDefaultPingTimeoutSecs)
|
|
||||||
{
|
|
||||||
_ws.setOnCloseCallback(
|
|
||||||
[this](uint16_t code, const std::string& reason, size_t wireSize, bool remote)
|
|
||||||
{
|
|
||||||
_onMessageCallback(WebSocketMessageType::Close, "", wireSize,
|
|
||||||
WebSocketErrorInfo(), WebSocketOpenInfo(),
|
|
||||||
WebSocketCloseInfo(code, reason, remote));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocket::~WebSocket()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setUrl(const std::string& url)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& WebSocket::getUrl() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
return _url;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_perMessageDeflateOptions = perMessageDeflateOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const WebSocketPerMessageDeflateOptions& WebSocket::getPerMessageDeflateOptions() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
return _perMessageDeflateOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setHeartBeatPeriod(int heartBeatPeriodSecs)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_pingIntervalSecs = heartBeatPeriodSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebSocket::getHeartBeatPeriod() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
return _pingIntervalSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setPingInterval(int pingIntervalSecs)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_pingIntervalSecs = pingIntervalSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebSocket::getPingInterval() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
return _pingIntervalSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setPingTimeout(int pingTimeoutSecs)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_pingTimeoutSecs = pingTimeoutSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebSocket::getPingTimeout() const
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
return _pingTimeoutSecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::enablePong()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_enablePong = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::disablePong()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_enablePong = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::start()
|
|
||||||
{
|
|
||||||
if (_thread.joinable()) return; // we've already been started
|
|
||||||
|
|
||||||
_thread = std::thread(&WebSocket::run, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::stop(uint16_t code,
|
|
||||||
const std::string& reason)
|
|
||||||
{
|
|
||||||
close(code, reason);
|
|
||||||
|
|
||||||
if (_thread.joinable())
|
|
||||||
{
|
|
||||||
// wait until working thread will exit
|
|
||||||
// it will exit after close operation is finished
|
|
||||||
_stop = true;
|
|
||||||
_thread.join();
|
|
||||||
_stop = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult WebSocket::connect(int timeoutSecs)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_ws.configure(_perMessageDeflateOptions,
|
|
||||||
_enablePong,
|
|
||||||
_pingIntervalSecs,
|
|
||||||
_pingTimeoutSecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
|
|
||||||
if (!status.success)
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
|
||||||
WebSocketErrorInfo(),
|
|
||||||
WebSocketOpenInfo(status.uri, status.headers),
|
|
||||||
WebSocketCloseInfo());
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult WebSocket::connectToSocket(int fd, int timeoutSecs)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
|
||||||
_ws.configure(_perMessageDeflateOptions,
|
|
||||||
_enablePong,
|
|
||||||
_pingIntervalSecs,
|
|
||||||
_pingTimeoutSecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
|
|
||||||
if (!status.success)
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMessageCallback(WebSocketMessageType::Open, "", 0,
|
|
||||||
WebSocketErrorInfo(),
|
|
||||||
WebSocketOpenInfo(status.uri, status.headers),
|
|
||||||
WebSocketCloseInfo());
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocket::isConnected() const
|
|
||||||
{
|
|
||||||
return getReadyState() == ReadyState::Open;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocket::isClosing() const
|
|
||||||
{
|
|
||||||
return getReadyState() == ReadyState::Closing;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::close(uint16_t code,
|
|
||||||
const std::string& reason)
|
|
||||||
{
|
|
||||||
_ws.close(code, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::checkConnection(bool firstConnectionAttempt)
|
|
||||||
{
|
|
||||||
using millis = std::chrono::duration<double, std::milli>;
|
|
||||||
|
|
||||||
uint32_t retries = 0;
|
|
||||||
millis duration(0);
|
|
||||||
|
|
||||||
// Try to connect perpertually
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (isConnected() || isClosing() || _stop)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstConnectionAttempt && !_automaticReconnection)
|
|
||||||
{
|
|
||||||
// Do not attempt to reconnect
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstConnectionAttempt = false;
|
|
||||||
|
|
||||||
// Only sleep if we are retrying
|
|
||||||
if (duration.count() > 0)
|
|
||||||
{
|
|
||||||
// to do: make sleeping conditional
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to connect synchronously
|
|
||||||
ix::WebSocketInitResult status = connect(_handshakeTimeoutSecs);
|
|
||||||
|
|
||||||
if (!status.success)
|
|
||||||
{
|
|
||||||
WebSocketErrorInfo connectErr;
|
|
||||||
|
|
||||||
if (_automaticReconnection)
|
|
||||||
{
|
|
||||||
duration = millis(calculateRetryWaitMilliseconds(retries++));
|
|
||||||
|
|
||||||
connectErr.wait_time = duration.count();
|
|
||||||
connectErr.retries = retries;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectErr.reason = status.errorStr;
|
|
||||||
connectErr.http_status = status.http_status;
|
|
||||||
|
|
||||||
_onMessageCallback(WebSocketMessageType::Error, "", 0,
|
|
||||||
connectErr, WebSocketOpenInfo(),
|
|
||||||
WebSocketCloseInfo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::run()
|
|
||||||
{
|
|
||||||
setThreadName(getUrl());
|
|
||||||
|
|
||||||
bool firstConnectionAttempt = true;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
// 1. Make sure we are always connected
|
|
||||||
checkConnection(firstConnectionAttempt);
|
|
||||||
|
|
||||||
firstConnectionAttempt = false;
|
|
||||||
|
|
||||||
// if here we are closed then checkConnection was not able to connect
|
|
||||||
if (getReadyState() == ReadyState::Closed)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can avoid to poll if we want to stop and are not closing
|
|
||||||
if (_stop && !isClosing()) break;
|
|
||||||
|
|
||||||
// 2. Poll to see if there's any new data available
|
|
||||||
WebSocketTransport::PollResult pollResult = _ws.poll();
|
|
||||||
|
|
||||||
// 3. Dispatch the incoming messages
|
|
||||||
_ws.dispatch(
|
|
||||||
pollResult,
|
|
||||||
[this](const std::string& msg,
|
|
||||||
size_t wireSize,
|
|
||||||
bool decompressionError,
|
|
||||||
WebSocketTransport::MessageKind messageKind)
|
|
||||||
{
|
|
||||||
WebSocketMessageType webSocketMessageType;
|
|
||||||
switch (messageKind)
|
|
||||||
{
|
|
||||||
default:
|
|
||||||
case WebSocketTransport::MessageKind::MSG:
|
|
||||||
{
|
|
||||||
webSocketMessageType = WebSocketMessageType::Message;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case WebSocketTransport::MessageKind::PING:
|
|
||||||
{
|
|
||||||
webSocketMessageType = WebSocketMessageType::Ping;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case WebSocketTransport::MessageKind::PONG:
|
|
||||||
{
|
|
||||||
webSocketMessageType = WebSocketMessageType::Pong;
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case WebSocketTransport::MessageKind::FRAGMENT:
|
|
||||||
{
|
|
||||||
webSocketMessageType = WebSocketMessageType::Fragment;
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketErrorInfo webSocketErrorInfo;
|
|
||||||
webSocketErrorInfo.decompressionError = decompressionError;
|
|
||||||
|
|
||||||
_onMessageCallback(webSocketMessageType, msg, wireSize,
|
|
||||||
webSocketErrorInfo, WebSocketOpenInfo(),
|
|
||||||
WebSocketCloseInfo());
|
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setOnMessageCallback(const OnMessageCallback& callback)
|
|
||||||
{
|
|
||||||
_onMessageCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
|
|
||||||
{
|
|
||||||
_onTrafficTrackerCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::resetTrafficTrackerCallback()
|
|
||||||
{
|
|
||||||
setTrafficTrackerCallback(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::invokeTrafficTrackerCallback(size_t size, bool incoming)
|
|
||||||
{
|
|
||||||
if (_onTrafficTrackerCallback)
|
|
||||||
{
|
|
||||||
_onTrafficTrackerCallback(size, incoming);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::send(const std::string& data,
|
|
||||||
const OnProgressCallback& onProgressCallback)
|
|
||||||
{
|
|
||||||
return sendMessage(data, SendMessageKind::Binary, onProgressCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendText(const std::string& text,
|
|
||||||
const OnProgressCallback& onProgressCallback)
|
|
||||||
{
|
|
||||||
return sendMessage(text, SendMessageKind::Text, onProgressCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
|
||||||
{
|
|
||||||
// Standard limit ping message size
|
|
||||||
constexpr size_t pingMaxPayloadSize = 125;
|
|
||||||
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
|
||||||
|
|
||||||
return sendMessage(text, SendMessageKind::Ping);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
|
||||||
SendMessageKind sendMessageKind,
|
|
||||||
const OnProgressCallback& onProgressCallback)
|
|
||||||
{
|
|
||||||
if (!isConnected()) return WebSocketSendInfo(false);
|
|
||||||
|
|
||||||
//
|
|
||||||
// It is OK to read and write on the same socket in 2 different threads.
|
|
||||||
// https://stackoverflow.com/questions/1981372/are-parallel-calls-to-send-recv-on-the-same-socket-valid
|
|
||||||
//
|
|
||||||
// This makes it so that messages are sent right away, and we dont need
|
|
||||||
// a timeout while we poll to keep wake ups to a minimum (which helps
|
|
||||||
// with battery life), and use the system select call to notify us when
|
|
||||||
// incoming messages are arriving / there's data to be received.
|
|
||||||
//
|
|
||||||
std::lock_guard<std::mutex> lock(_writeMutex);
|
|
||||||
WebSocketSendInfo webSocketSendInfo;
|
|
||||||
|
|
||||||
switch (sendMessageKind)
|
|
||||||
{
|
|
||||||
case SendMessageKind::Text:
|
|
||||||
{
|
|
||||||
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case SendMessageKind::Binary:
|
|
||||||
{
|
|
||||||
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case SendMessageKind::Ping:
|
|
||||||
{
|
|
||||||
webSocketSendInfo = _ws.sendPing(text);
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
|
||||||
|
|
||||||
return webSocketSendInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadyState WebSocket::getReadyState() const
|
|
||||||
{
|
|
||||||
switch (_ws.getReadyState())
|
|
||||||
{
|
|
||||||
case ix::WebSocketTransport::ReadyState::OPEN : return ReadyState::Open;
|
|
||||||
case ix::WebSocketTransport::ReadyState::CONNECTING: return ReadyState::Connecting;
|
|
||||||
case ix::WebSocketTransport::ReadyState::CLOSING : return ReadyState::Closing;
|
|
||||||
case ix::WebSocketTransport::ReadyState::CLOSED : return ReadyState::Closed;
|
|
||||||
default: return ReadyState::Closed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocket::readyStateToString(ReadyState readyState)
|
|
||||||
{
|
|
||||||
switch (readyState)
|
|
||||||
{
|
|
||||||
case ReadyState::Open : return "OPEN";
|
|
||||||
case ReadyState::Connecting: return "CONNECTING";
|
|
||||||
case ReadyState::Closing : return "CLOSING";
|
|
||||||
case ReadyState::Closed : return "CLOSED";
|
|
||||||
default: return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::enableAutomaticReconnection()
|
|
||||||
{
|
|
||||||
_automaticReconnection = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocket::disableAutomaticReconnection()
|
|
||||||
{
|
|
||||||
_automaticReconnection = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocket::isAutomaticReconnectionEnabled() const
|
|
||||||
{
|
|
||||||
return _automaticReconnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WebSocket::bufferedAmount() const
|
|
||||||
{
|
|
||||||
return _ws.bufferedAmount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocket.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* WebSocket RFC
|
|
||||||
* https://tools.ietf.org/html/rfc6455
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXProgressCallback.h"
|
|
||||||
#include "IXWebSocketCloseConstants.h"
|
|
||||||
#include "IXWebSocketErrorInfo.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include "IXWebSocketTransport.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
|
|
||||||
enum class ReadyState
|
|
||||||
{
|
|
||||||
Connecting = 0,
|
|
||||||
Open = 1,
|
|
||||||
Closing = 2,
|
|
||||||
Closed = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class WebSocketMessageType
|
|
||||||
{
|
|
||||||
Message = 0,
|
|
||||||
Open = 1,
|
|
||||||
Close = 2,
|
|
||||||
Error = 3,
|
|
||||||
Ping = 4,
|
|
||||||
Pong = 5,
|
|
||||||
Fragment = 6
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WebSocketOpenInfo
|
|
||||||
{
|
|
||||||
std::string uri;
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
|
|
||||||
WebSocketOpenInfo(const std::string& u = std::string(),
|
|
||||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
|
||||||
: uri(u)
|
|
||||||
, headers(h)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WebSocketCloseInfo
|
|
||||||
{
|
|
||||||
uint16_t code;
|
|
||||||
std::string reason;
|
|
||||||
bool remote;
|
|
||||||
|
|
||||||
WebSocketCloseInfo(uint16_t c = 0, const std::string& r = std::string(), bool rem = false)
|
|
||||||
: code(c)
|
|
||||||
, reason(r)
|
|
||||||
, remote(rem)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using OnMessageCallback = std::function<void(WebSocketMessageType,
|
|
||||||
const std::string&,
|
|
||||||
size_t wireSize,
|
|
||||||
const WebSocketErrorInfo&,
|
|
||||||
const WebSocketOpenInfo&,
|
|
||||||
const WebSocketCloseInfo&)>;
|
|
||||||
|
|
||||||
using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
|
|
||||||
|
|
||||||
class WebSocket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocket();
|
|
||||||
~WebSocket();
|
|
||||||
|
|
||||||
void setUrl(const std::string& url);
|
|
||||||
void setPerMessageDeflateOptions(
|
|
||||||
const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
|
||||||
void setHeartBeatPeriod(int heartBeatPeriodSecs);
|
|
||||||
void setPingInterval(int pingIntervalSecs); // alias of setHeartBeatPeriod
|
|
||||||
void setPingTimeout(int pingTimeoutSecs);
|
|
||||||
void enablePong();
|
|
||||||
void disablePong();
|
|
||||||
|
|
||||||
// Run asynchronously, by calling start and stop.
|
|
||||||
void start();
|
|
||||||
|
|
||||||
// stop is synchronous
|
|
||||||
void stop(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
|
||||||
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage);
|
|
||||||
|
|
||||||
// Run in blocking mode, by connecting first manually, and then calling run.
|
|
||||||
WebSocketInitResult connect(int timeoutSecs);
|
|
||||||
void run();
|
|
||||||
|
|
||||||
// send binary data
|
|
||||||
WebSocketSendInfo send(const std::string& data,
|
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
|
||||||
WebSocketSendInfo sendText(const std::string& text,
|
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
|
||||||
|
|
||||||
void close(uint16_t code = 1000, const std::string& reason = "Normal closure");
|
|
||||||
|
|
||||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
|
||||||
static void setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback);
|
|
||||||
static void resetTrafficTrackerCallback();
|
|
||||||
|
|
||||||
ReadyState getReadyState() const;
|
|
||||||
static std::string readyStateToString(ReadyState readyState);
|
|
||||||
|
|
||||||
const std::string& getUrl() const;
|
|
||||||
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
|
|
||||||
int getHeartBeatPeriod() const;
|
|
||||||
int getPingInterval() const;
|
|
||||||
int getPingTimeout() const;
|
|
||||||
size_t bufferedAmount() const;
|
|
||||||
|
|
||||||
void enableAutomaticReconnection();
|
|
||||||
void disableAutomaticReconnection();
|
|
||||||
bool isAutomaticReconnectionEnabled() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
WebSocketSendInfo sendMessage(const std::string& text,
|
|
||||||
SendMessageKind sendMessageKind,
|
|
||||||
const OnProgressCallback& callback = nullptr);
|
|
||||||
|
|
||||||
bool isConnected() const;
|
|
||||||
bool isClosing() const;
|
|
||||||
void checkConnection(bool firstConnectionAttempt);
|
|
||||||
static void invokeTrafficTrackerCallback(size_t size, bool incoming);
|
|
||||||
|
|
||||||
// Server
|
|
||||||
WebSocketInitResult connectToSocket(int fd, int timeoutSecs);
|
|
||||||
|
|
||||||
WebSocketTransport _ws;
|
|
||||||
|
|
||||||
std::string _url;
|
|
||||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
|
||||||
mutable std::mutex _configMutex; // protect all config variables access
|
|
||||||
|
|
||||||
OnMessageCallback _onMessageCallback;
|
|
||||||
static OnTrafficTrackerCallback _onTrafficTrackerCallback;
|
|
||||||
|
|
||||||
std::atomic<bool> _stop;
|
|
||||||
std::atomic<bool> _automaticReconnection;
|
|
||||||
std::thread _thread;
|
|
||||||
std::mutex _writeMutex;
|
|
||||||
|
|
||||||
std::atomic<int> _handshakeTimeoutSecs;
|
|
||||||
static const int kDefaultHandShakeTimeoutSecs;
|
|
||||||
|
|
||||||
// enable or disable PONG frame response to received PING frame
|
|
||||||
bool _enablePong;
|
|
||||||
static const bool kDefaultEnablePong;
|
|
||||||
|
|
||||||
// Optional ping and ping timeout
|
|
||||||
int _pingIntervalSecs;
|
|
||||||
int _pingTimeoutSecs;
|
|
||||||
static const int kDefaultPingIntervalSecs;
|
|
||||||
static const int kDefaultPingTimeoutSecs;
|
|
||||||
|
|
||||||
friend class WebSocketServer;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketCloseConstants.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketCloseConstants.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const uint16_t WebSocketCloseConstants::kNormalClosureCode(1000);
|
|
||||||
const uint16_t WebSocketCloseConstants::kInternalErrorCode(1011);
|
|
||||||
const uint16_t WebSocketCloseConstants::kAbnormalCloseCode(1006);
|
|
||||||
const uint16_t WebSocketCloseConstants::kProtocolErrorCode(1002);
|
|
||||||
const uint16_t WebSocketCloseConstants::kNoStatusCodeErrorCode(1005);
|
|
||||||
|
|
||||||
const std::string WebSocketCloseConstants::kNormalClosureMessage("Normal closure");
|
|
||||||
const std::string WebSocketCloseConstants::kInternalErrorMessage("Internal error");
|
|
||||||
const std::string WebSocketCloseConstants::kAbnormalCloseMessage("Abnormal closure");
|
|
||||||
const std::string WebSocketCloseConstants::kPingTimeoutMessage("Ping timeout");
|
|
||||||
const std::string WebSocketCloseConstants::kProtocolErrorMessage("Protocol error");
|
|
||||||
const std::string WebSocketCloseConstants::kNoStatusCodeErrorMessage("No status code");
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketCloseConstants.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
struct WebSocketCloseConstants
|
|
||||||
{
|
|
||||||
static const uint16_t kNormalClosureCode;
|
|
||||||
static const uint16_t kInternalErrorCode;
|
|
||||||
static const uint16_t kAbnormalCloseCode;
|
|
||||||
static const uint16_t kProtocolErrorCode;
|
|
||||||
static const uint16_t kNoStatusCodeErrorCode;
|
|
||||||
|
|
||||||
static const std::string kNormalClosureMessage;
|
|
||||||
static const std::string kInternalErrorMessage;
|
|
||||||
static const std::string kAbnormalCloseMessage;
|
|
||||||
static const std::string kPingTimeoutMessage;
|
|
||||||
static const std::string kProtocolErrorMessage;
|
|
||||||
static const std::string kNoStatusCodeErrorMessage;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketErrorInfo.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
struct WebSocketErrorInfo
|
|
||||||
{
|
|
||||||
uint32_t retries = 0;
|
|
||||||
double wait_time = 0;
|
|
||||||
int http_status = 0;
|
|
||||||
std::string reason;
|
|
||||||
bool decompressionError = false;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketHandshake.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketHandshake.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXUrlParser.h"
|
|
||||||
|
|
||||||
#include "libwshandshake.hpp"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <random>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
WebSocketHandshake::WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
|
|
||||||
std::shared_ptr<Socket> socket,
|
|
||||||
WebSocketPerMessageDeflate& perMessageDeflate,
|
|
||||||
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
|
||||||
std::atomic<bool>& enablePerMessageDeflate) :
|
|
||||||
_requestInitCancellation(requestInitCancellation),
|
|
||||||
_socket(socket),
|
|
||||||
_perMessageDeflate(perMessageDeflate),
|
|
||||||
_perMessageDeflateOptions(perMessageDeflateOptions),
|
|
||||||
_enablePerMessageDeflate(enablePerMessageDeflate)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketHandshake::trim(const std::string& str)
|
|
||||||
{
|
|
||||||
std::string out(str);
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), '\r'), out.end());
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), '\n'), out.end());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
|
||||||
{
|
|
||||||
return std::equal(a.begin(), a.end(),
|
|
||||||
b.begin(), b.end(),
|
|
||||||
[](char a, char b)
|
|
||||||
{
|
|
||||||
return tolower(a) == tolower(b);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
|
|
||||||
{
|
|
||||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
|
||||||
std::string token;
|
|
||||||
std::stringstream tokenStream(line);
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
|
|
||||||
// Split by ' '
|
|
||||||
while (std::getline(tokenStream, token, ' '))
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string method;
|
|
||||||
if (tokens.size() >= 1)
|
|
||||||
{
|
|
||||||
method = trim(tokens[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string requestUri;
|
|
||||||
if (tokens.size() >= 2)
|
|
||||||
{
|
|
||||||
requestUri = trim(tokens[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string httpVersion;
|
|
||||||
if (tokens.size() >= 3)
|
|
||||||
{
|
|
||||||
httpVersion = trim(tokens[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_tuple(method, requestUri, httpVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketHandshake::genRandomString(const int len)
|
|
||||||
{
|
|
||||||
std::string alphanum =
|
|
||||||
"0123456789"
|
|
||||||
"ABCDEFGH"
|
|
||||||
"abcdefgh";
|
|
||||||
|
|
||||||
std::random_device r;
|
|
||||||
std::default_random_engine e1(r());
|
|
||||||
std::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
|
|
||||||
|
|
||||||
std::string s;
|
|
||||||
s.resize(len);
|
|
||||||
|
|
||||||
for (int i = 0; i < len; ++i)
|
|
||||||
{
|
|
||||||
int x = dist(e1);
|
|
||||||
s[i] = alphanum[x];
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "HTTP/1.1 ";
|
|
||||||
ss << code;
|
|
||||||
ss << " ";
|
|
||||||
ss << reason;
|
|
||||||
ss << "\r\n";
|
|
||||||
|
|
||||||
// Socket write can only be cancelled through a timeout here, not manually.
|
|
||||||
static std::atomic<bool> requestInitCancellation(false);
|
|
||||||
auto isCancellationRequested =
|
|
||||||
makeCancellationRequestWithTimeout(1, requestInitCancellation);
|
|
||||||
|
|
||||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(false, 500, "Timed out while sending error response");
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebSocketInitResult(false, code, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult WebSocketHandshake::clientHandshake(const std::string& url,
|
|
||||||
const std::string& host,
|
|
||||||
const std::string& path,
|
|
||||||
int port,
|
|
||||||
int timeoutSecs)
|
|
||||||
{
|
|
||||||
_requestInitCancellation = false;
|
|
||||||
|
|
||||||
auto isCancellationRequested =
|
|
||||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
|
||||||
|
|
||||||
std::string errMsg;
|
|
||||||
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Unable to connect to " << host
|
|
||||||
<< " on port " << port
|
|
||||||
<< ", error: " << errMsg;
|
|
||||||
return WebSocketInitResult(false, 0, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
std::string secWebSocketKey = genRandomString(22);
|
|
||||||
secWebSocketKey += "==";
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "GET " << path << " HTTP/1.1\r\n";
|
|
||||||
ss << "Host: "<< host << ":" << port << "\r\n";
|
|
||||||
ss << "Upgrade: websocket\r\n";
|
|
||||||
ss << "Connection: Upgrade\r\n";
|
|
||||||
ss << "Sec-WebSocket-Version: 13\r\n";
|
|
||||||
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
|
|
||||||
|
|
||||||
if (_enablePerMessageDeflate)
|
|
||||||
{
|
|
||||||
ss << _perMessageDeflateOptions.generateHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "\r\n";
|
|
||||||
|
|
||||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(false, 0, std::string("Failed sending GET request to ") + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read HTTP status line
|
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
||||||
auto lineValid = lineResult.first;
|
|
||||||
auto line = lineResult.second;
|
|
||||||
|
|
||||||
if (!lineValid)
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(false, 0,
|
|
||||||
std::string("Failed reading HTTP status line from ") + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate status
|
|
||||||
int status;
|
|
||||||
|
|
||||||
// HTTP/1.0 is too old.
|
|
||||||
if (sscanf(line.c_str(), "HTTP/1.0 %d", &status) == 1)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Server version is HTTP/1.0. Rejecting connection to " << host
|
|
||||||
<< ", status: " << status
|
|
||||||
<< ", HTTP Status line: " << line;
|
|
||||||
return WebSocketInitResult(false, status, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want an 101 HTTP status
|
|
||||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &status) != 1 || status != 101)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Got bad status connecting to " << host
|
|
||||||
<< ", status: " << status
|
|
||||||
<< ", HTTP Status line: " << line;
|
|
||||||
return WebSocketInitResult(false, status, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
|
||||||
auto headersValid = result.first;
|
|
||||||
auto headers = result.second;
|
|
||||||
|
|
||||||
if (!headersValid)
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the presence of the connection field
|
|
||||||
if (headers.find("connection") == headers.end())
|
|
||||||
{
|
|
||||||
std::string errorMsg("Missing connection value");
|
|
||||||
return WebSocketInitResult(false, status, errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the value of the connection field
|
|
||||||
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
|
||||||
// connection header, so do a case insensitive comparison
|
|
||||||
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Invalid connection value: " << headers["connection"];
|
|
||||||
return WebSocketInitResult(false, status, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
char output[29] = {};
|
|
||||||
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
|
|
||||||
if (std::string(output) != headers["sec-websocket-accept"])
|
|
||||||
{
|
|
||||||
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
|
||||||
return WebSocketInitResult(false, status, errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_enablePerMessageDeflate)
|
|
||||||
{
|
|
||||||
// Parse the server response. Does it support deflate ?
|
|
||||||
std::string header = headers["sec-websocket-extensions"];
|
|
||||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
|
||||||
|
|
||||||
// If the server does not support that extension, disable it.
|
|
||||||
if (!webSocketPerMessageDeflateOptions.enabled())
|
|
||||||
{
|
|
||||||
_enablePerMessageDeflate = false;
|
|
||||||
}
|
|
||||||
// Otherwise try to initialize the deflate engine (zlib)
|
|
||||||
else if (!_perMessageDeflate.init(webSocketPerMessageDeflateOptions))
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(
|
|
||||||
false, 0,"Failed to initialize per message deflate engine");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebSocketInitResult(true, status, "", headers, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketInitResult WebSocketHandshake::serverHandshake(int fd, int timeoutSecs)
|
|
||||||
{
|
|
||||||
_requestInitCancellation = false;
|
|
||||||
|
|
||||||
// Set the socket to non blocking mode + other tweaks
|
|
||||||
SocketConnect::configure(fd);
|
|
||||||
|
|
||||||
auto isCancellationRequested =
|
|
||||||
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
|
||||||
|
|
||||||
std::string remote = std::string("remote fd ") + std::to_string(fd);
|
|
||||||
|
|
||||||
// Read first line
|
|
||||||
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
||||||
auto lineValid = lineResult.first;
|
|
||||||
auto line = lineResult.second;
|
|
||||||
|
|
||||||
if (!lineValid)
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Error reading HTTP request line");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
|
||||||
auto requestLine = parseRequestLine(line);
|
|
||||||
auto method = std::get<0>(requestLine);
|
|
||||||
auto uri = std::get<1>(requestLine);
|
|
||||||
auto httpVersion = std::get<2>(requestLine);
|
|
||||||
|
|
||||||
if (method != "GET")
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpVersion != "HTTP/1.1")
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "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)
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Error parsing HTTP headers");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers.find("sec-websocket-key") == headers.end())
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers["upgrade"] != "websocket")
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Invalid or missing Upgrade header");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers.find("sec-websocket-version") == headers.end())
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << headers["sec-websocket-version"];
|
|
||||||
int version;
|
|
||||||
ss >> version;
|
|
||||||
|
|
||||||
if (version != 13)
|
|
||||||
{
|
|
||||||
return sendErrorResponse(400, "Invalid Sec-WebSocket-Version, "
|
|
||||||
"need 13, got" + ss.str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char output[29] = {};
|
|
||||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
|
||||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
|
||||||
ss << "Upgrade: websocket\r\n";
|
|
||||||
ss << "Connection: Upgrade\r\n";
|
|
||||||
|
|
||||||
// Parse the client headers. Does it support deflate ?
|
|
||||||
std::string header = headers["sec-websocket-extensions"];
|
|
||||||
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
|
||||||
|
|
||||||
// If the client has requested that extension, enable it.
|
|
||||||
if (webSocketPerMessageDeflateOptions.enabled())
|
|
||||||
{
|
|
||||||
_enablePerMessageDeflate = true;
|
|
||||||
|
|
||||||
if (!_perMessageDeflate.init(webSocketPerMessageDeflateOptions))
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(
|
|
||||||
false, 0,"Failed to initialize per message deflate engine");
|
|
||||||
}
|
|
||||||
ss << webSocketPerMessageDeflateOptions.generateHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "\r\n";
|
|
||||||
|
|
||||||
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
||||||
{
|
|
||||||
return WebSocketInitResult(false, 0, std::string("Failed sending response to ") + remote);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebSocketInitResult(true, 200, "", headers, uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketHandshake.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
struct WebSocketInitResult
|
|
||||||
{
|
|
||||||
bool success;
|
|
||||||
int http_status;
|
|
||||||
std::string errorStr;
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
std::string uri;
|
|
||||||
|
|
||||||
WebSocketInitResult(bool s = false,
|
|
||||||
int status = 0,
|
|
||||||
const std::string& e = std::string(),
|
|
||||||
WebSocketHttpHeaders h = WebSocketHttpHeaders(),
|
|
||||||
const std::string& u = std::string())
|
|
||||||
{
|
|
||||||
success = s;
|
|
||||||
http_status = status;
|
|
||||||
errorStr = e;
|
|
||||||
headers = h;
|
|
||||||
uri = u;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class WebSocketHandshake
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
|
|
||||||
std::shared_ptr<Socket> _socket,
|
|
||||||
WebSocketPerMessageDeflate& perMessageDeflate,
|
|
||||||
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
|
||||||
std::atomic<bool>& enablePerMessageDeflate);
|
|
||||||
|
|
||||||
WebSocketInitResult clientHandshake(const std::string& url,
|
|
||||||
const std::string& host,
|
|
||||||
const std::string& path,
|
|
||||||
int port,
|
|
||||||
int timeoutSecs);
|
|
||||||
|
|
||||||
WebSocketInitResult serverHandshake(int fd, int timeoutSecs);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string genRandomString(const int len);
|
|
||||||
|
|
||||||
// Parse HTTP headers
|
|
||||||
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
|
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
|
||||||
std::string trim(const std::string& str);
|
|
||||||
bool insensitiveStringCompare(const std::string& a, const std::string& b);
|
|
||||||
|
|
||||||
std::atomic<bool>& _requestInitCancellation;
|
|
||||||
std::shared_ptr<Socket> _socket;
|
|
||||||
WebSocketPerMessageDeflate& _perMessageDeflate;
|
|
||||||
WebSocketPerMessageDeflateOptions& _perMessageDeflateOptions;
|
|
||||||
std::atomic<bool>& _enablePerMessageDeflate;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketHttpHeaders.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXSocket.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
bool CaseInsensitiveLess::NocaseCompare::operator()(const unsigned char & c1, const unsigned char & c2) const
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
return std::tolower(c1, std::locale()) < std::tolower(c2, std::locale());
|
|
||||||
#else
|
|
||||||
return std::tolower(c1) < std::tolower(c2);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CaseInsensitiveLess::operator()(const std::string & s1, const std::string & s2) const
|
|
||||||
{
|
|
||||||
return std::lexicographical_compare
|
|
||||||
(s1.begin(), s1.end(), // source range
|
|
||||||
s2.begin(), s2.end(), // dest range
|
|
||||||
NocaseCompare()); // comparison
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
|
||||||
std::shared_ptr<Socket> socket,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
|
|
||||||
char line[1024];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int colon = 0;
|
|
||||||
|
|
||||||
for (i = 0;
|
|
||||||
i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n');
|
|
||||||
++i)
|
|
||||||
{
|
|
||||||
if (!socket->readByte(line+i, isCancellationRequested))
|
|
||||||
{
|
|
||||||
return std::make_pair(false, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line[i] == ':' && colon == 0)
|
|
||||||
{
|
|
||||||
colon = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (line[0] == '\r' && line[1] == '\n')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// line is a single header entry. split by ':', and add it to our
|
|
||||||
// header map. ignore lines with no colon.
|
|
||||||
if (colon > 0)
|
|
||||||
{
|
|
||||||
line[i] = '\0';
|
|
||||||
std::string lineStr(line);
|
|
||||||
// colon is ':', colon+1 is ' ', colon+2 is the start of the value.
|
|
||||||
// i is end of string (\0), i-colon is length of string minus key;
|
|
||||||
// subtract 1 for '\0', 1 for '\n', 1 for '\r',
|
|
||||||
// 1 for the ' ' after the ':', and total is -4
|
|
||||||
std::string name(lineStr.substr(0, colon));
|
|
||||||
std::string value(lineStr.substr(colon + 2, i - colon - 4));
|
|
||||||
|
|
||||||
headers[name] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(true, headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketHttpHeaders.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class Socket;
|
|
||||||
|
|
||||||
struct CaseInsensitiveLess
|
|
||||||
{
|
|
||||||
// Case Insensitive compare_less binary function
|
|
||||||
struct NocaseCompare
|
|
||||||
{
|
|
||||||
bool operator()(const unsigned char& c1, const unsigned char& c2) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool operator()(const std::string& s1, const std::string& s2) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
|
|
||||||
|
|
||||||
std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
|
|
||||||
std::shared_ptr<Socket> socket, const CancellationRequest& isCancellationRequested);
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketMessageQueue.cpp
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketMessageQueue.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
|
|
||||||
WebSocketMessageQueue::WebSocketMessageQueue(WebSocket* websocket)
|
|
||||||
{
|
|
||||||
bindWebsocket(websocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketMessageQueue::~WebSocketMessageQueue()
|
|
||||||
{
|
|
||||||
if (!_messages.empty())
|
|
||||||
{
|
|
||||||
// not handled all messages
|
|
||||||
}
|
|
||||||
|
|
||||||
bindWebsocket(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::bindWebsocket(WebSocket * websocket)
|
|
||||||
{
|
|
||||||
if (_websocket == websocket) return;
|
|
||||||
|
|
||||||
// unbind old
|
|
||||||
if (_websocket)
|
|
||||||
{
|
|
||||||
// set dummy callback just to avoid crash
|
|
||||||
_websocket->setOnMessageCallback([](
|
|
||||||
WebSocketMessageType,
|
|
||||||
const std::string&,
|
|
||||||
size_t,
|
|
||||||
const WebSocketErrorInfo&,
|
|
||||||
const WebSocketOpenInfo&,
|
|
||||||
const WebSocketCloseInfo&)
|
|
||||||
{});
|
|
||||||
}
|
|
||||||
|
|
||||||
_websocket = websocket;
|
|
||||||
|
|
||||||
// bind new
|
|
||||||
if (_websocket)
|
|
||||||
{
|
|
||||||
_websocket->setOnMessageCallback([this](
|
|
||||||
WebSocketMessageType type,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const WebSocketErrorInfo& errorInfo,
|
|
||||||
const WebSocketOpenInfo& openInfo,
|
|
||||||
const WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
MessagePtr message(new Message());
|
|
||||||
|
|
||||||
message->type = type;
|
|
||||||
message->str = str;
|
|
||||||
message->wireSize = wireSize;
|
|
||||||
message->errorInfo = errorInfo;
|
|
||||||
message->openInfo = openInfo;
|
|
||||||
message->closeInfo = closeInfo;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
|
||||||
_messages.emplace_back(std::move(message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::setOnMessageCallback(const OnMessageCallback& callback)
|
|
||||||
{
|
|
||||||
_onMessageUserCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::setOnMessageCallback(OnMessageCallback&& callback)
|
|
||||||
{
|
|
||||||
_onMessageUserCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketMessageQueue::MessagePtr WebSocketMessageQueue::popMessage()
|
|
||||||
{
|
|
||||||
MessagePtr message;
|
|
||||||
std::lock_guard<std::mutex> lock(_messagesMutex);
|
|
||||||
|
|
||||||
if (!_messages.empty())
|
|
||||||
{
|
|
||||||
message = std::move(_messages.front());
|
|
||||||
_messages.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketMessageQueue::poll(int count)
|
|
||||||
{
|
|
||||||
if (!_onMessageUserCallback)
|
|
||||||
return;
|
|
||||||
|
|
||||||
MessagePtr message;
|
|
||||||
|
|
||||||
while (count > 0 && (message = popMessage()))
|
|
||||||
{
|
|
||||||
_onMessageUserCallback(
|
|
||||||
message->type,
|
|
||||||
message->str,
|
|
||||||
message->wireSize,
|
|
||||||
message->errorInfo,
|
|
||||||
message->openInfo,
|
|
||||||
message->closeInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
--count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketMessageQueue.h
|
|
||||||
* Author: Korchynskyi Dmytro
|
|
||||||
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXWebSocket.h"
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// A helper class to dispatch websocket message callbacks in your thread.
|
|
||||||
//
|
|
||||||
class WebSocketMessageQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketMessageQueue(WebSocket* websocket = nullptr);
|
|
||||||
~WebSocketMessageQueue();
|
|
||||||
|
|
||||||
void bindWebsocket(WebSocket* websocket);
|
|
||||||
|
|
||||||
void setOnMessageCallback(const OnMessageCallback& callback);
|
|
||||||
void setOnMessageCallback(OnMessageCallback&& callback);
|
|
||||||
|
|
||||||
void poll(int count = 512);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
struct Message
|
|
||||||
{
|
|
||||||
WebSocketMessageType type;
|
|
||||||
std::string str;
|
|
||||||
size_t wireSize;
|
|
||||||
WebSocketErrorInfo errorInfo;
|
|
||||||
WebSocketOpenInfo openInfo;
|
|
||||||
WebSocketCloseInfo closeInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
using MessagePtr = std::shared_ptr<Message>;
|
|
||||||
|
|
||||||
MessagePtr popMessage();
|
|
||||||
|
|
||||||
private:
|
|
||||||
WebSocket* _websocket = nullptr;
|
|
||||||
OnMessageCallback _onMessageUserCallback;
|
|
||||||
std::mutex _messagesMutex;
|
|
||||||
std::list<MessagePtr> _messages;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Peter Thorson. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the WebSocket++ Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp
|
|
||||||
* (same license as MZ: https://opensource.org/licenses/BSD-3-Clause)
|
|
||||||
*
|
|
||||||
* - Reused zlib compression + decompression bits.
|
|
||||||
* - Refactored to have 2 class for compression and decompression, to allow multi-threading
|
|
||||||
* and make sure that _compressBuffer is not shared between threads.
|
|
||||||
* - Original code wasn't working for some reason, I had to add checks
|
|
||||||
* for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers
|
|
||||||
* would start accepting receiving/decoding compressed messages. Original code was probably
|
|
||||||
* modifying the passed in buffers before processing in enabled.hpp ?
|
|
||||||
* - Added more documentation.
|
|
||||||
*
|
|
||||||
* Per message Deflate RFC: https://tools.ietf.org/html/rfc7692
|
|
||||||
* Chrome websocket -> https://github.com/chromium/chromium/tree/2ca8c5037021c9d2ecc00b787d58a31ed8fc8bcb/net/websockets
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateCodec.h"
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
WebSocketPerMessageDeflate::WebSocketPerMessageDeflate() :
|
|
||||||
_compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>()),
|
|
||||||
_decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
|
|
||||||
{
|
|
||||||
bool clientNoContextTakeover =
|
|
||||||
perMessageDeflateOptions.getClientNoContextTakeover();
|
|
||||||
|
|
||||||
uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
|
|
||||||
uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
|
|
||||||
|
|
||||||
return _compressor->init(deflateBits, clientNoContextTakeover) &&
|
|
||||||
_decompressor->init(inflateBits, clientNoContextTakeover);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflate::compress(const std::string& in,
|
|
||||||
std::string& out)
|
|
||||||
{
|
|
||||||
return _compressor->compress(in, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflate::decompress(const std::string& in,
|
|
||||||
std::string &out)
|
|
||||||
{
|
|
||||||
return _decompressor->decompress(in, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Peter Thorson. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the WebSocket++ Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*
|
|
||||||
* Adapted from websocketpp/extensions/permessage_deflate/enabled.hpp
|
|
||||||
* (same license as MZ: https://opensource.org/licenses/BSD-3-Clause)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class WebSocketPerMessageDeflateOptions;
|
|
||||||
class WebSocketPerMessageDeflateCompressor;
|
|
||||||
class WebSocketPerMessageDeflateDecompressor;
|
|
||||||
|
|
||||||
class WebSocketPerMessageDeflate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketPerMessageDeflate();
|
|
||||||
~WebSocketPerMessageDeflate();
|
|
||||||
|
|
||||||
bool init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
|
||||||
bool compress(const std::string& in, std::string& out);
|
|
||||||
bool decompress(const std::string& in, std::string& out);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
|
|
||||||
std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketPerMessageDeflateCodec.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketPerMessageDeflateCodec.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// The passed in size (4) is important, without it the string litteral
|
|
||||||
// is treated as a char* and the null termination (\x00) makes it
|
|
||||||
// look like an empty string.
|
|
||||||
const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
|
|
||||||
|
|
||||||
const int kBufferSize = 1 << 14;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Compressor
|
|
||||||
//
|
|
||||||
WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
|
|
||||||
: _compressBufferSize(kBufferSize)
|
|
||||||
{
|
|
||||||
memset(&_deflateState, 0, sizeof(_deflateState));
|
|
||||||
|
|
||||||
_deflateState.zalloc = Z_NULL;
|
|
||||||
_deflateState.zfree = Z_NULL;
|
|
||||||
_deflateState.opaque = Z_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
|
|
||||||
{
|
|
||||||
deflateEnd(&_deflateState);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
|
|
||||||
bool clientNoContextTakeOver)
|
|
||||||
{
|
|
||||||
int ret = deflateInit2(
|
|
||||||
&_deflateState,
|
|
||||||
Z_DEFAULT_COMPRESSION,
|
|
||||||
Z_DEFLATED,
|
|
||||||
-1*deflateBits,
|
|
||||||
4, // memory level 1-9
|
|
||||||
Z_DEFAULT_STRATEGY
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ret != Z_OK) return false;
|
|
||||||
|
|
||||||
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
|
|
||||||
|
|
||||||
_flush = (clientNoContextTakeOver)
|
|
||||||
? Z_FULL_FLUSH
|
|
||||||
: Z_SYNC_FLUSH;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
|
|
||||||
const std::string& ending)
|
|
||||||
{
|
|
||||||
if (ending.size() > value.size()) return false;
|
|
||||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
|
|
||||||
std::string& out)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// 7.2.1. Compression
|
|
||||||
//
|
|
||||||
// An endpoint uses the following algorithm to compress a message.
|
|
||||||
//
|
|
||||||
// 1. Compress all the octets of the payload of the message using
|
|
||||||
// DEFLATE.
|
|
||||||
//
|
|
||||||
// 2. If the resulting data does not end with an empty DEFLATE block
|
|
||||||
// with no compression (the "BTYPE" bits are set to 00), append an
|
|
||||||
// empty DEFLATE block with no compression to the tail end.
|
|
||||||
//
|
|
||||||
// 3. Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
|
|
||||||
// After this step, the last octet of the compressed data contains
|
|
||||||
// (possibly part of) the DEFLATE header bits with the "BTYPE" bits
|
|
||||||
// set to 00.
|
|
||||||
//
|
|
||||||
size_t output;
|
|
||||||
|
|
||||||
if (in.empty())
|
|
||||||
{
|
|
||||||
uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
|
|
||||||
out.append((char *)(buf), 6);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_deflateState.avail_in = (uInt) in.size();
|
|
||||||
_deflateState.next_in = (Bytef*) in.data();
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// Output to local buffer
|
|
||||||
_deflateState.avail_out = (uInt) _compressBufferSize;
|
|
||||||
_deflateState.next_out = _compressBuffer.get();
|
|
||||||
|
|
||||||
deflate(&_deflateState, _flush);
|
|
||||||
|
|
||||||
output = _compressBufferSize - _deflateState.avail_out;
|
|
||||||
|
|
||||||
out.append((char *)(_compressBuffer.get()),output);
|
|
||||||
} while (_deflateState.avail_out == 0);
|
|
||||||
|
|
||||||
if (endsWith(out, kEmptyUncompressedBlock))
|
|
||||||
{
|
|
||||||
out.resize(out.size() - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Decompressor
|
|
||||||
//
|
|
||||||
WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
|
|
||||||
: _compressBufferSize(kBufferSize)
|
|
||||||
{
|
|
||||||
memset(&_inflateState, 0, sizeof(_inflateState));
|
|
||||||
|
|
||||||
_inflateState.zalloc = Z_NULL;
|
|
||||||
_inflateState.zfree = Z_NULL;
|
|
||||||
_inflateState.opaque = Z_NULL;
|
|
||||||
_inflateState.avail_in = 0;
|
|
||||||
_inflateState.next_in = Z_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
|
|
||||||
{
|
|
||||||
inflateEnd(&_inflateState);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
|
|
||||||
bool clientNoContextTakeOver)
|
|
||||||
{
|
|
||||||
int ret = inflateInit2(
|
|
||||||
&_inflateState,
|
|
||||||
-1*inflateBits
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ret != Z_OK) return false;
|
|
||||||
|
|
||||||
_compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
|
|
||||||
|
|
||||||
_flush = (clientNoContextTakeOver)
|
|
||||||
? Z_FULL_FLUSH
|
|
||||||
: Z_SYNC_FLUSH;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
|
|
||||||
std::string& out)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// 7.2.2. Decompression
|
|
||||||
//
|
|
||||||
// An endpoint uses the following algorithm to decompress a message.
|
|
||||||
//
|
|
||||||
// 1. Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
|
|
||||||
// payload of the message.
|
|
||||||
//
|
|
||||||
// 2. Decompress the resulting data using DEFLATE.
|
|
||||||
//
|
|
||||||
std::string inFixed(in);
|
|
||||||
inFixed += kEmptyUncompressedBlock;
|
|
||||||
|
|
||||||
_inflateState.avail_in = (uInt) inFixed.size();
|
|
||||||
_inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
_inflateState.avail_out = (uInt) _compressBufferSize;
|
|
||||||
_inflateState.next_out = _compressBuffer.get();
|
|
||||||
|
|
||||||
int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
|
|
||||||
|
|
||||||
if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
|
|
||||||
{
|
|
||||||
return false; // zlib error
|
|
||||||
}
|
|
||||||
|
|
||||||
out.append(
|
|
||||||
reinterpret_cast<char *>(_compressBuffer.get()),
|
|
||||||
_compressBufferSize - _inflateState.avail_out
|
|
||||||
);
|
|
||||||
} while (_inflateState.avail_out == 0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketPerMessageDeflateCodec.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "zlib.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class WebSocketPerMessageDeflateCompressor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketPerMessageDeflateCompressor();
|
|
||||||
~WebSocketPerMessageDeflateCompressor();
|
|
||||||
|
|
||||||
bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
|
|
||||||
bool compress(const std::string& in, std::string& out);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static bool endsWith(const std::string& value, const std::string& ending);
|
|
||||||
|
|
||||||
int _flush;
|
|
||||||
size_t _compressBufferSize;
|
|
||||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
|
||||||
z_stream _deflateState;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WebSocketPerMessageDeflateDecompressor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketPerMessageDeflateDecompressor();
|
|
||||||
~WebSocketPerMessageDeflateDecompressor();
|
|
||||||
|
|
||||||
bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
|
|
||||||
bool decompress(const std::string& in, std::string& out);
|
|
||||||
|
|
||||||
private:
|
|
||||||
int _flush;
|
|
||||||
size_t _compressBufferSize;
|
|
||||||
std::unique_ptr<unsigned char[]> _compressBuffer;
|
|
||||||
z_stream _inflateState;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketPerMessageDeflateOptions.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
/// Default values as defined in the RFC
|
|
||||||
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultServerMaxWindowBits = 15;
|
|
||||||
static const int minServerMaxWindowBits = 8;
|
|
||||||
static const int maxServerMaxWindowBits = 15;
|
|
||||||
|
|
||||||
const uint8_t WebSocketPerMessageDeflateOptions::kDefaultClientMaxWindowBits = 15;
|
|
||||||
static const int minClientMaxWindowBits = 8;
|
|
||||||
static const int maxClientMaxWindowBits = 15;
|
|
||||||
|
|
||||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(
|
|
||||||
bool enabled,
|
|
||||||
bool clientNoContextTakeover,
|
|
||||||
bool serverNoContextTakeover,
|
|
||||||
uint8_t clientMaxWindowBits,
|
|
||||||
uint8_t serverMaxWindowBits)
|
|
||||||
{
|
|
||||||
_enabled = enabled;
|
|
||||||
_clientNoContextTakeover = clientNoContextTakeover;
|
|
||||||
_serverNoContextTakeover = serverNoContextTakeover;
|
|
||||||
_clientMaxWindowBits = clientMaxWindowBits;
|
|
||||||
_serverMaxWindowBits = serverMaxWindowBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Four extension parameters are defined for "permessage-deflate" to
|
|
||||||
// help endpoints manage per-connection resource usage.
|
|
||||||
//
|
|
||||||
// - "server_no_context_takeover"
|
|
||||||
// - "client_no_context_takeover"
|
|
||||||
// - "server_max_window_bits"
|
|
||||||
// - "client_max_window_bits"
|
|
||||||
//
|
|
||||||
// Server response could look like that:
|
|
||||||
//
|
|
||||||
// Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover
|
|
||||||
//
|
|
||||||
WebSocketPerMessageDeflateOptions::WebSocketPerMessageDeflateOptions(std::string extension)
|
|
||||||
{
|
|
||||||
extension = removeSpaces(extension);
|
|
||||||
|
|
||||||
_enabled = false;
|
|
||||||
_clientNoContextTakeover = false;
|
|
||||||
_serverNoContextTakeover = false;
|
|
||||||
_clientMaxWindowBits = kDefaultClientMaxWindowBits;
|
|
||||||
_serverMaxWindowBits = kDefaultServerMaxWindowBits;
|
|
||||||
|
|
||||||
// Split by ;
|
|
||||||
std::string token;
|
|
||||||
std::stringstream tokenStream(extension);
|
|
||||||
|
|
||||||
while (std::getline(tokenStream, token, ';'))
|
|
||||||
{
|
|
||||||
if (token == "permessage-deflate")
|
|
||||||
{
|
|
||||||
_enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token == "server_no_context_takeover")
|
|
||||||
{
|
|
||||||
_serverNoContextTakeover = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token == "client_no_context_takeover")
|
|
||||||
{
|
|
||||||
_clientNoContextTakeover = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith(token, "server_max_window_bits="))
|
|
||||||
{
|
|
||||||
std::string val = token.substr(token.find_last_of("=") + 1);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << val;
|
|
||||||
int x;
|
|
||||||
ss >> x;
|
|
||||||
|
|
||||||
// Sanitize values to be in the proper range [8, 15] in
|
|
||||||
// case a server would give us bogus values
|
|
||||||
_serverMaxWindowBits =
|
|
||||||
std::min(maxServerMaxWindowBits,
|
|
||||||
std::max(x, minServerMaxWindowBits));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWith(token, "client_max_window_bits="))
|
|
||||||
{
|
|
||||||
std::string val = token.substr(token.find_last_of("=") + 1);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << val;
|
|
||||||
int x;
|
|
||||||
ss >> x;
|
|
||||||
|
|
||||||
// Sanitize values to be in the proper range [8, 15] in
|
|
||||||
// case a server would give us bogus values
|
|
||||||
_clientMaxWindowBits =
|
|
||||||
std::min(maxClientMaxWindowBits,
|
|
||||||
std::max(x, minClientMaxWindowBits));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketPerMessageDeflateOptions::generateHeader()
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Sec-WebSocket-Extensions: permessage-deflate";
|
|
||||||
|
|
||||||
if (_clientNoContextTakeover) ss << "; client_no_context_takeover";
|
|
||||||
if (_serverNoContextTakeover) ss << "; server_no_context_takeover";
|
|
||||||
|
|
||||||
ss << "; server_max_window_bits=" << _serverMaxWindowBits;
|
|
||||||
ss << "; client_max_window_bits=" << _clientMaxWindowBits;
|
|
||||||
|
|
||||||
ss << "\r\n";
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::enabled() const
|
|
||||||
{
|
|
||||||
return _enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::getClientNoContextTakeover() const
|
|
||||||
{
|
|
||||||
return _clientNoContextTakeover;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::getServerNoContextTakeover() const
|
|
||||||
{
|
|
||||||
return _serverNoContextTakeover;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t WebSocketPerMessageDeflateOptions::getClientMaxWindowBits() const
|
|
||||||
{
|
|
||||||
return _clientMaxWindowBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t WebSocketPerMessageDeflateOptions::getServerMaxWindowBits() const
|
|
||||||
{
|
|
||||||
return _serverMaxWindowBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketPerMessageDeflateOptions::startsWith(const std::string& str,
|
|
||||||
const std::string& start)
|
|
||||||
{
|
|
||||||
return str.compare(0, start.length(), start) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
|
|
||||||
{
|
|
||||||
std::string out(str);
|
|
||||||
out.erase(std::remove_if(out.begin(),
|
|
||||||
out.end(),
|
|
||||||
[](unsigned char x){ return std::isspace(x); }),
|
|
||||||
out.end());
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketPerMessageDeflateOptions.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class WebSocketPerMessageDeflateOptions
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketPerMessageDeflateOptions(
|
|
||||||
bool enabled = false,
|
|
||||||
bool clientNoContextTakeover = false,
|
|
||||||
bool serverNoContextTakeover = false,
|
|
||||||
uint8_t clientMaxWindowBits = kDefaultClientMaxWindowBits,
|
|
||||||
uint8_t serverMaxWindowBits = kDefaultServerMaxWindowBits);
|
|
||||||
|
|
||||||
WebSocketPerMessageDeflateOptions(std::string extension);
|
|
||||||
|
|
||||||
std::string generateHeader();
|
|
||||||
bool enabled() const;
|
|
||||||
bool getClientNoContextTakeover() const;
|
|
||||||
bool getServerNoContextTakeover() const;
|
|
||||||
uint8_t getServerMaxWindowBits() const;
|
|
||||||
uint8_t getClientMaxWindowBits() const;
|
|
||||||
|
|
||||||
static bool startsWith(const std::string& str, const std::string& start);
|
|
||||||
static std::string removeSpaces(const std::string& str);
|
|
||||||
|
|
||||||
static uint8_t const kDefaultClientMaxWindowBits;
|
|
||||||
static uint8_t const kDefaultServerMaxWindowBits;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool _enabled;
|
|
||||||
bool _clientNoContextTakeover;
|
|
||||||
bool _serverNoContextTakeover;
|
|
||||||
int _clientMaxWindowBits;
|
|
||||||
int _serverMaxWindowBits;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketSendInfo.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
struct WebSocketSendInfo
|
|
||||||
{
|
|
||||||
bool success;
|
|
||||||
bool compressionError;
|
|
||||||
size_t payloadSize;
|
|
||||||
size_t wireSize;
|
|
||||||
|
|
||||||
WebSocketSendInfo(bool s = false, bool c = false, size_t p = 0, size_t w = 0)
|
|
||||||
: success(s)
|
|
||||||
, compressionError(c)
|
|
||||||
, payloadSize(p)
|
|
||||||
, wireSize(w)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketServer.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXWebSocketServer.h"
|
|
||||||
#include "IXWebSocketTransport.h"
|
|
||||||
#include "IXWebSocket.h"
|
|
||||||
#include "IXSocketConnect.h"
|
|
||||||
#include "IXNetSystem.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <future>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
|
|
||||||
const bool WebSocketServer::kDefaultEnablePong(true);
|
|
||||||
|
|
||||||
WebSocketServer::WebSocketServer(int port,
|
|
||||||
const std::string& host,
|
|
||||||
int backlog,
|
|
||||||
size_t maxConnections,
|
|
||||||
int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
|
|
||||||
_handshakeTimeoutSecs(handshakeTimeoutSecs),
|
|
||||||
_enablePong(kDefaultEnablePong)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketServer::~WebSocketServer()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketServer::stop()
|
|
||||||
{
|
|
||||||
stopAcceptingConnections();
|
|
||||||
|
|
||||||
auto clients = getClients();
|
|
||||||
for (auto client : clients)
|
|
||||||
{
|
|
||||||
client->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketServer::stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketServer::enablePong()
|
|
||||||
{
|
|
||||||
_enablePong = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketServer::disablePong()
|
|
||||||
{
|
|
||||||
_enablePong = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
|
||||||
{
|
|
||||||
_onConnectionCallback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketServer::handleConnection(
|
|
||||||
int fd,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState)
|
|
||||||
{
|
|
||||||
auto webSocket = std::make_shared<WebSocket>();
|
|
||||||
_onConnectionCallback(webSocket, connectionState);
|
|
||||||
|
|
||||||
webSocket->disableAutomaticReconnection();
|
|
||||||
|
|
||||||
if (_enablePong)
|
|
||||||
webSocket->enablePong();
|
|
||||||
else
|
|
||||||
webSocket->disablePong();
|
|
||||||
|
|
||||||
// Add this client to our client set
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
|
||||||
_clients.insert(webSocket);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto status = webSocket->connectToSocket(fd, _handshakeTimeoutSecs);
|
|
||||||
if (status.success)
|
|
||||||
{
|
|
||||||
// Process incoming messages and execute callbacks
|
|
||||||
// until the connection is closed
|
|
||||||
webSocket->run();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "WebSocketServer::handleConnection() error: "
|
|
||||||
<< status.http_status
|
|
||||||
<< " error: "
|
|
||||||
<< status.errorStr;
|
|
||||||
logError(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove this client from our client set
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
|
||||||
if (_clients.erase(webSocket) != 1)
|
|
||||||
{
|
|
||||||
logError("Cannot delete client");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logInfo("WebSocketServer::handleConnection() done");
|
|
||||||
connectionState->setTerminated();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::shared_ptr<WebSocket>> WebSocketServer::getClients()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
|
||||||
return _clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WebSocketServer::getConnectedClientsCount()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_clientsMutex);
|
|
||||||
return _clients.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketServer.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "IXSocketServer.h"
|
|
||||||
#include "IXWebSocket.h"
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <utility> // pair
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
using OnConnectionCallback =
|
|
||||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
|
||||||
|
|
||||||
class WebSocketServer final : public SocketServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
|
||||||
size_t maxConnections = SocketServer::kDefaultMaxConnections,
|
|
||||||
int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
|
|
||||||
virtual ~WebSocketServer();
|
|
||||||
virtual void stop() final;
|
|
||||||
|
|
||||||
void enablePong();
|
|
||||||
void disablePong();
|
|
||||||
|
|
||||||
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
|
||||||
|
|
||||||
// Get all the connected clients
|
|
||||||
std::set<std::shared_ptr<WebSocket>> getClients();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Member variables
|
|
||||||
int _handshakeTimeoutSecs;
|
|
||||||
bool _enablePong;
|
|
||||||
|
|
||||||
OnConnectionCallback _onConnectionCallback;
|
|
||||||
|
|
||||||
std::mutex _clientsMutex;
|
|
||||||
std::set<std::shared_ptr<WebSocket>> _clients;
|
|
||||||
|
|
||||||
const static int kDefaultHandShakeTimeoutSecs;
|
|
||||||
const static bool kDefaultEnablePong;
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
virtual void handleConnection(int fd,
|
|
||||||
std::shared_ptr<ConnectionState> connectionState) final;
|
|
||||||
virtual size_t getConnectedClientsCount() final;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,251 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXWebSocketTransport.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
//
|
|
||||||
// Adapted from https://github.com/dhbaird/easywsclient
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "IXCancellationRequest.h"
|
|
||||||
#include "IXProgressCallback.h"
|
|
||||||
#include "IXWebSocketCloseConstants.h"
|
|
||||||
#include "IXWebSocketHandshake.h"
|
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflate.h"
|
|
||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
|
|
||||||
#include "IXWebSocketSendInfo.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <list>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class Socket;
|
|
||||||
|
|
||||||
enum class SendMessageKind
|
|
||||||
{
|
|
||||||
Text,
|
|
||||||
Binary,
|
|
||||||
Ping
|
|
||||||
};
|
|
||||||
|
|
||||||
class WebSocketTransport
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum class ReadyState
|
|
||||||
{
|
|
||||||
CLOSING,
|
|
||||||
CLOSED,
|
|
||||||
CONNECTING,
|
|
||||||
OPEN
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class MessageKind
|
|
||||||
{
|
|
||||||
MSG,
|
|
||||||
PING,
|
|
||||||
PONG,
|
|
||||||
FRAGMENT
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class PollResult
|
|
||||||
{
|
|
||||||
Succeeded,
|
|
||||||
AbnormalClose
|
|
||||||
};
|
|
||||||
|
|
||||||
using OnMessageCallback =
|
|
||||||
std::function<void(const std::string&, size_t, bool, MessageKind)>;
|
|
||||||
using OnCloseCallback = std::function<void(uint16_t, const std::string&, size_t, bool)>;
|
|
||||||
|
|
||||||
WebSocketTransport();
|
|
||||||
~WebSocketTransport();
|
|
||||||
|
|
||||||
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
|
||||||
bool enablePong,
|
|
||||||
int pingIntervalSecs,
|
|
||||||
int pingTimeoutSecs);
|
|
||||||
|
|
||||||
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
|
||||||
int timeoutSecs);
|
|
||||||
WebSocketInitResult connectToSocket(int fd, // Server
|
|
||||||
int timeoutSecs);
|
|
||||||
|
|
||||||
PollResult poll();
|
|
||||||
WebSocketSendInfo sendBinary(const std::string& message,
|
|
||||||
const OnProgressCallback& onProgressCallback);
|
|
||||||
WebSocketSendInfo sendText(const std::string& message,
|
|
||||||
const OnProgressCallback& onProgressCallback);
|
|
||||||
WebSocketSendInfo sendPing(const std::string& message);
|
|
||||||
|
|
||||||
void close(uint16_t code = WebSocketCloseConstants::kNormalClosureCode,
|
|
||||||
const std::string& reason = WebSocketCloseConstants::kNormalClosureMessage,
|
|
||||||
size_t closeWireSize = 0,
|
|
||||||
bool remote = false);
|
|
||||||
|
|
||||||
void closeSocket();
|
|
||||||
ssize_t send();
|
|
||||||
|
|
||||||
ReadyState getReadyState() const;
|
|
||||||
void setReadyState(ReadyState readyState);
|
|
||||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
|
||||||
void dispatch(PollResult pollResult, const OnMessageCallback& onMessageCallback);
|
|
||||||
size_t bufferedAmount() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string _url;
|
|
||||||
|
|
||||||
struct wsheader_type
|
|
||||||
{
|
|
||||||
unsigned header_size;
|
|
||||||
bool fin;
|
|
||||||
bool rsv1;
|
|
||||||
bool mask;
|
|
||||||
enum opcode_type
|
|
||||||
{
|
|
||||||
CONTINUATION = 0x0,
|
|
||||||
TEXT_FRAME = 0x1,
|
|
||||||
BINARY_FRAME = 0x2,
|
|
||||||
CLOSE = 8,
|
|
||||||
PING = 9,
|
|
||||||
PONG = 0xa,
|
|
||||||
} opcode;
|
|
||||||
int N0;
|
|
||||||
uint64_t N;
|
|
||||||
uint8_t masking_key[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tells whether we should mask the data we send.
|
|
||||||
// client should mask but server should not
|
|
||||||
std::atomic<bool> _useMask;
|
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
|
||||||
std::vector<uint8_t> _readbuf;
|
|
||||||
|
|
||||||
// Contains all messages that were fetched in the last socket read.
|
|
||||||
// This could be a mix of control messages (Close, Ping, etc...) and
|
|
||||||
// data messages. That buffer
|
|
||||||
std::vector<uint8_t> _rxbuf;
|
|
||||||
|
|
||||||
// Contains all messages that are waiting to be sent
|
|
||||||
std::vector<uint8_t> _txbuf;
|
|
||||||
mutable std::mutex _txbufMutex;
|
|
||||||
|
|
||||||
// Hold fragments for multi-fragments messages in a list. We support receiving very large
|
|
||||||
// messages (tested messages up to 700M) and we cannot put them in a single
|
|
||||||
// buffer that is resized, as this operation can be slow when a buffer has its
|
|
||||||
// size increased 2 fold, while appending to a list has a fixed cost.
|
|
||||||
std::list<std::vector<uint8_t>> _chunks;
|
|
||||||
|
|
||||||
// Fragments are 32K long
|
|
||||||
static constexpr size_t kChunkSize = 1 << 15;
|
|
||||||
|
|
||||||
// Underlying TCP socket
|
|
||||||
std::shared_ptr<Socket> _socket;
|
|
||||||
std::mutex _socketMutex;
|
|
||||||
|
|
||||||
// Hold the state of the connection (OPEN, CLOSED, etc...)
|
|
||||||
std::atomic<ReadyState> _readyState;
|
|
||||||
|
|
||||||
OnCloseCallback _onCloseCallback;
|
|
||||||
uint16_t _closeCode;
|
|
||||||
std::string _closeReason;
|
|
||||||
size_t _closeWireSize;
|
|
||||||
bool _closeRemote;
|
|
||||||
mutable std::mutex _closeDataMutex;
|
|
||||||
|
|
||||||
// Data used for Per Message Deflate compression (with zlib)
|
|
||||||
WebSocketPerMessageDeflate _perMessageDeflate;
|
|
||||||
WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
|
|
||||||
std::atomic<bool> _enablePerMessageDeflate;
|
|
||||||
|
|
||||||
// Used to cancel dns lookup + socket connect + http upgrade
|
|
||||||
std::atomic<bool> _requestInitCancellation;
|
|
||||||
|
|
||||||
mutable std::mutex _closingTimePointMutex;
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _closingTimePoint;
|
|
||||||
static const int kClosingMaximumWaitingDelayInMs;
|
|
||||||
|
|
||||||
// enable auto response to ping
|
|
||||||
std::atomic<bool> _enablePong;
|
|
||||||
static const bool kDefaultEnablePong;
|
|
||||||
|
|
||||||
// Optional ping and pong timeout
|
|
||||||
// if both ping interval and timeout are set (> 0),
|
|
||||||
// then use GCD of these value to wait for the lowest time
|
|
||||||
int _pingIntervalSecs;
|
|
||||||
int _pingTimeoutSecs;
|
|
||||||
int _pingIntervalOrTimeoutGCDSecs;
|
|
||||||
|
|
||||||
static const int kDefaultPingIntervalSecs;
|
|
||||||
static const int kDefaultPingTimeoutSecs;
|
|
||||||
static const std::string kPingMessage;
|
|
||||||
|
|
||||||
// Record time step for ping/ ping timeout to ensure we wait for the right left duration
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _nextGCDTimePoint;
|
|
||||||
|
|
||||||
// We record when ping are being sent so that we can know when to send the next one
|
|
||||||
// We also record when pong are being sent as a reply to pings, to close the connections
|
|
||||||
// if no pong were received sufficiently fast.
|
|
||||||
mutable std::mutex _lastSendPingTimePointMutex;
|
|
||||||
mutable std::mutex _lastReceivePongTimePointMutex;
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _lastSendPingTimePoint;
|
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _lastReceivePongTimePoint;
|
|
||||||
|
|
||||||
// If this function returns true, it is time to send a new ping
|
|
||||||
bool pingIntervalExceeded();
|
|
||||||
|
|
||||||
// No PONG data was received through the socket for longer than ping timeout delay
|
|
||||||
bool pingTimeoutExceeded();
|
|
||||||
|
|
||||||
// after calling close(), if no CLOSE frame answer is received back from the remote, we
|
|
||||||
// should close the connexion
|
|
||||||
bool closingDelayExceeded();
|
|
||||||
|
|
||||||
void initTimePointsAndGCDAfterConnect();
|
|
||||||
|
|
||||||
void sendCloseFrame(uint16_t code, const std::string& reason);
|
|
||||||
|
|
||||||
void closeSocketAndSwitchToClosedState(uint16_t code,
|
|
||||||
const std::string& reason,
|
|
||||||
size_t closeWireSize,
|
|
||||||
bool remote);
|
|
||||||
|
|
||||||
void sendOnSocket();
|
|
||||||
WebSocketSendInfo sendData(wsheader_type::opcode_type type,
|
|
||||||
const std::string& message,
|
|
||||||
bool compress,
|
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
|
||||||
|
|
||||||
void sendFragment(wsheader_type::opcode_type type,
|
|
||||||
bool fin,
|
|
||||||
std::string::const_iterator begin,
|
|
||||||
std::string::const_iterator end,
|
|
||||||
bool compress);
|
|
||||||
|
|
||||||
void emitMessage(MessageKind messageKind,
|
|
||||||
const std::string& message,
|
|
||||||
const wsheader_type& ws,
|
|
||||||
const OnMessageCallback& onMessageCallback);
|
|
||||||
|
|
||||||
bool isSendBufferEmpty() const;
|
|
||||||
void appendToSendBuffer(const std::vector<uint8_t>& header,
|
|
||||||
std::string::const_iterator begin,
|
|
||||||
std::string::const_iterator end,
|
|
||||||
uint64_t message_size,
|
|
||||||
uint8_t masking_key[4]);
|
|
||||||
|
|
||||||
unsigned getRandomUnsigned();
|
|
||||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
|
||||||
|
|
||||||
std::string getMergedChunks() const;
|
|
||||||
};
|
|
||||||
} // namespace ix
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
/*
|
|
||||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
|
||||||
* https://github.com/corporateshark/LUrlParser
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
|
||||||
*
|
|
||||||
* 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 "LUrlParser.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
// check if the scheme name is valid
|
|
||||||
static bool IsSchemeValid( const std::string& SchemeName )
|
|
||||||
{
|
|
||||||
for ( auto c : SchemeName )
|
|
||||||
{
|
|
||||||
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
|
|
||||||
{
|
|
||||||
if ( !IsValid() ) { return false; }
|
|
||||||
|
|
||||||
int Port = atoi( m_Port.c_str() );
|
|
||||||
|
|
||||||
if ( Port <= 0 || Port > 65535 ) { return false; }
|
|
||||||
|
|
||||||
if ( OutPort ) { *OutPort = Port; }
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on RFC 1738 and RFC 3986
|
|
||||||
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
|
|
||||||
{
|
|
||||||
LUrlParser::clParseURL Result;
|
|
||||||
|
|
||||||
const char* CurrentString = URL.c_str();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* <scheme>:<scheme-specific-part>
|
|
||||||
* <scheme> := [a-z\+\-\.]+
|
|
||||||
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
|
|
||||||
*/
|
|
||||||
|
|
||||||
// try to read scheme
|
|
||||||
{
|
|
||||||
const char* LocalString = strchr( CurrentString, ':' );
|
|
||||||
|
|
||||||
if ( !LocalString )
|
|
||||||
{
|
|
||||||
return clParseURL( LUrlParserError_NoUrlCharacter );
|
|
||||||
}
|
|
||||||
|
|
||||||
// save the scheme name
|
|
||||||
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
if ( !IsSchemeValid( Result.m_Scheme ) )
|
|
||||||
{
|
|
||||||
return clParseURL( LUrlParserError_InvalidSchemeName );
|
|
||||||
}
|
|
||||||
|
|
||||||
// scheme should be lowercase
|
|
||||||
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
|
|
||||||
|
|
||||||
// skip ':'
|
|
||||||
CurrentString = LocalString+1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* //<user>:<password>@<host>:<port>/<url-path>
|
|
||||||
* any ":", "@" and "/" must be normalized
|
|
||||||
*/
|
|
||||||
|
|
||||||
// skip "//"
|
|
||||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
|
||||||
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
|
|
||||||
|
|
||||||
// check if the user name and password are specified
|
|
||||||
bool bHasUserName = false;
|
|
||||||
|
|
||||||
const char* LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString )
|
|
||||||
{
|
|
||||||
if ( *LocalString == '@' )
|
|
||||||
{
|
|
||||||
// user name and password are specified
|
|
||||||
bHasUserName = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if ( *LocalString == '/' )
|
|
||||||
{
|
|
||||||
// end of <host>:<port> specification
|
|
||||||
bHasUserName = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// user name and password
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
if ( bHasUserName )
|
|
||||||
{
|
|
||||||
// read user name
|
|
||||||
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
// proceed with the current pointer
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
if ( *CurrentString == ':' )
|
|
||||||
{
|
|
||||||
// skip ':'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read password
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString && *LocalString != '@' ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip '@'
|
|
||||||
if ( *CurrentString != '@' )
|
|
||||||
{
|
|
||||||
return clParseURL( LUrlParserError_NoAtSign );
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bHasBracket = ( *CurrentString == '[' );
|
|
||||||
|
|
||||||
// go ahead, read the host name
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString )
|
|
||||||
{
|
|
||||||
if ( bHasBracket && *LocalString == ']' )
|
|
||||||
{
|
|
||||||
// end of IPv6 address
|
|
||||||
LocalString++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
|
|
||||||
{
|
|
||||||
// port number is specified
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalString++;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
// is port number specified?
|
|
||||||
if ( *CurrentString == ':' )
|
|
||||||
{
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read port number
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString && *LocalString != '/' ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// end of string
|
|
||||||
if ( !*CurrentString )
|
|
||||||
{
|
|
||||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip '/'
|
|
||||||
if ( *CurrentString != '/' )
|
|
||||||
{
|
|
||||||
return clParseURL( LUrlParserError_NoSlash );
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// parse the path
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
|
|
||||||
// check for query
|
|
||||||
if ( *CurrentString == '?' )
|
|
||||||
{
|
|
||||||
// skip '?'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read query
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString && *LocalString != '#' ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
|
|
||||||
CurrentString = LocalString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for fragment
|
|
||||||
if ( *CurrentString == '#' )
|
|
||||||
{
|
|
||||||
// skip '#'
|
|
||||||
CurrentString++;
|
|
||||||
|
|
||||||
// read fragment
|
|
||||||
LocalString = CurrentString;
|
|
||||||
|
|
||||||
while ( *LocalString ) LocalString++;
|
|
||||||
|
|
||||||
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.m_ErrorCode = LUrlParserError_Ok;
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
|
|
||||||
* https://github.com/corporateshark/LUrlParser
|
|
||||||
*
|
|
||||||
* The MIT License (MIT)
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace LUrlParser
|
|
||||||
{
|
|
||||||
enum LUrlParserError
|
|
||||||
{
|
|
||||||
LUrlParserError_Ok = 0,
|
|
||||||
LUrlParserError_Uninitialized = 1,
|
|
||||||
LUrlParserError_NoUrlCharacter = 2,
|
|
||||||
LUrlParserError_InvalidSchemeName = 3,
|
|
||||||
LUrlParserError_NoDoubleSlash = 4,
|
|
||||||
LUrlParserError_NoAtSign = 5,
|
|
||||||
LUrlParserError_UnexpectedEndOfLine = 6,
|
|
||||||
LUrlParserError_NoSlash = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
class clParseURL
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LUrlParserError m_ErrorCode;
|
|
||||||
std::string m_Scheme;
|
|
||||||
std::string m_Host;
|
|
||||||
std::string m_Port;
|
|
||||||
std::string m_Path;
|
|
||||||
std::string m_Query;
|
|
||||||
std::string m_Fragment;
|
|
||||||
std::string m_UserName;
|
|
||||||
std::string m_Password;
|
|
||||||
|
|
||||||
clParseURL()
|
|
||||||
: m_ErrorCode(LUrlParserError_Uninitialized)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return 'true' if the parsing was successful
|
|
||||||
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
|
|
||||||
|
|
||||||
/// helper to convert the port number to int, return 'true' if the port is valid (within the
|
|
||||||
/// 0..65535 range)
|
|
||||||
bool GetPort(int* OutPort) const;
|
|
||||||
|
|
||||||
/// parse the URL
|
|
||||||
static clParseURL ParseURL(const std::string& URL);
|
|
||||||
|
|
||||||
private:
|
|
||||||
explicit clParseURL(LUrlParserError ErrorCode)
|
|
||||||
: m_ErrorCode(ErrorCode)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace LUrlParser
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_apple.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Apple reserves 16 bytes for its thread names
|
|
||||||
// Notice that the Apple version of pthread_setname_np
|
|
||||||
// does not take a pthread_t argument
|
|
||||||
//
|
|
||||||
pthread_setname_np(name.substr(0, 63).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
// Copyright (c) 2016 Alex Hultman and contributors
|
|
||||||
|
|
||||||
// This software is provided 'as-is', without any express or implied
|
|
||||||
// warranty. In no event will the authors be held liable for any damages
|
|
||||||
// arising from the use of this software.
|
|
||||||
|
|
||||||
// Permission is granted to anyone to use this software for any purpose,
|
|
||||||
// including commercial applications, and to alter it and redistribute it
|
|
||||||
// freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
// 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
// claim that you wrote the original software. If you use this software
|
|
||||||
// in a product, an acknowledgement in the product documentation would be
|
|
||||||
// appreciated but is not required.
|
|
||||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
// misrepresented as being the original software.
|
|
||||||
// 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
class WebSocketHandshakeKeyGen {
|
|
||||||
template <int N, typename T>
|
|
||||||
struct static_for {
|
|
||||||
void operator()(uint32_t *a, uint32_t *b) {
|
|
||||||
static_for<N - 1, T>()(a, b);
|
|
||||||
T::template f<N - 1>(a, b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct static_for<0, T> {
|
|
||||||
void operator()(uint32_t * /*a*/, uint32_t * /*hash*/) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <int state>
|
|
||||||
struct Sha1Loop {
|
|
||||||
static inline uint32_t rol(uint32_t value, size_t bits) {return (value << bits) | (value >> (32 - bits));}
|
|
||||||
static inline uint32_t blk(uint32_t b[16], size_t i) {
|
|
||||||
return rol(b[(i + 13) & 15] ^ b[(i + 8) & 15] ^ b[(i + 2) & 15] ^ b[i], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <int i>
|
|
||||||
static inline void f(uint32_t *a, uint32_t *b) {
|
|
||||||
switch (state) {
|
|
||||||
case 1:
|
|
||||||
a[i % 5] += ((a[(3 + i) % 5] & (a[(2 + i) % 5] ^ a[(1 + i) % 5])) ^ a[(1 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
b[i] = blk(b, i);
|
|
||||||
a[(1 + i) % 5] += ((a[(4 + i) % 5] & (a[(3 + i) % 5] ^ a[(2 + i) % 5])) ^ a[(2 + i) % 5]) + b[i] + 0x5a827999 + rol(a[(5 + i) % 5], 5);
|
|
||||||
a[(4 + i) % 5] = rol(a[(4 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
b[(i + 4) % 16] = blk(b, (i + 4) % 16);
|
|
||||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 4) % 16] + 0x6ed9eba1 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
b[(i + 8) % 16] = blk(b, (i + 8) % 16);
|
|
||||||
a[i % 5] += (((a[(3 + i) % 5] | a[(2 + i) % 5]) & a[(1 + i) % 5]) | (a[(3 + i) % 5] & a[(2 + i) % 5])) + b[(i + 8) % 16] + 0x8f1bbcdc + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
b[(i + 12) % 16] = blk(b, (i + 12) % 16);
|
|
||||||
a[i % 5] += (a[(3 + i) % 5] ^ a[(2 + i) % 5] ^ a[(1 + i) % 5]) + b[(i + 12) % 16] + 0xca62c1d6 + rol(a[(4 + i) % 5], 5);
|
|
||||||
a[(3 + i) % 5] = rol(a[(3 + i) % 5], 30);
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
b[i] += a[4 - i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline void sha1(uint32_t hash[5], uint32_t b[16]) {
|
|
||||||
uint32_t a[5] = {hash[4], hash[3], hash[2], hash[1], hash[0]};
|
|
||||||
static_for<16, Sha1Loop<1>>()(a, b);
|
|
||||||
static_for<4, Sha1Loop<2>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<3>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<4>>()(a, b);
|
|
||||||
static_for<20, Sha1Loop<5>>()(a, b);
|
|
||||||
static_for<5, Sha1Loop<6>>()(a, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void base64(unsigned char *src, char *dst) {
|
|
||||||
const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
for (int i = 0; i < 18; i += 3) {
|
|
||||||
*dst++ = b64[(src[i] >> 2) & 63];
|
|
||||||
*dst++ = b64[((src[i] & 3) << 4) | ((src[i + 1] & 240) >> 4)];
|
|
||||||
*dst++ = b64[((src[i + 1] & 15) << 2) | ((src[i + 2] & 192) >> 6)];
|
|
||||||
*dst++ = b64[src[i + 2] & 63];
|
|
||||||
}
|
|
||||||
*dst++ = b64[(src[18] >> 2) & 63];
|
|
||||||
*dst++ = b64[((src[18] & 3) << 4) | ((src[19] & 240) >> 4)];
|
|
||||||
*dst++ = b64[((src[19] & 15) << 2)];
|
|
||||||
*dst++ = '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static inline void generate(const std::string& inputStr, char output[28]) {
|
|
||||||
|
|
||||||
char input[25] = {};
|
|
||||||
strncpy(input, inputStr.c_str(), 25 - 1);
|
|
||||||
input[25 - 1] = '\0';
|
|
||||||
|
|
||||||
uint32_t b_output[5] = {
|
|
||||||
0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
|
|
||||||
};
|
|
||||||
uint32_t b_input[16] = {
|
|
||||||
0, 0, 0, 0, 0, 0, 0x32353845, 0x41464135, 0x2d453931, 0x342d3437, 0x44412d39,
|
|
||||||
0x3543412d, 0x43354142, 0x30444338, 0x35423131, 0x80000000
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
b_input[i] = (input[4 * i + 3] & 0xff) | (input[4 * i + 2] & 0xff) << 8 | (input[4 * i + 1] & 0xff) << 16 | (input[4 * i + 0] & 0xff) << 24;
|
|
||||||
}
|
|
||||||
sha1(b_output, b_input);
|
|
||||||
uint32_t last_b[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480};
|
|
||||||
sha1(b_output, last_b);
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
uint32_t tmp = b_output[i];
|
|
||||||
char *bytes = (char *) &b_output[i];
|
|
||||||
bytes[3] = tmp & 0xff;
|
|
||||||
bytes[2] = (tmp >> 8) & 0xff;
|
|
||||||
bytes[1] = (tmp >> 16) & 0xff;
|
|
||||||
bytes[0] = (tmp >> 24) & 0xff;
|
|
||||||
}
|
|
||||||
base64((unsigned char *) b_output, output);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_linux.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// Linux only reserves 16 bytes for its thread names
|
|
||||||
// See prctl and PR_SET_NAME property in
|
|
||||||
// http://man7.org/linux/man-pages/man2/prctl.2.html
|
|
||||||
//
|
|
||||||
pthread_setname_np(pthread_self(),
|
|
||||||
name.substr(0, 15).c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXSetThreadName_windows.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
#include "../IXSetThreadName.h"
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
const DWORD MS_VC_EXCEPTION = 0x406D1388;
|
|
||||||
|
|
||||||
#pragma pack(push,8)
|
|
||||||
typedef struct tagTHREADNAME_INFO
|
|
||||||
{
|
|
||||||
DWORD dwType; // Must be 0x1000.
|
|
||||||
LPCSTR szName; // Pointer to name (in user addr space).
|
|
||||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
|
||||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
|
||||||
} THREADNAME_INFO;
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
void SetThreadName(DWORD dwThreadID, const char* threadName)
|
|
||||||
{
|
|
||||||
THREADNAME_INFO info;
|
|
||||||
info.dwType = 0x1000;
|
|
||||||
info.szName = threadName;
|
|
||||||
info.dwThreadID = dwThreadID;
|
|
||||||
info.dwFlags = 0;
|
|
||||||
|
|
||||||
__try
|
|
||||||
{
|
|
||||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)& info);
|
|
||||||
}
|
|
||||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setThreadName(const std::string& name)
|
|
||||||
{
|
|
||||||
SetThreadName(-1, name.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+283
@@ -0,0 +1,283 @@
|
|||||||
|
function getSearchTerm() {
|
||||||
|
var sPageURL = window.location.search.substring(1);
|
||||||
|
var sURLVariables = sPageURL.split('&');
|
||||||
|
for (var i = 0; i < sURLVariables.length; i++) {
|
||||||
|
var sParameterName = sURLVariables[i].split('=');
|
||||||
|
if (sParameterName[0] == 'q') {
|
||||||
|
return sParameterName[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTopPadding() {
|
||||||
|
// Update various absolute positions to match where the main container
|
||||||
|
// starts. This is necessary for handling multi-line nav headers, since
|
||||||
|
// that pushes the main container down.
|
||||||
|
var offset = $('body > .container').offset();
|
||||||
|
$('html').css('scroll-padding-top', offset.top + 'px');
|
||||||
|
$('.bs-sidebar.affix').css('top', offset.top + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
applyTopPadding();
|
||||||
|
|
||||||
|
var search_term = getSearchTerm(),
|
||||||
|
$search_modal = $('#mkdocs_search_modal'),
|
||||||
|
$keyboard_modal = $('#mkdocs_keyboard_modal');
|
||||||
|
|
||||||
|
if (search_term) {
|
||||||
|
$search_modal.modal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure search input gets autofocus every time modal opens.
|
||||||
|
$search_modal.on('shown.bs.modal', function() {
|
||||||
|
$search_modal.find('#mkdocs-search-query').focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close search modal when result is selected
|
||||||
|
// The links get added later so listen to parent
|
||||||
|
$('#mkdocs-search-results').click(function(e) {
|
||||||
|
if ($(e.target).is('a')) {
|
||||||
|
$search_modal.modal('hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate keyboard modal with proper Keys
|
||||||
|
$keyboard_modal.find('.help.shortcut kbd')[0].innerHTML = keyCodes[shortcuts.help];
|
||||||
|
$keyboard_modal.find('.prev.shortcut kbd')[0].innerHTML = keyCodes[shortcuts.previous];
|
||||||
|
$keyboard_modal.find('.next.shortcut kbd')[0].innerHTML = keyCodes[shortcuts.next];
|
||||||
|
$keyboard_modal.find('.search.shortcut kbd')[0].innerHTML = keyCodes[shortcuts.search];
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
|
document.addEventListener("keydown", function(e) {
|
||||||
|
if ($(e.target).is(':input')) return true;
|
||||||
|
var key = e.which || e.keyCode || window.event && window.event.keyCode;
|
||||||
|
var page;
|
||||||
|
switch (key) {
|
||||||
|
case shortcuts.next:
|
||||||
|
page = $('.navbar a[rel="next"]:first').prop('href');
|
||||||
|
break;
|
||||||
|
case shortcuts.previous:
|
||||||
|
page = $('.navbar a[rel="prev"]:first').prop('href');
|
||||||
|
break;
|
||||||
|
case shortcuts.search:
|
||||||
|
e.preventDefault();
|
||||||
|
$keyboard_modal.modal('hide');
|
||||||
|
$search_modal.modal('show');
|
||||||
|
$search_modal.find('#mkdocs-search-query').focus();
|
||||||
|
break;
|
||||||
|
case shortcuts.help:
|
||||||
|
$search_modal.modal('hide');
|
||||||
|
$keyboard_modal.modal('show');
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (page) {
|
||||||
|
$keyboard_modal.modal('hide');
|
||||||
|
window.location.href = page;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('table').addClass('table table-striped table-hover');
|
||||||
|
|
||||||
|
// Improve the scrollspy behaviour when users click on a TOC item.
|
||||||
|
$(".bs-sidenav a").on("click", function() {
|
||||||
|
var clicked = this;
|
||||||
|
setTimeout(function() {
|
||||||
|
var active = $('.nav li.active a');
|
||||||
|
active = active[active.length - 1];
|
||||||
|
if (clicked !== active) {
|
||||||
|
$(active).parent().removeClass("active");
|
||||||
|
$(clicked).parent().addClass("active");
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
function showInnerDropdown(item) {
|
||||||
|
var popup = $(item).next('.dropdown-menu');
|
||||||
|
popup.addClass('show');
|
||||||
|
$(item).addClass('open');
|
||||||
|
|
||||||
|
// First, close any sibling dropdowns.
|
||||||
|
var container = $(item).parent().parent();
|
||||||
|
container.find('> .dropdown-submenu > a').each(function(i, el) {
|
||||||
|
if (el !== item) {
|
||||||
|
hideInnerDropdown(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var popupMargin = 10;
|
||||||
|
var maxBottom = $(window).height() - popupMargin;
|
||||||
|
var bounds = item.getBoundingClientRect();
|
||||||
|
|
||||||
|
popup.css('left', bounds.right + 'px');
|
||||||
|
if (bounds.top + popup.height() > maxBottom &&
|
||||||
|
bounds.top > $(window).height() / 2) {
|
||||||
|
popup.css({
|
||||||
|
'top': (bounds.bottom - popup.height()) + 'px',
|
||||||
|
'max-height': (bounds.bottom - popupMargin) + 'px',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
popup.css({
|
||||||
|
'top': bounds.top + 'px',
|
||||||
|
'max-height': (maxBottom - bounds.top) + 'px',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInnerDropdown(item) {
|
||||||
|
var popup = $(item).next('.dropdown-menu');
|
||||||
|
popup.removeClass('show');
|
||||||
|
$(item).removeClass('open');
|
||||||
|
|
||||||
|
popup.scrollTop(0);
|
||||||
|
popup.find('.dropdown-menu').scrollTop(0).removeClass('show');
|
||||||
|
popup.find('.dropdown-submenu > a').removeClass('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.dropdown-submenu > a').on('click', function(e) {
|
||||||
|
if ($(this).next('.dropdown-menu').hasClass('show')) {
|
||||||
|
hideInnerDropdown(this);
|
||||||
|
} else {
|
||||||
|
showInnerDropdown(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.dropdown-menu').parent().on('hide.bs.dropdown', function(e) {
|
||||||
|
$(this).find('.dropdown-menu').scrollTop(0);
|
||||||
|
$(this).find('.dropdown-submenu > a').removeClass('open');
|
||||||
|
$(this).find('.dropdown-menu .dropdown-menu').removeClass('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('resize', applyTopPadding);
|
||||||
|
|
||||||
|
$('body').scrollspy({
|
||||||
|
target: '.bs-sidebar',
|
||||||
|
offset: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Prevent disabled links from causing a page reload */
|
||||||
|
$("li.disabled a").click(function() {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// See https://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
|
||||||
|
// We only list common keys below. Obscure keys are omitted and their use is discouraged.
|
||||||
|
var keyCodes = {
|
||||||
|
8: 'backspace',
|
||||||
|
9: 'tab',
|
||||||
|
13: 'enter',
|
||||||
|
16: 'shift',
|
||||||
|
17: 'ctrl',
|
||||||
|
18: 'alt',
|
||||||
|
19: 'pause/break',
|
||||||
|
20: 'caps lock',
|
||||||
|
27: 'escape',
|
||||||
|
32: 'spacebar',
|
||||||
|
33: 'page up',
|
||||||
|
34: 'page down',
|
||||||
|
35: 'end',
|
||||||
|
36: 'home',
|
||||||
|
37: '←',
|
||||||
|
38: '↑',
|
||||||
|
39: '→',
|
||||||
|
40: '↓',
|
||||||
|
45: 'insert',
|
||||||
|
46: 'delete',
|
||||||
|
48: '0',
|
||||||
|
49: '1',
|
||||||
|
50: '2',
|
||||||
|
51: '3',
|
||||||
|
52: '4',
|
||||||
|
53: '5',
|
||||||
|
54: '6',
|
||||||
|
55: '7',
|
||||||
|
56: '8',
|
||||||
|
57: '9',
|
||||||
|
65: 'a',
|
||||||
|
66: 'b',
|
||||||
|
67: 'c',
|
||||||
|
68: 'd',
|
||||||
|
69: 'e',
|
||||||
|
70: 'f',
|
||||||
|
71: 'g',
|
||||||
|
72: 'h',
|
||||||
|
73: 'i',
|
||||||
|
74: 'j',
|
||||||
|
75: 'k',
|
||||||
|
76: 'l',
|
||||||
|
77: 'm',
|
||||||
|
78: 'n',
|
||||||
|
79: 'o',
|
||||||
|
80: 'p',
|
||||||
|
81: 'q',
|
||||||
|
82: 'r',
|
||||||
|
83: 's',
|
||||||
|
84: 't',
|
||||||
|
85: 'u',
|
||||||
|
86: 'v',
|
||||||
|
87: 'w',
|
||||||
|
88: 'x',
|
||||||
|
89: 'y',
|
||||||
|
90: 'z',
|
||||||
|
91: 'Left Windows Key / Left ⌘',
|
||||||
|
92: 'Right Windows Key',
|
||||||
|
93: 'Windows Menu / Right ⌘',
|
||||||
|
96: 'numpad 0',
|
||||||
|
97: 'numpad 1',
|
||||||
|
98: 'numpad 2',
|
||||||
|
99: 'numpad 3',
|
||||||
|
100: 'numpad 4',
|
||||||
|
101: 'numpad 5',
|
||||||
|
102: 'numpad 6',
|
||||||
|
103: 'numpad 7',
|
||||||
|
104: 'numpad 8',
|
||||||
|
105: 'numpad 9',
|
||||||
|
106: 'multiply',
|
||||||
|
107: 'add',
|
||||||
|
109: 'subtract',
|
||||||
|
110: 'decimal point',
|
||||||
|
111: 'divide',
|
||||||
|
112: 'f1',
|
||||||
|
113: 'f2',
|
||||||
|
114: 'f3',
|
||||||
|
115: 'f4',
|
||||||
|
116: 'f5',
|
||||||
|
117: 'f6',
|
||||||
|
118: 'f7',
|
||||||
|
119: 'f8',
|
||||||
|
120: 'f9',
|
||||||
|
121: 'f10',
|
||||||
|
122: 'f11',
|
||||||
|
123: 'f12',
|
||||||
|
124: 'f13',
|
||||||
|
125: 'f14',
|
||||||
|
126: 'f15',
|
||||||
|
127: 'f16',
|
||||||
|
128: 'f17',
|
||||||
|
129: 'f18',
|
||||||
|
130: 'f19',
|
||||||
|
131: 'f20',
|
||||||
|
132: 'f21',
|
||||||
|
133: 'f22',
|
||||||
|
134: 'f23',
|
||||||
|
135: 'f24',
|
||||||
|
144: 'num lock',
|
||||||
|
145: 'scroll lock',
|
||||||
|
186: ';',
|
||||||
|
187: '=',
|
||||||
|
188: ',',
|
||||||
|
189: '‐',
|
||||||
|
190: '.',
|
||||||
|
191: '?',
|
||||||
|
192: '`',
|
||||||
|
219: '[',
|
||||||
|
220: '\',
|
||||||
|
221: ']',
|
||||||
|
222: ''',
|
||||||
|
};
|
||||||
Vendored
+7
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user