Compare commits
71 Commits
feature/ht
...
feature/us
Author | SHA1 | Date | |
---|---|---|---|
5e1a4541bf | |||
2e9c610ac9 | |||
eb063ec60a | |||
37fb14646d | |||
ae543518d3 | |||
c865d64608 | |||
3004422cb6 | |||
0c46a17443 | |||
497373d976 | |||
91198aca0d | |||
b17a5e5f0b | |||
3f0ef59f65 | |||
1e96edc293 | |||
0afb77393b | |||
7614b642bb | |||
bc89580dfe | |||
358ae13a88 | |||
ccf9dcba70 | |||
94604fad61 | |||
5c4cc7c50d | |||
9ed961ec06 | |||
e6bd8cc8c4 | |||
ee25bd0f92 | |||
e77b9176f3 | |||
afe8b966ad | |||
310724c961 | |||
ceba8ae620 | |||
fead661ab7 | |||
9c8c17f577 | |||
a04f83930f | |||
c421d19800 | |||
521f02c90e | |||
c86b6074f2 | |||
d5d1a2c5f4 | |||
2a90e3f478 | |||
1d49ba41ea | |||
e1de1f6682 | |||
47ed5e4d4d | |||
d77f6f5659 | |||
05f0045d5d | |||
c4afb84f6e | |||
b0b2f9b6d2 | |||
ee37feb489 | |||
6b8337596f | |||
250665b92e | |||
86b83c889e | |||
c9c657c07b | |||
4f2babaf54 | |||
1b03bf4555 | |||
977b995af9 | |||
310ab990bd | |||
d6b49b54d4 | |||
f00cf39462 | |||
18550cf1cb | |||
168918f807 | |||
2750df8aa7 | |||
d6597d9f52 | |||
892ea375e3 | |||
03abe77b5f | |||
e46eb8aa49 | |||
2c4862e0f1 | |||
fd69efa45c | |||
e8aa15917f | |||
b3d77f8902 | |||
9c3b0b08ec | |||
fe7d94194c | |||
d6c26d6aa8 | |||
8a74ddcd13 | |||
18e7189a07 | |||
785dd42c84 | |||
0cff5065d9 |
0
.gitmodules
vendored
@ -15,8 +15,11 @@ if (NOT WIN32)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32")
|
||||||
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_SOURCES
|
set( IXWEBSOCKET_SOURCES
|
||||||
ixwebsocket/IXEventFd.cpp
|
|
||||||
ixwebsocket/IXSocket.cpp
|
ixwebsocket/IXSocket.cpp
|
||||||
ixwebsocket/IXSocketServer.cpp
|
ixwebsocket/IXSocketServer.cpp
|
||||||
ixwebsocket/IXSocketConnect.cpp
|
ixwebsocket/IXSocketConnect.cpp
|
||||||
@ -33,10 +36,13 @@ set( IXWEBSOCKET_SOURCES
|
|||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXHttpClient.cpp
|
||||||
ixwebsocket/IXUrlParser.cpp
|
ixwebsocket/IXUrlParser.cpp
|
||||||
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
|
ixwebsocket/IXSelectInterruptPipe.cpp
|
||||||
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
|
ixwebsocket/IXConnectionState.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
ixwebsocket/IXEventFd.h
|
|
||||||
ixwebsocket/IXSocket.h
|
ixwebsocket/IXSocket.h
|
||||||
ixwebsocket/IXSocketServer.h
|
ixwebsocket/IXSocketServer.h
|
||||||
ixwebsocket/IXSocketConnect.h
|
ixwebsocket/IXSocketConnect.h
|
||||||
@ -58,6 +64,10 @@ set( IXWEBSOCKET_HEADERS
|
|||||||
ixwebsocket/libwshandshake.hpp
|
ixwebsocket/libwshandshake.hpp
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXHttpClient.h
|
||||||
ixwebsocket/IXUrlParser.h
|
ixwebsocket/IXUrlParser.h
|
||||||
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
|
ixwebsocket/IXSelectInterruptPipe.h
|
||||||
|
ixwebsocket/IXSelectInterruptFactory.h
|
||||||
|
ixwebsocket/IXConnectionState.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Platform specific code
|
# Platform specific code
|
||||||
@ -67,6 +77,8 @@ elseif (WIN32)
|
|||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
|
||||||
else()
|
else()
|
||||||
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
|
||||||
|
list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
|
||||||
|
list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_TLS)
|
if (USE_TLS)
|
||||||
@ -123,3 +135,4 @@ set( IXWEBSOCKET_INCLUDE_DIRS
|
|||||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
|
||||||
|
|
||||||
add_subdirectory(ws)
|
add_subdirectory(ws)
|
||||||
|
add_subdirectory(third_party/cpp_redis)
|
||||||
|
1
DOCKER_VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
1.3.2
|
@ -1 +1 @@
|
|||||||
docker/Dockerfile.debian
|
Dockerfile.dev
|
@ -12,11 +12,20 @@ RUN apt-get -y install libz-dev
|
|||||||
RUN apt-get -y install vim
|
RUN apt-get -y install vim
|
||||||
RUN apt-get -y install make
|
RUN apt-get -y install make
|
||||||
RUN apt-get -y install cmake
|
RUN apt-get -y install cmake
|
||||||
|
RUN apt-get -y install curl
|
||||||
|
RUN apt-get -y install python
|
||||||
|
RUN apt-get -y install netcat
|
||||||
|
|
||||||
|
# debian strech cmake is too old for building with Docker
|
||||||
|
COPY makefile .
|
||||||
|
RUN ["make", "install_cmake_for_linux"]
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
WORKDIR ws
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-rc4-Linux-x86_64/bin
|
||||||
RUN ["sh", "docker_build.sh"]
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
# RUN ["make"]
|
||||||
|
|
||||||
EXPOSE 8765
|
EXPOSE 8765
|
||||||
CMD ["/ws/ws", "transfer", "8765"]
|
CMD ["/ws/ws", "transfer", "--port", "8765", "--host", "0.0.0.0"]
|
30
Dockerfile.prod
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM debian:buster
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update
|
||||||
|
|
||||||
|
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 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 adduser app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
|
||||||
|
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
|
||||||
|
|
||||||
|
RUN ["make"]
|
||||||
|
|
||||||
|
# Now run in usermode
|
||||||
|
USER app
|
||||||
|
|
||||||
|
EXPOSE 8765
|
||||||
|
CMD ["bash"]
|
46
README.md
@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
|
[*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.
|
||||||
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
|
* macOS
|
||||||
* iOS
|
* iOS
|
||||||
* Linux
|
* Linux
|
||||||
* Android
|
* Android
|
||||||
* Windows (no TLS support yet)
|
|
||||||
|
The code was made to compile once on Windows but support is currently broken on this platform.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@ -47,9 +47,12 @@ webSocket.setOnMessageCallback(
|
|||||||
// Now that our callback is setup, we can start our background thread and receive messages
|
// Now that our callback is setup, we can start our background thread and receive messages
|
||||||
webSocket.start();
|
webSocket.start();
|
||||||
|
|
||||||
// Send a message to the server
|
// Send a message to the server (default to BINARY mode)
|
||||||
webSocket.send("hello world");
|
webSocket.send("hello world");
|
||||||
|
|
||||||
|
// The message can be sent in TEXT mode
|
||||||
|
webSocket.sendText("hello again");
|
||||||
|
|
||||||
// ... finally ...
|
// ... finally ...
|
||||||
|
|
||||||
// Stop the connection
|
// Stop the connection
|
||||||
@ -64,10 +67,11 @@ Here is what the server API looks like. Note that server support is very recent
|
|||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -77,7 +81,16 @@ server.setOnConnectionCallback(
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
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 << "Uri: " << openInfo.uri << std::endl;
|
||||||
|
|
||||||
std::cerr << "Headers:" << std::endl;
|
std::cerr << "Headers:" << std::endl;
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
{
|
{
|
||||||
@ -122,7 +135,7 @@ HttpRequestArgs args;
|
|||||||
// Custom headers can be set
|
// Custom headers can be set
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
headers["Foo"] = "bar";
|
headers["Foo"] = "bar";
|
||||||
args.extraHeaders = parseHeaders(headersData);
|
args.extraHeaders = headers;
|
||||||
|
|
||||||
// Timeout options
|
// Timeout options
|
||||||
args.connectTimeout = connectTimeout;
|
args.connectTimeout = connectTimeout;
|
||||||
@ -178,6 +191,13 @@ CMakefiles for the library and the examples are available. This library has few
|
|||||||
|
|
||||||
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
|
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
|
||||||
|
|
||||||
|
You can build and install the ws command line tool with Homebrew.
|
||||||
|
|
||||||
|
```
|
||||||
|
brew tap bsergean/IXWebSocket
|
||||||
|
brew install IXWebSocket
|
||||||
|
```
|
||||||
|
|
||||||
## Implementation details
|
## Implementation details
|
||||||
|
|
||||||
### Per Message Deflate compression.
|
### Per Message Deflate compression.
|
||||||
@ -198,11 +218,11 @@ If the remote end (server) breaks the connection, the code will try to perpetual
|
|||||||
|
|
||||||
### Large messages
|
### 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 500M were sent and received succesfully.
|
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
|
## Limitations
|
||||||
|
|
||||||
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
|
* 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.
|
* 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.
|
* 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.
|
||||||
|
|
||||||
@ -214,13 +234,13 @@ Here is a simplistic diagram which explains how the code is structured in term o
|
|||||||
+-----------------------+ --- Public
|
+-----------------------+ --- Public
|
||||||
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
| | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
|
||||||
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
| IXWebSocket | Interface used by C++ test clients. No IX dependencies.
|
||||||
| |
|
| |
|
||||||
+-----------------------+
|
+-----------------------+
|
||||||
| |
|
| |
|
||||||
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
| IXWebSocketServer | Run a server and give each connections its own WebSocket object.
|
||||||
| | Each connection is handled in a new OS thread.
|
| | Each connection is handled in a new OS thread.
|
||||||
| |
|
| |
|
||||||
+-----------------------+ --- Private
|
+-----------------------+ --- Private
|
||||||
| |
|
| |
|
||||||
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
| IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
|
||||||
| |
|
| |
|
||||||
@ -370,7 +390,7 @@ websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes l
|
|||||||
### Heartbeat.
|
### Heartbeat.
|
||||||
|
|
||||||
You can configure an optional heart beat / keep-alive, sent every 45 seconds
|
You can configure an optional heart beat / keep-alive, sent every 45 seconds
|
||||||
when there is not any traffic to make sure that load balancers do not kill an
|
when there is no any traffic to make sure that load balancers do not kill an
|
||||||
idle connection.
|
idle connection.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
BIN
doc/redis_conf_2019/brocoli.jpg
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
doc/redis_conf_2019/grafana_critical_logs.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
doc/redis_conf_2019/grafana_zlib.png
Normal file
After Width: | Height: | Size: 80 KiB |
2
doc/redis_conf_2019/makefile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
all:
|
||||||
|
(cd .. ; make docker && make docker_push)
|
BIN
doc/redis_conf_2019/mz_engine.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
doc/redis_conf_2019/neo.png
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
doc/redis_conf_2019/neo_map.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
doc/redis_conf_2019/neo_session.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
doc/redis_conf_2019/redisconf_10_years.png
Normal file
After Width: | Height: | Size: 673 KiB |
BIN
doc/redis_conf_2019/redisconf_first_slide.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
doc/redis_conf_2019/redisconf_last_slide.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
18
doc/redis_conf_2019/remark-latest.min.js
vendored
Normal file
BIN
doc/redis_conf_2019/sentry.png
Normal file
After Width: | Height: | Size: 90 KiB |
1164
doc/redis_conf_2019/slides.html
Normal file
BIN
doc/redis_conf_2019/tableau.png
Normal file
After Width: | Height: | Size: 36 KiB |
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
ws:
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
image: bsergean/ws:build
|
||||||
|
ports:
|
||||||
|
- "8765:8765"
|
||||||
|
entrypoint: bash
|
||||||
|
networks:
|
||||||
|
- ws-net
|
||||||
|
depends_on:
|
||||||
|
- redis1
|
||||||
|
|
||||||
|
redis1:
|
||||||
|
image: redis:alpine
|
||||||
|
networks:
|
||||||
|
- ws-net
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ws-net:
|
@ -1,16 +0,0 @@
|
|||||||
FROM debian:stretch
|
|
||||||
|
|
||||||
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get -y install g++
|
|
||||||
RUN apt-get -y install libssl-dev
|
|
||||||
RUN apt-get -y install gdb
|
|
||||||
RUN apt-get -y install screen
|
|
||||||
RUN apt-get -y install procps
|
|
||||||
RUN apt-get -y install lsof
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
WORKDIR examples/ws_connect
|
|
||||||
RUN ["sh", "build_linux.sh"]
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM alpine:3.8
|
|
||||||
|
|
||||||
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
WORKDIR examples/ws_connect
|
|
||||||
RUN ["sh", "build_linux.sh"]
|
|
||||||
|
|
||||||
EXPOSE 8765
|
|
||||||
CMD ["ws_connect"]
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM alpine:3.8
|
|
||||||
|
|
||||||
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
WORKDIR examples/ws_connect
|
|
||||||
RUN ["sh", "build_linux.sh"]
|
|
||||||
|
|
||||||
EXPOSE 8765
|
|
||||||
CMD ["ws_connect"]
|
|
@ -1,8 +0,0 @@
|
|||||||
FROM gcc:8
|
|
||||||
|
|
||||||
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
WORKDIR examples/ws_connect
|
|
||||||
RUN ["sh", "build_linux.sh"]
|
|
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXHash.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
uint64_t djb2Hash(const std::string& data)
|
|
||||||
{
|
|
||||||
uint64_t hashAddress = 5381;
|
|
||||||
|
|
||||||
for (auto& c : data)
|
|
||||||
{
|
|
||||||
hashAddress = ((hashAddress << 5) + hashAddress) + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashAddress;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXHash.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
uint64_t djb2Hash(const std::string& data);
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXUuid.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random uuid similar to the uuid python module
|
|
||||||
*
|
|
||||||
* >>> import uuid
|
|
||||||
* >>> uuid.uuid4().hex
|
|
||||||
* 'bec08155b37d4050a1f3c3fa0276bf12'
|
|
||||||
*
|
|
||||||
* Code adapted from https://github.com/r-lyeh-archived/sole
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "IXUuid.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class Uuid
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Uuid();
|
|
||||||
std::string toString() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint64_t _ab;
|
|
||||||
uint64_t _cd;
|
|
||||||
};
|
|
||||||
|
|
||||||
Uuid::Uuid()
|
|
||||||
{
|
|
||||||
static std::random_device rd;
|
|
||||||
static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
|
|
||||||
|
|
||||||
_ab = dist(rd);
|
|
||||||
_cd = dist(rd);
|
|
||||||
|
|
||||||
_ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
|
|
||||||
_cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Uuid::toString() const
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::hex << std::nouppercase << std::setfill('0');
|
|
||||||
|
|
||||||
uint32_t a = (_ab >> 32);
|
|
||||||
uint32_t b = (_ab & 0xFFFFFFFF);
|
|
||||||
uint32_t c = (_cd >> 32);
|
|
||||||
uint32_t d = (_cd & 0xFFFFFFFF);
|
|
||||||
|
|
||||||
ss << std::setw(8) << (a);
|
|
||||||
ss << std::setw(4) << (b >> 16);
|
|
||||||
ss << std::setw(4) << (b & 0xFFFF);
|
|
||||||
ss << std::setw(4) << (c >> 16 );
|
|
||||||
ss << std::setw(4) << (c & 0xFFFF);
|
|
||||||
ss << std::setw(8) << d;
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string uuid4()
|
|
||||||
{
|
|
||||||
Uuid id;
|
|
||||||
return id.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXUuid.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017 Machine Zone. All rights reserved.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Generate a random uuid
|
|
||||||
*/
|
|
||||||
std::string uuid4();
|
|
||||||
|
|
||||||
}
|
|
1
examples/ws_receive/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
build
|
|
@ -1,30 +0,0 @@
|
|||||||
#
|
|
||||||
# Author: Benjamin Sergeant
|
|
||||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
cmake_minimum_required (VERSION 3.4.1)
|
|
||||||
project (ws_receive)
|
|
||||||
|
|
||||||
# There's -Weverything too for clang
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
option(USE_TLS "Add TLS support" ON)
|
|
||||||
|
|
||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
|
|
||||||
|
|
||||||
include_directories(ws_receive .)
|
|
||||||
|
|
||||||
add_executable(ws_receive
|
|
||||||
jsoncpp/jsoncpp.cpp
|
|
||||||
ixcrypto/IXBase64.cpp
|
|
||||||
ixcrypto/IXHash.cpp
|
|
||||||
ws_receive.cpp)
|
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
|
||||||
target_link_libraries(ws_receive "-framework foundation" "-framework security")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(ws_receive ixwebsocket)
|
|
@ -1 +0,0 @@
|
|||||||
ws_receive is a simple server upload program. It needs to be used in conjonction with ws_send.
|
|
@ -1 +0,0 @@
|
|||||||
../cobra_publisher/ixcrypto
|
|
@ -1,333 +0,0 @@
|
|||||||
/// Json-cpp amalgated forward header (http://jsoncpp.sourceforge.net/).
|
|
||||||
/// It is intended to be used with #include "json/json-forwards.h"
|
|
||||||
/// This header provides forward declaration for all JsonCpp types.
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: LICENSE
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/*
|
|
||||||
The JsonCpp library's source code, including accompanying documentation,
|
|
||||||
tests and demonstration applications, are licensed under the following
|
|
||||||
conditions...
|
|
||||||
|
|
||||||
Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all
|
|
||||||
jurisdictions which recognize such a disclaimer. In such jurisdictions,
|
|
||||||
this software is released into the Public Domain.
|
|
||||||
|
|
||||||
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
|
|
||||||
2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and
|
|
||||||
The JsonCpp Authors, and is released under the terms of the MIT License (see below).
|
|
||||||
|
|
||||||
In jurisdictions which recognize Public Domain property, the user of this
|
|
||||||
software may choose to accept it either as 1) Public Domain, 2) under the
|
|
||||||
conditions of the MIT License (see below), or 3) under the terms of dual
|
|
||||||
Public Domain/MIT License conditions described here, as they choose.
|
|
||||||
|
|
||||||
The MIT License is about as close to Public Domain as a license can get, and is
|
|
||||||
described in clear, concise terms at:
|
|
||||||
|
|
||||||
http://en.wikipedia.org/wiki/MIT_License
|
|
||||||
|
|
||||||
The full text of the MIT License follows:
|
|
||||||
|
|
||||||
========================================================================
|
|
||||||
Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
|
|
||||||
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.
|
|
||||||
========================================================================
|
|
||||||
(END LICENSE TEXT)
|
|
||||||
|
|
||||||
The MIT license is compatible with both the GPL and commercial
|
|
||||||
software, affording one all of the rights of Public Domain with the
|
|
||||||
minor nuisance of being required to keep the above copyright notice
|
|
||||||
and license text in the source code. Note also that by accepting the
|
|
||||||
Public Domain "license" you can re-license your copy using whatever
|
|
||||||
license you like.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: LICENSE
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
||||||
# define JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
||||||
/// If defined, indicates that the source file is amalgated
|
|
||||||
/// to prevent private header inclusion.
|
|
||||||
#define JSON_IS_AMALGAMATION
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: include/json/config.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
// Distributed under MIT license, or public domain if desired and
|
|
||||||
// recognized in your jurisdiction.
|
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
|
||||||
|
|
||||||
#ifndef JSON_CONFIG_H_INCLUDED
|
|
||||||
#define JSON_CONFIG_H_INCLUDED
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string> //typedef String
|
|
||||||
#include <stdint.h> //typedef int64_t, uint64_t
|
|
||||||
|
|
||||||
/// If defined, indicates that json library is embedded in CppTL library.
|
|
||||||
//# define JSON_IN_CPPTL 1
|
|
||||||
|
|
||||||
/// If defined, indicates that json may leverage CppTL library
|
|
||||||
//# define JSON_USE_CPPTL 1
|
|
||||||
/// If defined, indicates that cpptl vector based map should be used instead of
|
|
||||||
/// std::map
|
|
||||||
/// as Value container.
|
|
||||||
//# define JSON_USE_CPPTL_SMALLMAP 1
|
|
||||||
|
|
||||||
// If non-zero, the library uses exceptions to report bad input instead of C
|
|
||||||
// assertion macros. The default is to use exceptions.
|
|
||||||
#ifndef JSON_USE_EXCEPTION
|
|
||||||
#define JSON_USE_EXCEPTION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// If defined, indicates that the source file is amalgated
|
|
||||||
/// to prevent private header inclusion.
|
|
||||||
/// Remarks: it is automatically defined in the generated amalgated header.
|
|
||||||
// #define JSON_IS_AMALGAMATION
|
|
||||||
|
|
||||||
#ifdef JSON_IN_CPPTL
|
|
||||||
#include <cpptl/config.h>
|
|
||||||
#ifndef JSON_USE_CPPTL
|
|
||||||
#define JSON_USE_CPPTL 1
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_IN_CPPTL
|
|
||||||
#define JSON_API CPPTL_API
|
|
||||||
#elif defined(JSON_DLL_BUILD)
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
#define JSON_API __declspec(dllexport)
|
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
#elif defined(JSON_DLL)
|
|
||||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
|
||||||
#define JSON_API __declspec(dllimport)
|
|
||||||
#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
#endif // ifdef JSON_IN_CPPTL
|
|
||||||
#if !defined(JSON_API)
|
|
||||||
#define JSON_API
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for
|
|
||||||
// integer
|
|
||||||
// Storages, and 64 bits integer support is disabled.
|
|
||||||
// #define JSON_NO_INT64 1
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) // MSVC
|
|
||||||
# if _MSC_VER <= 1200 // MSVC 6
|
|
||||||
// Microsoft Visual Studio 6 only support conversion from __int64 to double
|
|
||||||
// (no conversion from unsigned __int64).
|
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
|
||||||
// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255'
|
|
||||||
// characters in the debug information)
|
|
||||||
// All projects I've ever seen with VS6 were using this globally (not bothering
|
|
||||||
// with pragma push/pop).
|
|
||||||
# pragma warning(disable : 4786)
|
|
||||||
# endif // MSVC 6
|
|
||||||
|
|
||||||
# if _MSC_VER >= 1500 // MSVC 2008
|
|
||||||
/// Indicates that the following function is deprecated.
|
|
||||||
# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message))
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif // defined(_MSC_VER)
|
|
||||||
|
|
||||||
// In c++11 the override keyword allows you to explicity define that a function
|
|
||||||
// is intended to override the base-class version. This makes the code more
|
|
||||||
// managable and fixes a set of common hard-to-find bugs.
|
|
||||||
#if __cplusplus >= 201103L
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER > 1600 && _MSC_VER < 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#elif defined(_MSC_VER) && _MSC_VER >= 1900
|
|
||||||
# define JSONCPP_OVERRIDE override
|
|
||||||
# define JSONCPP_NOEXCEPT noexcept
|
|
||||||
#else
|
|
||||||
# define JSONCPP_OVERRIDE
|
|
||||||
# define JSONCPP_NOEXCEPT throw()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && _MSC_VER >= 1600 // MSVC >= 2010
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // MSVC >= 2010
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
#if __has_feature(cxx_rvalue_references)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // has_feature
|
|
||||||
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L)
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 1
|
|
||||||
#endif // GXX_EXPERIMENTAL
|
|
||||||
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#endif // not defined JSON_HAS_RVALUE_REFERENCES
|
|
||||||
|
|
||||||
#ifndef JSON_HAS_RVALUE_REFERENCES
|
|
||||||
#define JSON_HAS_RVALUE_REFERENCES 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __clang__
|
|
||||||
# if __has_extension(attribute_deprecated_with_message)
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# endif
|
|
||||||
#elif defined __GNUC__ // not clang (gcc comes later since clang emulates gcc)
|
|
||||||
# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message)))
|
|
||||||
# elif (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))
|
|
||||||
# define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__))
|
|
||||||
# endif // GNUC version
|
|
||||||
#endif // __clang__ || __GNUC__
|
|
||||||
|
|
||||||
#if !defined(JSONCPP_DEPRECATED)
|
|
||||||
#define JSONCPP_DEPRECATED(message)
|
|
||||||
#endif // if !defined(JSONCPP_DEPRECATED)
|
|
||||||
|
|
||||||
#if __GNUC__ >= 6
|
|
||||||
# define JSON_USE_INT64_DOUBLE_CONVERSION 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
# include "version.h"
|
|
||||||
|
|
||||||
# if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
# include "allocator.h" //typedef Allocator
|
|
||||||
# endif
|
|
||||||
|
|
||||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
namespace Json {
|
|
||||||
typedef int Int;
|
|
||||||
typedef unsigned int UInt;
|
|
||||||
#if defined(JSON_NO_INT64)
|
|
||||||
typedef int LargestInt;
|
|
||||||
typedef unsigned int LargestUInt;
|
|
||||||
#undef JSON_HAS_INT64
|
|
||||||
#else // if defined(JSON_NO_INT64)
|
|
||||||
// For Microsoft Visual use specific types as long long is not supported
|
|
||||||
#if defined(_MSC_VER) // Microsoft Visual Studio
|
|
||||||
typedef __int64 Int64;
|
|
||||||
typedef unsigned __int64 UInt64;
|
|
||||||
#else // if defined(_MSC_VER) // Other platforms, use long long
|
|
||||||
typedef int64_t Int64;
|
|
||||||
typedef uint64_t UInt64;
|
|
||||||
#endif // if defined(_MSC_VER)
|
|
||||||
typedef Int64 LargestInt;
|
|
||||||
typedef UInt64 LargestUInt;
|
|
||||||
#define JSON_HAS_INT64
|
|
||||||
#endif // if defined(JSON_NO_INT64)
|
|
||||||
#if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
#define JSONCPP_STRING std::basic_string<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::basic_ostringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_OSTREAM std::basic_ostream<char, std::char_traits<char>>
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::basic_istringstream<char, std::char_traits<char>, Json::SecureAllocator<char> >
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#else
|
|
||||||
#define JSONCPP_STRING std::string
|
|
||||||
#define JSONCPP_OSTRINGSTREAM std::ostringstream
|
|
||||||
#define JSONCPP_OSTREAM std::ostream
|
|
||||||
#define JSONCPP_ISTRINGSTREAM std::istringstream
|
|
||||||
#define JSONCPP_ISTREAM std::istream
|
|
||||||
#endif // if JSONCPP_USING_SECURE_MEMORY
|
|
||||||
} // end namespace Json
|
|
||||||
|
|
||||||
#endif // JSON_CONFIG_H_INCLUDED
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: include/json/config.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// Beginning of content of file: include/json/forwards.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors
|
|
||||||
// Distributed under MIT license, or public domain if desired and
|
|
||||||
// recognized in your jurisdiction.
|
|
||||||
// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE
|
|
||||||
|
|
||||||
#ifndef JSON_FORWARDS_H_INCLUDED
|
|
||||||
#define JSON_FORWARDS_H_INCLUDED
|
|
||||||
|
|
||||||
#if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
#include "config.h"
|
|
||||||
#endif // if !defined(JSON_IS_AMALGAMATION)
|
|
||||||
|
|
||||||
namespace Json {
|
|
||||||
|
|
||||||
// writer.h
|
|
||||||
class FastWriter;
|
|
||||||
class StyledWriter;
|
|
||||||
|
|
||||||
// reader.h
|
|
||||||
class Reader;
|
|
||||||
|
|
||||||
// features.h
|
|
||||||
class Features;
|
|
||||||
|
|
||||||
// value.h
|
|
||||||
typedef unsigned int ArrayIndex;
|
|
||||||
class StaticString;
|
|
||||||
class Path;
|
|
||||||
class PathArgument;
|
|
||||||
class Value;
|
|
||||||
class ValueIteratorBase;
|
|
||||||
class ValueIterator;
|
|
||||||
class ValueConstIterator;
|
|
||||||
|
|
||||||
} // namespace Json
|
|
||||||
|
|
||||||
#endif // JSON_FORWARDS_H_INCLUDED
|
|
||||||
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
// End of content of file: include/json/forwards.h
|
|
||||||
// //////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //ifndef JSON_FORWARD_AMALGATED_H_INCLUDED
|
|
29
examples/ws_receive/package-lock.json
generated
@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"requires": true,
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"dependencies": {
|
|
||||||
"async-limiter": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
|
||||||
},
|
|
||||||
"base-64": {
|
|
||||||
"version": "0.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
|
||||||
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
|
||||||
},
|
|
||||||
"djb2": {
|
|
||||||
"version": "0.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/djb2/-/djb2-0.0.2.tgz",
|
|
||||||
"integrity": "sha1-RAs4kao6uBQrVNRpsXe66p6W5O8="
|
|
||||||
},
|
|
||||||
"ws": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
|
||||||
"requires": {
|
|
||||||
"async-limiter": "1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* ws_receive.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
|
||||||
#include <jsoncpp/json/json.h>
|
|
||||||
#include <ixcrypto/IXBase64.h>
|
|
||||||
#include <ixcrypto/IXHash.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// We should cleanup the file name and full path further to remove .. as well
|
|
||||||
std::string extractFilename(const std::string& path)
|
|
||||||
{
|
|
||||||
std::string filename("filename.conf");
|
|
||||||
std::string::size_type idx;
|
|
||||||
|
|
||||||
idx = path.rfind('/');
|
|
||||||
if (idx != std::string::npos)
|
|
||||||
{
|
|
||||||
std::string filename = path.substr(idx+1);
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
void errorHandler(const std::string& errMsg,
|
|
||||||
const std::string& id,
|
|
||||||
std::shared_ptr<ix::WebSocket> webSocket)
|
|
||||||
{
|
|
||||||
Json::Value pdu;
|
|
||||||
pdu["kind"] = "error";
|
|
||||||
pdu["id"] = id;
|
|
||||||
pdu["message"] = errMsg;
|
|
||||||
webSocket->send(pdu.toStyledString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void messageHandler(const std::string& str,
|
|
||||||
std::shared_ptr<ix::WebSocket> webSocket)
|
|
||||||
{
|
|
||||||
std::cerr << "Received message: " << str.size() << std::endl;
|
|
||||||
|
|
||||||
Json::Value data;
|
|
||||||
Json::Reader reader;
|
|
||||||
if (!reader.parse(str, data))
|
|
||||||
{
|
|
||||||
errorHandler("Invalid JSON", std::string(), webSocket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "id: " << data["id"].asString() << std::endl;
|
|
||||||
|
|
||||||
std::string content = ix::base64_decode(data["content"].asString());
|
|
||||||
std::cout << "Content size: " << content.size() << std::endl;
|
|
||||||
|
|
||||||
// Validate checksum
|
|
||||||
uint64_t cksum = ix::djb2Hash(data["content"].asString());
|
|
||||||
uint64_t cksumRef = data["djb2_hash"].asUInt64();
|
|
||||||
|
|
||||||
std::cout << "Computed hash: " << cksum << std::endl;
|
|
||||||
std::cout << "Reference hash: " << cksumRef << std::endl;
|
|
||||||
|
|
||||||
if (cksum != cksumRef)
|
|
||||||
{
|
|
||||||
errorHandler("Hash mismatch.", std::string(), webSocket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filename = data["filename"].asString();
|
|
||||||
filename = extractFilename(filename);
|
|
||||||
|
|
||||||
std::ofstream out(filename);
|
|
||||||
out << content;
|
|
||||||
out.close();
|
|
||||||
|
|
||||||
Json::Value pdu;
|
|
||||||
pdu["ack"] = true;
|
|
||||||
pdu["id"] = data["id"];
|
|
||||||
pdu["filename"] = data["filename"];
|
|
||||||
webSocket->send(pdu.toStyledString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
int port = 8080;
|
|
||||||
if (argc == 2)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << argv[1];
|
|
||||||
ss >> port;
|
|
||||||
}
|
|
||||||
|
|
||||||
ix::WebSocketServer server(port);
|
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
|
||||||
{
|
|
||||||
webSocket->setOnMessageCallback(
|
|
||||||
[webSocket, &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::WebSocket_MessageType_Open)
|
|
||||||
{
|
|
||||||
std::cerr << "New connection" << std::endl;
|
|
||||||
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::WebSocket_MessageType_Close)
|
|
||||||
{
|
|
||||||
std::cerr << "Closed connection" << std::endl;
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
|
||||||
{
|
|
||||||
messageHandler(str, webSocket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
auto res = server.listen();
|
|
||||||
if (!res.first)
|
|
||||||
{
|
|
||||||
std::cerr << res.second << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
server.wait();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* ws_receive.js
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
const WebSocket = require('ws')
|
|
||||||
const djb2 = require('djb2')
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const wss = new WebSocket.Server({ port: 8080,
|
|
||||||
perMessageDeflate: false,
|
|
||||||
maxPayload: 1024 * 1024 * 1024 * 1024});
|
|
||||||
|
|
||||||
wss.on('connection', function connection(ws) {
|
|
||||||
ws.on('message', function incoming(data) {
|
|
||||||
console.log('Received message')
|
|
||||||
|
|
||||||
let str = data.toString()
|
|
||||||
let obj = JSON.parse(str)
|
|
||||||
|
|
||||||
console.log(obj.id)
|
|
||||||
console.log(obj.djb2_hash)
|
|
||||||
console.log(djb2(obj.content))
|
|
||||||
|
|
||||||
var content = Buffer.from(obj.content, 'base64')
|
|
||||||
// let bytes = base64.decode(obj.content)
|
|
||||||
|
|
||||||
let path = obj.filename
|
|
||||||
fs.writeFile(path, content, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err
|
|
||||||
} else {
|
|
||||||
console.log('wrote data to disk')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let response = {
|
|
||||||
id: obj.id
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.send(JSON.stringify(response))
|
|
||||||
});
|
|
||||||
});
|
|
1
examples/ws_send/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
build
|
|
@ -1,31 +0,0 @@
|
|||||||
#
|
|
||||||
# Author: Benjamin Sergeant
|
|
||||||
# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
||||||
#
|
|
||||||
|
|
||||||
cmake_minimum_required (VERSION 3.4.1)
|
|
||||||
project (ws_send)
|
|
||||||
|
|
||||||
# There's -Weverything too for clang
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
|
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
|
||||||
|
|
||||||
option(USE_TLS "Add TLS support" ON)
|
|
||||||
|
|
||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
|
|
||||||
|
|
||||||
include_directories(ws_send .)
|
|
||||||
|
|
||||||
add_executable(ws_send
|
|
||||||
jsoncpp/jsoncpp.cpp
|
|
||||||
ixcrypto/IXBase64.cpp
|
|
||||||
ixcrypto/IXUuid.cpp
|
|
||||||
ixcrypto/IXHash.cpp
|
|
||||||
ws_send.cpp)
|
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
|
||||||
target_link_libraries(ws_send "-framework foundation" "-framework security")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(ws_send ixwebsocket)
|
|
@ -1 +0,0 @@
|
|||||||
ws_send is a simple upload program. It needs to be used in conjonction with ws_receive.
|
|
@ -1 +0,0 @@
|
|||||||
../cobra_publisher/ixcrypto
|
|
@ -1 +0,0 @@
|
|||||||
../cobra_publisher/jsoncpp
|
|
@ -1,306 +0,0 @@
|
|||||||
/*
|
|
||||||
* ws_send.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <vector>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <mutex>
|
|
||||||
#include <chrono>
|
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
|
||||||
#include <ixwebsocket/IXSocket.h>
|
|
||||||
#include <ixcrypto/IXUuid.h>
|
|
||||||
#include <ixcrypto/IXBase64.h>
|
|
||||||
#include <ixcrypto/IXHash.h>
|
|
||||||
#include <jsoncpp/json/json.h>
|
|
||||||
|
|
||||||
using namespace ix;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
void log(const std::string& msg)
|
|
||||||
{
|
|
||||||
std::cout << msg << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
class WebSocketSender
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebSocketSender(const std::string& _url,
|
|
||||||
bool enablePerMessageDeflate);
|
|
||||||
|
|
||||||
void subscribe(const std::string& channel);
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
void waitForConnection();
|
|
||||||
void waitForAck();
|
|
||||||
|
|
||||||
void sendMessage(const std::string& filename, bool throttle);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string _url;
|
|
||||||
std::string _id;
|
|
||||||
ix::WebSocket _webSocket;
|
|
||||||
bool _enablePerMessageDeflate;
|
|
||||||
|
|
||||||
std::mutex _conditionVariableMutex;
|
|
||||||
std::condition_variable _condition;
|
|
||||||
};
|
|
||||||
|
|
||||||
WebSocketSender::WebSocketSender(const std::string& url,
|
|
||||||
bool enablePerMessageDeflate) :
|
|
||||||
_url(url),
|
|
||||||
_enablePerMessageDeflate(enablePerMessageDeflate)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketSender::stop()
|
|
||||||
{
|
|
||||||
_webSocket.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketSender::waitForConnection()
|
|
||||||
{
|
|
||||||
std::cout << "Connecting..." << std::endl;
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
||||||
_condition.wait(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketSender::waitForAck()
|
|
||||||
{
|
|
||||||
std::cout << "Waiting for ack..." << std::endl;
|
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
|
||||||
_condition.wait(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string load(const std::string& path)
|
|
||||||
{
|
|
||||||
// std::vector<uint8_t> memblock;
|
|
||||||
std::string str;
|
|
||||||
|
|
||||||
std::ifstream file(path);
|
|
||||||
if (!file.is_open()) return std::string();
|
|
||||||
|
|
||||||
file.seekg(0, file.end);
|
|
||||||
std::streamoff size = file.tellg();
|
|
||||||
file.seekg(0, file.beg);
|
|
||||||
|
|
||||||
str.resize(size);
|
|
||||||
file.read((char*)&str.front(), static_cast<std::streamsize>(size));
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketSender::start()
|
|
||||||
{
|
|
||||||
_webSocket.setUrl(_url);
|
|
||||||
|
|
||||||
ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
|
|
||||||
_enablePerMessageDeflate, false, false, 15, 15);
|
|
||||||
_webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
log(std::string("Connecting to url: ") + _url);
|
|
||||||
|
|
||||||
_webSocket.setOnMessageCallback(
|
|
||||||
[this](ix::WebSocketMessageType messageType,
|
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
|
||||||
{
|
|
||||||
_condition.notify_one();
|
|
||||||
|
|
||||||
log("ws_send: connected");
|
|
||||||
std::cout << "Uri: " << openInfo.uri << std::endl;
|
|
||||||
std::cout << "Handshake Headers:" << std::endl;
|
|
||||||
for (auto it : openInfo.headers)
|
|
||||||
{
|
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
|
||||||
{
|
|
||||||
ss << "ws_send: connection closed:";
|
|
||||||
ss << " code " << closeInfo.code;
|
|
||||||
ss << " reason " << closeInfo.reason << std::endl;
|
|
||||||
log(ss.str());
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocket_MessageType_Message)
|
|
||||||
{
|
|
||||||
_condition.notify_one();
|
|
||||||
|
|
||||||
ss << "ws_send: received message: "
|
|
||||||
<< str;
|
|
||||||
log(ss.str());
|
|
||||||
|
|
||||||
Json::Value data;
|
|
||||||
Json::Reader reader;
|
|
||||||
if (!reader.parse(str, data))
|
|
||||||
{
|
|
||||||
std::cerr << "Invalid JSON response" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string id = data["id"].asString();
|
|
||||||
if (_id != id)
|
|
||||||
{
|
|
||||||
std::cerr << "Invalid id" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (messageType == ix::WebSocket_MessageType_Error)
|
|
||||||
{
|
|
||||||
ss << "Connection error: " << error.reason << std::endl;
|
|
||||||
ss << "#retries: " << error.retries << std::endl;
|
|
||||||
ss << "Wait time(ms): " << error.wait_time << std::endl;
|
|
||||||
ss << "HTTP Status: " << error.http_status << std::endl;
|
|
||||||
log(ss.str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss << "Invalid ix::WebSocketMessageType";
|
|
||||||
log(ss.str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_webSocket.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
class Bench
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Bench(const std::string& description) :
|
|
||||||
_description(description),
|
|
||||||
_start(std::chrono::system_clock::now()),
|
|
||||||
_reported(false)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Bench()
|
|
||||||
{
|
|
||||||
if (!_reported)
|
|
||||||
{
|
|
||||||
report();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void report()
|
|
||||||
{
|
|
||||||
auto now = std::chrono::system_clock::now();
|
|
||||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
|
|
||||||
|
|
||||||
_ms = milliseconds.count();
|
|
||||||
std::cout << _description << " completed in "
|
|
||||||
<< _ms << "ms" << std::endl;
|
|
||||||
|
|
||||||
_reported = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t getDuration() const
|
|
||||||
{
|
|
||||||
return _ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string _description;
|
|
||||||
std::chrono::time_point<std::chrono::system_clock> _start;
|
|
||||||
uint64_t _ms;
|
|
||||||
bool _reported;
|
|
||||||
};
|
|
||||||
|
|
||||||
void WebSocketSender::sendMessage(const std::string& filename,
|
|
||||||
bool throttle)
|
|
||||||
{
|
|
||||||
std::string content;
|
|
||||||
{
|
|
||||||
Bench bench("load file from disk");
|
|
||||||
content = load(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
_id = uuid4();
|
|
||||||
|
|
||||||
std::string b64Content;
|
|
||||||
{
|
|
||||||
Bench bench("base 64 encode file");
|
|
||||||
b64Content = base64_encode(content, content.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
Json::Value pdu;
|
|
||||||
pdu["kind"] = "send";
|
|
||||||
pdu["id"] = _id;
|
|
||||||
pdu["content"] = b64Content;
|
|
||||||
pdu["djb2_hash"] = djb2Hash(b64Content);
|
|
||||||
pdu["filename"] = filename;
|
|
||||||
|
|
||||||
Bench bench("Sending file through websocket");
|
|
||||||
_webSocket.send(pdu.toStyledString(),
|
|
||||||
[throttle](int current, int total) -> bool
|
|
||||||
{
|
|
||||||
std::cout << "Step " << current << " out of " << total << std::endl;
|
|
||||||
|
|
||||||
if (throttle)
|
|
||||||
{
|
|
||||||
std::chrono::duration<double, std::milli> duration(10);
|
|
||||||
std::this_thread::sleep_for(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
bench.report();
|
|
||||||
auto duration = bench.getDuration();
|
|
||||||
auto transferRate = 1000 * b64Content.size() / duration;
|
|
||||||
transferRate /= (1024 * 1024);
|
|
||||||
std::cout << "Send transfer rate: " << transferRate << "MB/s" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wsSend(const std::string& url,
|
|
||||||
const std::string& path,
|
|
||||||
bool enablePerMessageDeflate,
|
|
||||||
bool throttle)
|
|
||||||
{
|
|
||||||
WebSocketSender webSocketSender(url, enablePerMessageDeflate);
|
|
||||||
webSocketSender.start();
|
|
||||||
|
|
||||||
webSocketSender.waitForConnection();
|
|
||||||
|
|
||||||
std::cout << "Sending..." << std::endl;
|
|
||||||
webSocketSender.sendMessage(path, throttle);
|
|
||||||
|
|
||||||
webSocketSender.waitForAck();
|
|
||||||
|
|
||||||
std::cout << "Done !" << std::endl;
|
|
||||||
webSocketSender.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
|
||||||
{
|
|
||||||
if (argc != 3)
|
|
||||||
{
|
|
||||||
std::cerr << "Usage: ws_send <url> <path>" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
std::string url = argv[1];
|
|
||||||
std::string path = argv[2];
|
|
||||||
|
|
||||||
bool throttle = false;
|
|
||||||
bool enablePerMessageDeflate = false;
|
|
||||||
|
|
||||||
Socket::init();
|
|
||||||
wsSend(url, path, enablePerMessageDeflate, throttle);
|
|
||||||
return 0;
|
|
||||||
}
|
|
33
ixwebsocket/IXConnectionState.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
{
|
||||||
|
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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
33
ixwebsocket/IXConnectionState.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* IXConnectionState.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class ConnectionState {
|
||||||
|
public:
|
||||||
|
ConnectionState();
|
||||||
|
virtual ~ConnectionState() = default;
|
||||||
|
|
||||||
|
virtual void computeId();
|
||||||
|
virtual const std::string& getId() const;
|
||||||
|
|
||||||
|
static std::shared_ptr<ConnectionState> createConnectionState();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string _id;
|
||||||
|
|
||||||
|
static std::atomic<uint64_t> _globalId;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ namespace ix
|
|||||||
errMsg = "no error";
|
errMsg = "no error";
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the background thread terminated ?
|
// Maybe a cancellation request got in before the background thread terminated ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -121,7 +121,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Were we cancelled ?
|
// Were we cancelled ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -129,7 +129,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Maybe a cancellation request got in before the bg terminated ?
|
// Maybe a cancellation request got in before the bg terminated ?
|
||||||
if (isCancellationRequested())
|
if (isCancellationRequested && isCancellationRequested())
|
||||||
{
|
{
|
||||||
errMsg = "cancellation requested";
|
errMsg = "cancellation requested";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXEventFd.cpp
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "IXEventFd.h"
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
# include <sys/eventfd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
#include <unistd.h> // for write
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
EventFd::EventFd() :
|
|
||||||
_eventfd(-1)
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
_eventfd = eventfd(0, 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
EventFd::~EventFd()
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
::close(_eventfd);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventFd::notify()
|
|
||||||
{
|
|
||||||
#if defined(__linux__)
|
|
||||||
if (_eventfd == -1) return false;
|
|
||||||
|
|
||||||
// select will wake up when a non-zero value is written to our eventfd
|
|
||||||
uint64_t value = 1;
|
|
||||||
|
|
||||||
// we should write 8 bytes for an uint64_t
|
|
||||||
return write(_eventfd, &value, sizeof(value)) == 8;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventFd::clear()
|
|
||||||
{
|
|
||||||
#if defined(__linux__)
|
|
||||||
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;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int EventFd::getFd()
|
|
||||||
{
|
|
||||||
return _eventfd;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* IXEventFd.h
|
|
||||||
* Author: Benjamin Sergeant
|
|
||||||
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace ix
|
|
||||||
{
|
|
||||||
class EventFd {
|
|
||||||
public:
|
|
||||||
EventFd();
|
|
||||||
virtual ~EventFd();
|
|
||||||
|
|
||||||
bool notify();
|
|
||||||
bool clear();
|
|
||||||
int getFd();
|
|
||||||
|
|
||||||
private:
|
|
||||||
int _eventfd;
|
|
||||||
};
|
|
||||||
}
|
|
@ -231,19 +231,17 @@ namespace ix
|
|||||||
|
|
||||||
payload.reserve(contentLength);
|
payload.reserve(contentLength);
|
||||||
|
|
||||||
// FIXME: very inefficient way to read bytes, but it works...
|
auto chunkResult = _socket->readBytes(contentLength,
|
||||||
for (int i = 0; i < contentLength; ++i)
|
args.onProgressCallback,
|
||||||
|
isCancellationRequested);
|
||||||
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
char c;
|
errorMsg = "Cannot read chunk";
|
||||||
if (!_socket->readByte(&c, isCancellationRequested))
|
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||||
{
|
headers, payload, errorMsg,
|
||||||
return std::make_tuple(code, HttpErrorCode_ReadError,
|
uploadSize, downloadSize);
|
||||||
headers, payload, "Cannot read byte",
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
payload += c;
|
|
||||||
}
|
}
|
||||||
|
payload += chunkResult.second;
|
||||||
}
|
}
|
||||||
else if (headers.find("Transfer-Encoding") != headers.end() &&
|
else if (headers.find("Transfer-Encoding") != headers.end() &&
|
||||||
headers["Transfer-Encoding"] == "chunked")
|
headers["Transfer-Encoding"] == "chunked")
|
||||||
@ -277,22 +275,20 @@ namespace ix
|
|||||||
|
|
||||||
payload.reserve(payload.size() + chunkSize);
|
payload.reserve(payload.size() + chunkSize);
|
||||||
|
|
||||||
// Read another line
|
// Read a chunk
|
||||||
|
auto chunkResult = _socket->readBytes(chunkSize,
|
||||||
for (uint64_t i = 0; i < chunkSize; ++i)
|
args.onProgressCallback,
|
||||||
|
isCancellationRequested);
|
||||||
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
char c;
|
errorMsg = "Cannot read chunk";
|
||||||
if (!_socket->readByte(&c, isCancellationRequested))
|
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
||||||
{
|
headers, payload, errorMsg,
|
||||||
errorMsg = "Cannot read byte";
|
uploadSize, downloadSize);
|
||||||
return std::make_tuple(code, HttpErrorCode_ChunkReadError,
|
|
||||||
headers, payload, errorMsg,
|
|
||||||
uploadSize, downloadSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
payload += c;
|
|
||||||
}
|
}
|
||||||
|
payload += chunkResult.second;
|
||||||
|
|
||||||
|
// Read the line that terminates the chunk (\r\n)
|
||||||
lineResult = _socket->readLine(isCancellationRequested);
|
lineResult = _socket->readLine(isCancellationRequested);
|
||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
|
@ -61,6 +61,7 @@ namespace ix
|
|||||||
bool verbose;
|
bool verbose;
|
||||||
bool compress;
|
bool compress;
|
||||||
Logger logger;
|
Logger logger;
|
||||||
|
OnProgressCallback onProgressCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpClient {
|
class HttpClient {
|
||||||
|
46
ixwebsocket/IXSelectInterrupt.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
ixwebsocket/IXSelectInterrupt.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
116
ixwebsocket/IXSelectInterruptEventFd.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
32
ixwebsocket/IXSelectInterruptEventFd.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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 : 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
25
ixwebsocket/IXSelectInterruptFactory.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
15
ixwebsocket/IXSelectInterruptFactory.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
138
ixwebsocket/IXSelectInterruptPipe.cpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
int fd = _fildes[kPipeReadIndex];
|
||||||
|
|
||||||
|
uint64_t value = 0;
|
||||||
|
::read(fd, &value, sizeof(value));
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SelectInterruptPipe::clear()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SelectInterruptPipe::getFd() const
|
||||||
|
{
|
||||||
|
return _fildes[kPipeReadIndex];
|
||||||
|
}
|
||||||
|
}
|
39
ixwebsocket/IXSelectInterruptPipe.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* IXSelectInterruptPipe.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 SelectInterruptPipe : 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];
|
||||||
|
|
||||||
|
// Used to identify the read/write idx
|
||||||
|
static const int kPipeReadIndex;
|
||||||
|
static const int kPipeWriteIndex;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@
|
|||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXNetSystem.h"
|
#include "IXNetSystem.h"
|
||||||
|
#include "IXSelectInterrupt.h"
|
||||||
|
#include "IXSelectInterruptFactory.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -23,11 +25,15 @@ namespace ix
|
|||||||
{
|
{
|
||||||
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
|
||||||
const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
|
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) :
|
Socket::Socket(int fd) :
|
||||||
_sockfd(fd)
|
_sockfd(fd),
|
||||||
|
_selectInterrupt(createSelectInterrupt())
|
||||||
{
|
{
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket::~Socket()
|
Socket::~Socket()
|
||||||
@ -39,44 +45,93 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (_sockfd == -1)
|
if (_sockfd == -1)
|
||||||
{
|
{
|
||||||
onPollCallback(PollResultType_Error);
|
if (onPollCallback) onPollCallback(PollResultType::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fd_set rfds;
|
PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
|
||||||
FD_ZERO(&rfds);
|
|
||||||
FD_SET(_sockfd, &rfds);
|
|
||||||
|
|
||||||
#ifdef __linux__
|
if (onPollCallback) onPollCallback(pollResult);
|
||||||
FD_SET(_eventfd.getFd(), &rfds);
|
}
|
||||||
#endif
|
|
||||||
|
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;
|
||||||
|
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;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = timeoutSecs;
|
timeout.tv_sec = timeoutMs / 1000;
|
||||||
timeout.tv_usec = 0;
|
timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
|
||||||
|
|
||||||
|
// Compute the highest fd.
|
||||||
int sockfd = _sockfd;
|
int sockfd = _sockfd;
|
||||||
int nfds = (std::max)(sockfd, _eventfd.getFd());
|
int nfds = (std::max)(sockfd, interruptFd);
|
||||||
int ret = select(nfds + 1, &rfds, nullptr, nullptr,
|
|
||||||
(timeoutSecs < 0) ? nullptr : &timeout);
|
|
||||||
|
|
||||||
PollResultType pollResult = PollResultType_ReadyForRead;
|
int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
|
||||||
|
(timeoutMs < 0) ? nullptr : &timeout);
|
||||||
|
|
||||||
|
PollResultType pollResult = PollResultType::ReadyForRead;
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_Error;
|
pollResult = PollResultType::Error;
|
||||||
}
|
}
|
||||||
else if (ret == 0)
|
else if (ret == 0)
|
||||||
{
|
{
|
||||||
pollResult = PollResultType_Timeout;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPollCallback(pollResult);
|
return pollResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Socket::wakeUpFromPoll()
|
PollResultType Socket::isReadyToRead(int timeoutMs)
|
||||||
{
|
{
|
||||||
// this will wake up the thread blocked on select, only needed on Linux
|
bool readyToRead = true;
|
||||||
_eventfd.notify();
|
return select(readyToRead, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PollResultType Socket::isReadyToWrite(int timeoutMs)
|
||||||
|
{
|
||||||
|
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(uint8_t wakeUpCode)
|
||||||
|
{
|
||||||
|
return _selectInterrupt->notify(wakeUpCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::connect(const std::string& host,
|
bool Socket::connect(const std::string& host,
|
||||||
@ -86,7 +141,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_socketMutex);
|
std::lock_guard<std::mutex> lock(_socketMutex);
|
||||||
|
|
||||||
if (!_eventfd.clear()) return false;
|
if (!_selectInterrupt->clear()) return false;
|
||||||
|
|
||||||
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
_sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
|
||||||
return _sockfd != -1;
|
return _sockfd != -1;
|
||||||
@ -145,69 +200,9 @@ namespace ix
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::init()
|
bool Socket::init(std::string& errorMsg)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
return _selectInterrupt->init(errorMsg);
|
||||||
INT rc;
|
|
||||||
WSADATA wsaData;
|
|
||||||
|
|
||||||
rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
||||||
return rc != 0;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Socket::cleanup()
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Socket::readByte(void* buffer,
|
|
||||||
const CancellationRequest& isCancellationRequested)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (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 && (getErrno() == EWOULDBLOCK ||
|
|
||||||
getErrno() == EAGAIN))
|
|
||||||
{
|
|
||||||
// Wait with a timeout until something is written.
|
|
||||||
// This way we are not busy looping
|
|
||||||
fd_set rfds;
|
|
||||||
struct timeval timeout;
|
|
||||||
timeout.tv_sec = 0;
|
|
||||||
timeout.tv_usec = 1 * 1000; // 1ms timeout
|
|
||||||
|
|
||||||
FD_ZERO(&rfds);
|
|
||||||
FD_SET(_sockfd, &rfds);
|
|
||||||
|
|
||||||
if (select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
|
|
||||||
(errno == EBADF || errno == EINVAL))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// There was an error during the read, abort
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Socket::writeBytes(const std::string& str,
|
bool Socket::writeBytes(const std::string& str,
|
||||||
@ -215,7 +210,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) return false;
|
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||||
|
|
||||||
char* buffer = const_cast<char*>(str.c_str());
|
char* buffer = const_cast<char*>(str.c_str());
|
||||||
int len = (int) str.size();
|
int len = (int) str.size();
|
||||||
@ -227,7 +222,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
return ret == len;
|
return ret == len;
|
||||||
}
|
}
|
||||||
// There is possibly something to be write, try again
|
// There is possibly something to be writen, try again
|
||||||
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
|
||||||
getErrno() == EAGAIN))
|
getErrno() == EAGAIN))
|
||||||
{
|
{
|
||||||
@ -241,7 +236,42 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<bool, std::string> Socket::readLine(const CancellationRequest& isCancellationRequested)
|
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 && (getErrno() == EWOULDBLOCK ||
|
||||||
|
getErrno() == EAGAIN))
|
||||||
|
{
|
||||||
|
// 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;
|
char c;
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -251,7 +281,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (!readByte(&c, isCancellationRequested))
|
if (!readByte(&c, isCancellationRequested))
|
||||||
{
|
{
|
||||||
return std::make_pair(false, std::string());
|
// Return what we were able to read
|
||||||
|
return std::make_pair(false, line);
|
||||||
}
|
}
|
||||||
|
|
||||||
line += c;
|
line += c;
|
||||||
@ -259,4 +290,52 @@ namespace ix
|
|||||||
|
|
||||||
return std::make_pair(true, line);
|
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 && (getErrno() != EWOULDBLOCK &&
|
||||||
|
getErrno() != EAGAIN))
|
||||||
|
{
|
||||||
|
// Error
|
||||||
|
return std::make_pair(false, std::string());
|
||||||
|
}
|
||||||
|
else if (ret > 0)
|
||||||
|
{
|
||||||
|
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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,22 +10,29 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <BaseTsd.h>
|
#include <BaseTsd.h>
|
||||||
typedef SSIZE_T ssize_t;
|
typedef SSIZE_T ssize_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "IXEventFd.h"
|
|
||||||
#include "IXCancellationRequest.h"
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXProgressCallback.h"
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
enum PollResultType
|
class SelectInterrupt;
|
||||||
|
|
||||||
|
enum class PollResultType
|
||||||
{
|
{
|
||||||
PollResultType_ReadyForRead = 0,
|
ReadyForRead = 0,
|
||||||
PollResultType_Timeout = 1,
|
ReadyForWrite = 1,
|
||||||
PollResultType_Error = 2
|
Timeout = 2,
|
||||||
|
Error = 3,
|
||||||
|
SendRequest = 4,
|
||||||
|
CloseRequest = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
class Socket {
|
class Socket {
|
||||||
@ -34,12 +41,17 @@ namespace ix
|
|||||||
|
|
||||||
Socket(int fd = -1);
|
Socket(int fd = -1);
|
||||||
virtual ~Socket();
|
virtual ~Socket();
|
||||||
|
bool init(std::string& errorMsg);
|
||||||
|
|
||||||
void configure();
|
void configure();
|
||||||
|
|
||||||
virtual void poll(const OnPollCallback& onPollCallback,
|
// Functions to check whether there is activity on the socket
|
||||||
int timeoutSecs = kDefaultPollTimeout);
|
void poll(const OnPollCallback& onPollCallback,
|
||||||
virtual void wakeUpFromPoll();
|
int timeoutSecs = kDefaultPollTimeout);
|
||||||
|
bool wakeUpFromPoll(uint8_t wakeUpCode);
|
||||||
|
|
||||||
|
PollResultType isReadyToWrite(int timeoutMs);
|
||||||
|
PollResultType isReadyToRead(int timeoutMs);
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
virtual bool connect(const std::string& url,
|
virtual bool connect(const std::string& url,
|
||||||
@ -58,21 +70,36 @@ namespace ix
|
|||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
bool writeBytes(const std::string& str,
|
bool writeBytes(const std::string& str,
|
||||||
const CancellationRequest& isCancellationRequested);
|
const CancellationRequest& isCancellationRequested);
|
||||||
std::pair<bool, std::string> readLine(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 int getErrno();
|
||||||
static bool init(); // Required on Windows to initialize WinSocket
|
|
||||||
static void cleanup(); // Required on Windows to cleanup WinSocket
|
// Used as special codes for pipe communication
|
||||||
|
static const uint64_t kSendRequest;
|
||||||
|
static const uint64_t kCloseRequest;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void closeSocket(int fd);
|
void closeSocket(int fd);
|
||||||
|
|
||||||
std::atomic<int> _sockfd;
|
std::atomic<int> _sockfd;
|
||||||
std::mutex _socketMutex;
|
std::mutex _socketMutex;
|
||||||
EventFd _eventfd;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
PollResultType select(bool readyToRead, int timeoutMs);
|
||||||
|
|
||||||
static const int kDefaultPollTimeout;
|
static const int kDefaultPollTimeout;
|
||||||
static const int kDefaultPollNoTimeout;
|
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ namespace ix
|
|||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested()) // Must handle timeout as well
|
if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
|
||||||
{
|
{
|
||||||
closeSocket(fd);
|
closeSocket(fd);
|
||||||
errMsg = "Cancelled";
|
errMsg = "Cancelled";
|
||||||
|
@ -20,23 +20,45 @@ namespace ix
|
|||||||
std::string& errorMsg)
|
std::string& errorMsg)
|
||||||
{
|
{
|
||||||
errorMsg.clear();
|
errorMsg.clear();
|
||||||
|
std::shared_ptr<Socket> socket;
|
||||||
|
|
||||||
if (!tls)
|
if (!tls)
|
||||||
{
|
{
|
||||||
return std::make_shared<Socket>();
|
socket = std::make_shared<Socket>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
#ifdef IXWEBSOCKET_USE_TLS
|
||||||
# ifdef __APPLE__
|
# ifdef __APPLE__
|
||||||
return std::make_shared<SocketAppleSSL>();
|
socket = std::make_shared<SocketAppleSSL>();
|
||||||
# else
|
# else
|
||||||
return std::make_shared<SocketOpenSSL>();
|
socket = std::make_shared<SocketOpenSSL>();
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
errorMsg = "TLS support is not enabled on this platform.";
|
errorMsg = "TLS support is not enabled on this platform.";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif
|
#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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,4 +14,7 @@ namespace ix
|
|||||||
class Socket;
|
class Socket;
|
||||||
std::shared_ptr<Socket> createSocket(bool tls,
|
std::shared_ptr<Socket> createSocket(bool tls,
|
||||||
std::string& errorMsg);
|
std::string& errorMsg);
|
||||||
|
|
||||||
|
std::shared_ptr<Socket> createSocket(int fd,
|
||||||
|
std::string& errorMsg);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
|
||||||
|
std::once_flag SocketOpenSSL::_openSSLInitFlag;
|
||||||
|
|
||||||
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
|
||||||
_ssl_connection(nullptr),
|
_ssl_connection(nullptr),
|
||||||
|
@ -50,7 +50,7 @@ namespace ix
|
|||||||
const SSL_METHOD* _ssl_method;
|
const SSL_METHOD* _ssl_method;
|
||||||
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
mutable std::mutex _mutex; // OpenSSL routines are not thread-safe
|
||||||
|
|
||||||
std::once_flag _openSSLInitFlag;
|
static std::once_flag _openSSLInitFlag;
|
||||||
static std::atomic<bool> _openSSLInitializationSuccessful;
|
static std::atomic<bool> _openSSLInitializationSuccessful;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ namespace ix
|
|||||||
_host(host),
|
_host(host),
|
||||||
_backlog(backlog),
|
_backlog(backlog),
|
||||||
_maxConnections(maxConnections),
|
_maxConnections(maxConnections),
|
||||||
_stop(false)
|
_stop(false),
|
||||||
|
_connectionStateFactory(&ConnectionState::createConnectionState)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -145,6 +146,12 @@ namespace ix
|
|||||||
::close(_serverFd);
|
::close(_serverFd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SocketServer::setConnectionStateFactory(
|
||||||
|
const ConnectionStateFactory& connectionStateFactory)
|
||||||
|
{
|
||||||
|
_connectionStateFactory = connectionStateFactory;
|
||||||
|
}
|
||||||
|
|
||||||
void SocketServer::run()
|
void SocketServer::run()
|
||||||
{
|
{
|
||||||
// Set the socket to non blocking mode, so that accept calls are not blocking
|
// Set the socket to non blocking mode, so that accept calls are not blocking
|
||||||
@ -214,6 +221,12 @@ namespace ix
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ConnectionState> connectionState;
|
||||||
|
if (_connectionStateFactory)
|
||||||
|
{
|
||||||
|
connectionState = _connectionStateFactory();
|
||||||
|
}
|
||||||
|
|
||||||
// Launch the handleConnection work asynchronously in its own thread.
|
// Launch the handleConnection work asynchronously in its own thread.
|
||||||
//
|
//
|
||||||
// the destructor of a future returned by std::async blocks,
|
// the destructor of a future returned by std::async blocks,
|
||||||
@ -221,7 +234,8 @@ namespace ix
|
|||||||
f = std::async(std::launch::async,
|
f = std::async(std::launch::async,
|
||||||
&SocketServer::handleConnection,
|
&SocketServer::handleConnection,
|
||||||
this,
|
this,
|
||||||
clientFd);
|
clientFd,
|
||||||
|
connectionState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXConnectionState.h"
|
||||||
|
|
||||||
#include <utility> // pair
|
#include <utility> // pair
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
@ -20,6 +22,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class SocketServer {
|
class SocketServer {
|
||||||
public:
|
public:
|
||||||
|
using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
|
||||||
|
|
||||||
SocketServer(int port = SocketServer::kDefaultPort,
|
SocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
@ -27,6 +31,8 @@ namespace ix
|
|||||||
virtual ~SocketServer();
|
virtual ~SocketServer();
|
||||||
virtual void stop();
|
virtual void stop();
|
||||||
|
|
||||||
|
void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
|
||||||
|
|
||||||
const static int kDefaultPort;
|
const static int kDefaultPort;
|
||||||
const static std::string kDefaultHost;
|
const static std::string kDefaultHost;
|
||||||
const static int kDefaultTcpBacklog;
|
const static int kDefaultTcpBacklog;
|
||||||
@ -60,9 +66,13 @@ namespace ix
|
|||||||
std::condition_variable _conditionVariable;
|
std::condition_variable _conditionVariable;
|
||||||
std::mutex _conditionVariableMutex;
|
std::mutex _conditionVariableMutex;
|
||||||
|
|
||||||
|
//
|
||||||
|
ConnectionStateFactory _connectionStateFactory;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
void run();
|
void run();
|
||||||
virtual void handleConnection(int fd) = 0;
|
virtual void handleConnection(int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState) = 0;
|
||||||
virtual size_t getConnectedClientsCount() = 0;
|
virtual size_t getConnectedClientsCount() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -79,10 +79,10 @@ namespace ix
|
|||||||
return _perMessageDeflateOptions;
|
return _perMessageDeflateOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
|
void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_configMutex);
|
std::lock_guard<std::mutex> lock(_configMutex);
|
||||||
_heartBeatPeriod = hearBeatPeriod;
|
_heartBeatPeriod = heartBeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebSocket::getHeartBeatPeriod() const
|
int WebSocket::getHeartBeatPeriod() const
|
||||||
@ -252,6 +252,11 @@ namespace ix
|
|||||||
{
|
{
|
||||||
webSocketMessageType = WebSocket_MessageType_Pong;
|
webSocketMessageType = WebSocket_MessageType_Pong;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case WebSocketTransport::FRAGMENT:
|
||||||
|
{
|
||||||
|
webSocketMessageType = WebSocket_MessageType_Fragment;
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketErrorInfo webSocketErrorInfo;
|
WebSocketErrorInfo webSocketErrorInfo;
|
||||||
@ -297,7 +302,13 @@ namespace ix
|
|||||||
WebSocketSendInfo WebSocket::send(const std::string& text,
|
WebSocketSendInfo WebSocket::send(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
return sendMessage(text, false, onProgressCallback);
|
return sendMessage(text, 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)
|
WebSocketSendInfo WebSocket::ping(const std::string& text)
|
||||||
@ -306,11 +317,11 @@ namespace ix
|
|||||||
constexpr size_t pingMaxPayloadSize = 125;
|
constexpr size_t pingMaxPayloadSize = 125;
|
||||||
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
|
||||||
|
|
||||||
return sendMessage(text, true);
|
return sendMessage(text, SendMessageKind::Ping);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
|
||||||
bool ping,
|
SendMessageKind sendMessageKind,
|
||||||
const OnProgressCallback& onProgressCallback)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
if (!isConnected()) return WebSocketSendInfo(false);
|
if (!isConnected()) return WebSocketSendInfo(false);
|
||||||
@ -327,13 +338,22 @@ namespace ix
|
|||||||
std::lock_guard<std::mutex> lock(_writeMutex);
|
std::lock_guard<std::mutex> lock(_writeMutex);
|
||||||
WebSocketSendInfo webSocketSendInfo;
|
WebSocketSendInfo webSocketSendInfo;
|
||||||
|
|
||||||
if (ping)
|
switch (sendMessageKind)
|
||||||
{
|
{
|
||||||
webSocketSendInfo = _ws.sendPing(text);
|
case SendMessageKind::Text:
|
||||||
}
|
{
|
||||||
else
|
webSocketSendInfo = _ws.sendText(text, onProgressCallback);
|
||||||
{
|
} break;
|
||||||
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
|
||||||
|
case SendMessageKind::Binary:
|
||||||
|
{
|
||||||
|
webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SendMessageKind::Ping:
|
||||||
|
{
|
||||||
|
webSocketSendInfo = _ws.sendPing(text);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
|
||||||
@ -374,4 +394,9 @@ namespace ix
|
|||||||
{
|
{
|
||||||
_automaticReconnection = false;
|
_automaticReconnection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t WebSocket::bufferedAmount() const
|
||||||
|
{
|
||||||
|
return _ws.bufferedAmount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,8 @@ namespace ix
|
|||||||
WebSocket_MessageType_Close = 2,
|
WebSocket_MessageType_Close = 2,
|
||||||
WebSocket_MessageType_Error = 3,
|
WebSocket_MessageType_Error = 3,
|
||||||
WebSocket_MessageType_Ping = 4,
|
WebSocket_MessageType_Ping = 4,
|
||||||
WebSocket_MessageType_Pong = 5
|
WebSocket_MessageType_Pong = 5,
|
||||||
|
WebSocket_MessageType_Fragment = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WebSocketOpenInfo
|
struct WebSocketOpenInfo
|
||||||
@ -88,7 +89,7 @@ namespace ix
|
|||||||
void setUrl(const std::string& url);
|
void setUrl(const std::string& url);
|
||||||
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
|
||||||
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
void setHandshakeTimeout(int handshakeTimeoutSecs);
|
||||||
void setHeartBeatPeriod(int hearBeatPeriod);
|
void setHeartBeatPeriod(int heartBeatPeriod);
|
||||||
|
|
||||||
// Run asynchronously, by calling start and stop.
|
// Run asynchronously, by calling start and stop.
|
||||||
void start();
|
void start();
|
||||||
@ -100,6 +101,8 @@ namespace ix
|
|||||||
|
|
||||||
WebSocketSendInfo send(const std::string& text,
|
WebSocketSendInfo send(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo ping(const std::string& text);
|
WebSocketSendInfo ping(const std::string& text);
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@ -111,6 +114,7 @@ namespace ix
|
|||||||
const std::string& getUrl() const;
|
const std::string& getUrl() const;
|
||||||
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
|
const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
|
||||||
int getHeartBeatPeriod() const;
|
int getHeartBeatPeriod() const;
|
||||||
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
void enableAutomaticReconnection();
|
void enableAutomaticReconnection();
|
||||||
void disableAutomaticReconnection();
|
void disableAutomaticReconnection();
|
||||||
@ -118,7 +122,7 @@ namespace ix
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
WebSocketSendInfo sendMessage(const std::string& text,
|
WebSocketSendInfo sendMessage(const std::string& text,
|
||||||
bool ping,
|
SendMessageKind sendMessageKind,
|
||||||
const OnProgressCallback& callback = nullptr);
|
const OnProgressCallback& callback = nullptr);
|
||||||
|
|
||||||
bool isConnected() const;
|
bool isConnected() const;
|
||||||
|
@ -114,7 +114,7 @@ namespace ix
|
|||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "HTTP/1.1 ";
|
ss << "HTTP/1.1 ";
|
||||||
ss << code;
|
ss << code;
|
||||||
ss << "\r\n";
|
ss << " ";
|
||||||
ss << reason;
|
ss << reason;
|
||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ namespace ix
|
|||||||
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
|
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "HTTP/1.1 101\r\n";
|
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||||
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
||||||
ss << "Upgrade: websocket\r\n";
|
ss << "Upgrade: websocket\r\n";
|
||||||
ss << "Connection: Upgrade\r\n";
|
ss << "Connection: Upgrade\r\n";
|
||||||
|
@ -49,10 +49,12 @@ namespace ix
|
|||||||
_onConnectionCallback = callback;
|
_onConnectionCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketServer::handleConnection(int fd)
|
void WebSocketServer::handleConnection(
|
||||||
|
int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
auto webSocket = std::make_shared<WebSocket>();
|
auto webSocket = std::make_shared<WebSocket>();
|
||||||
_onConnectionCallback(webSocket);
|
_onConnectionCallback(webSocket, connectionState);
|
||||||
|
|
||||||
webSocket->disableAutomaticReconnection();
|
webSocket->disableAutomaticReconnection();
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;
|
using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>,
|
||||||
|
std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
class WebSocketServer : public SocketServer {
|
class WebSocketServer : public SocketServer {
|
||||||
public:
|
public:
|
||||||
@ -49,7 +50,8 @@ namespace ix
|
|||||||
const static int kDefaultHandShakeTimeoutSecs;
|
const static int kDefaultHandShakeTimeoutSecs;
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
virtual void handleConnection(int fd) final;
|
virtual void handleConnection(int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
virtual size_t getConnectedClientsCount() final;
|
virtual size_t getConnectedClientsCount() final;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2012, 2013 <dhbaird@gmail.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.
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IXWebSocketTransport.cpp
|
* IXWebSocketTransport.cpp
|
||||||
* Author: Benjamin Sergeant
|
* Author: Benjamin Sergeant
|
||||||
* Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
|
* Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -14,14 +38,6 @@
|
|||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
#include "IXSocketFactory.h"
|
#include "IXSocketFactory.h"
|
||||||
|
|
||||||
#ifdef IXWEBSOCKET_USE_TLS
|
|
||||||
# ifdef __APPLE__
|
|
||||||
# include "IXSocketAppleSSL.h"
|
|
||||||
# else
|
|
||||||
# include "IXSocketOpenSSL.h"
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@ -37,11 +53,12 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
|
const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::heartbeat");
|
||||||
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
|
||||||
constexpr size_t WebSocketTransport::kChunkSize;
|
constexpr size_t WebSocketTransport::kChunkSize;
|
||||||
|
|
||||||
WebSocketTransport::WebSocketTransport() :
|
WebSocketTransport::WebSocketTransport() :
|
||||||
|
_useMask(true),
|
||||||
_readyState(CLOSED),
|
_readyState(CLOSED),
|
||||||
_closeCode(0),
|
_closeCode(0),
|
||||||
_closeWireSize(0),
|
_closeWireSize(0),
|
||||||
@ -59,11 +76,11 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||||
int hearBeatPeriod)
|
int heartBeatPeriod)
|
||||||
{
|
{
|
||||||
_perMessageDeflateOptions = perMessageDeflateOptions;
|
_perMessageDeflateOptions = perMessageDeflateOptions;
|
||||||
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
|
_enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
|
||||||
_heartBeatPeriod = hearBeatPeriod;
|
_heartBeatPeriod = heartBeatPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client
|
// Client
|
||||||
@ -80,16 +97,6 @@ namespace ix
|
|||||||
std::string("Could not parse URL ") + url);
|
std::string("Could not parse URL ") + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (protocol != "ws" && protocol != "wss")
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Invalid protocol: " << protocol
|
|
||||||
<< " for url " << url
|
|
||||||
<< " . Supported protocols are ws and wss";
|
|
||||||
|
|
||||||
return WebSocketInitResult(false, 0, ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tls = protocol == "wss";
|
bool tls = protocol == "wss";
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
_socket = createSocket(tls, errorMsg);
|
_socket = createSocket(tls, errorMsg);
|
||||||
@ -117,8 +124,16 @@ namespace ix
|
|||||||
// Server
|
// Server
|
||||||
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
WebSocketInitResult WebSocketTransport::connectToSocket(int fd, int timeoutSecs)
|
||||||
{
|
{
|
||||||
_socket.reset();
|
// Server should not mask the data it sends to the client
|
||||||
_socket = std::make_shared<Socket>(fd);
|
_useMask = false;
|
||||||
|
|
||||||
|
std::string errorMsg;
|
||||||
|
_socket = createSocket(fd, errorMsg);
|
||||||
|
|
||||||
|
if (!_socket)
|
||||||
|
{
|
||||||
|
return WebSocketInitResult(false, 0, errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
WebSocketHandshake webSocketHandshake(_requestInitCancellation,
|
||||||
_socket,
|
_socket,
|
||||||
@ -178,43 +193,75 @@ namespace ix
|
|||||||
// If (1) heartbeat is enabled, and (2) no data was received or
|
// If (1) heartbeat is enabled, and (2) no data was received or
|
||||||
// send for a duration exceeding our heart-beat period, send a
|
// send for a duration exceeding our heart-beat period, send a
|
||||||
// ping to the server.
|
// ping to the server.
|
||||||
if (pollResult == PollResultType_Timeout &&
|
if (pollResult == PollResultType::Timeout &&
|
||||||
heartBeatPeriodExceeded())
|
heartBeatPeriodExceeded())
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
|
ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
|
||||||
sendPing(ss.str());
|
sendPing(ss.str());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// Make sure we send all the buffered data
|
||||||
while (true)
|
// there can be a lot of it for large messages.
|
||||||
|
else if (pollResult == PollResultType::SendRequest)
|
||||||
{
|
{
|
||||||
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
while (!isSendBufferEmpty() && !_requestInitCancellation)
|
||||||
|
{
|
||||||
|
// Wait with a 10ms timeout until the socket is ready to write.
|
||||||
|
// This way we are not busy looping
|
||||||
|
PollResultType result = _socket->isReadyToWrite(10);
|
||||||
|
|
||||||
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
if (result == PollResultType::Error)
|
||||||
_socket->getErrno() == EAGAIN))
|
{
|
||||||
{
|
_socket->close();
|
||||||
break;
|
setReadyState(CLOSED);
|
||||||
}
|
break;
|
||||||
else if (ret <= 0)
|
}
|
||||||
{
|
else if (result == PollResultType::ReadyForWrite)
|
||||||
_rxbuf.clear();
|
{
|
||||||
_socket->close();
|
sendOnSocket();
|
||||||
setReadyState(CLOSED);
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_rxbuf.insert(_rxbuf.end(),
|
|
||||||
_readbuf.begin(),
|
|
||||||
_readbuf.begin() + ret);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (pollResult == PollResultType::ReadyForRead)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
|
||||||
|
|
||||||
if (isSendBufferEmpty() && _readyState == CLOSING)
|
if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
|
||||||
|
_socket->getErrno() == EAGAIN))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (ret <= 0)
|
||||||
|
{
|
||||||
|
_rxbuf.clear();
|
||||||
|
_socket->close();
|
||||||
|
setReadyState(CLOSED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rxbuf.insert(_rxbuf.end(),
|
||||||
|
_readbuf.begin(),
|
||||||
|
_readbuf.begin() + ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
_socket->close();
|
||||||
|
}
|
||||||
|
else if (pollResult == PollResultType::CloseRequest)
|
||||||
|
{
|
||||||
|
_socket->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid a race condition where we get stuck in select
|
||||||
|
// while closing.
|
||||||
|
if (_readyState == CLOSING)
|
||||||
{
|
{
|
||||||
_socket->close();
|
_socket->close();
|
||||||
setReadyState(CLOSED);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_heartBeatPeriod);
|
_heartBeatPeriod);
|
||||||
@ -237,19 +284,15 @@ namespace ix
|
|||||||
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
|
_txbuf.insert(_txbuf.end(), header.begin(), header.end());
|
||||||
_txbuf.insert(_txbuf.end(), begin, end);
|
_txbuf.insert(_txbuf.end(), begin, end);
|
||||||
|
|
||||||
// Masking
|
if (_useMask)
|
||||||
for (size_t i = 0; i != (size_t) message_size; ++i)
|
|
||||||
{
|
{
|
||||||
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
|
for (size_t i = 0; i != (size_t) message_size; ++i)
|
||||||
|
{
|
||||||
|
*(_txbuf.end() - (size_t) message_size + i) ^= masking_key[i&0x3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebSocketTransport::appendToSendBuffer(const std::vector<uint8_t>& buffer)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
|
||||||
_txbuf.insert(_txbuf.end(), buffer.begin(), buffer.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws)
|
void WebSocketTransport::unmaskReceiveBuffer(const wsheader_type& ws)
|
||||||
{
|
{
|
||||||
if (ws.mask)
|
if (ws.mask)
|
||||||
@ -392,6 +435,10 @@ namespace ix
|
|||||||
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
|
emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
|
||||||
_chunks.clear();
|
_chunks.clear();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ws.opcode == wsheader_type::PING)
|
else if (ws.opcode == wsheader_type::PING)
|
||||||
@ -475,7 +522,7 @@ namespace ix
|
|||||||
size_t wireSize = message.size();
|
size_t wireSize = message.size();
|
||||||
|
|
||||||
// When the RSV1 bit is 1 it means the message is compressed
|
// When the RSV1 bit is 1 it means the message is compressed
|
||||||
if (_enablePerMessageDeflate && ws.rsv1)
|
if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
|
||||||
{
|
{
|
||||||
std::string decompressedMessage;
|
std::string decompressedMessage;
|
||||||
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
bool success = _perMessageDeflate.decompress(message, decompressedMessage);
|
||||||
@ -573,7 +620,7 @@ namespace ix
|
|||||||
// Send message
|
// Send message
|
||||||
sendFragment(opcodeType, fin, begin, end, compress);
|
sendFragment(opcodeType, fin, begin, end, compress);
|
||||||
|
|
||||||
if (onProgressCallback && !onProgressCallback(i, steps))
|
if (onProgressCallback && !onProgressCallback((int)i, (int) steps))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -582,6 +629,12 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request to flush the send buffer on the background thread if it isn't empty
|
||||||
|
if (!isSendBufferEmpty())
|
||||||
|
{
|
||||||
|
_socket->wakeUpFromPoll(Socket::kSendRequest);
|
||||||
|
}
|
||||||
|
|
||||||
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
|
return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,7 +656,8 @@ namespace ix
|
|||||||
std::vector<uint8_t> header;
|
std::vector<uint8_t> header;
|
||||||
header.assign(2 +
|
header.assign(2 +
|
||||||
(message_size >= 126 ? 2 : 0) +
|
(message_size >= 126 ? 2 : 0) +
|
||||||
(message_size >= 65536 ? 6 : 0) + 4, 0);
|
(message_size >= 65536 ? 6 : 0) +
|
||||||
|
(_useMask ? 4 : 0), 0);
|
||||||
header[0] = type;
|
header[0] = type;
|
||||||
|
|
||||||
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
// The fin bit indicate that this is the last fragment. Fin is French for end.
|
||||||
@ -620,27 +674,33 @@ namespace ix
|
|||||||
|
|
||||||
if (message_size < 126)
|
if (message_size < 126)
|
||||||
{
|
{
|
||||||
header[1] = (message_size & 0xff) | 0x80;
|
header[1] = (message_size & 0xff) | (_useMask ? 0x80 : 0);
|
||||||
|
|
||||||
header[2] = masking_key[0];
|
if (_useMask)
|
||||||
header[3] = masking_key[1];
|
{
|
||||||
header[4] = masking_key[2];
|
header[2] = masking_key[0];
|
||||||
header[5] = masking_key[3];
|
header[3] = masking_key[1];
|
||||||
|
header[4] = masking_key[2];
|
||||||
|
header[5] = masking_key[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (message_size < 65536)
|
else if (message_size < 65536)
|
||||||
{
|
{
|
||||||
header[1] = 126 | 0x80;
|
header[1] = 126 | (_useMask ? 0x80 : 0);
|
||||||
header[2] = (message_size >> 8) & 0xff;
|
header[2] = (message_size >> 8) & 0xff;
|
||||||
header[3] = (message_size >> 0) & 0xff;
|
header[3] = (message_size >> 0) & 0xff;
|
||||||
|
|
||||||
header[4] = masking_key[0];
|
if (_useMask)
|
||||||
header[5] = masking_key[1];
|
{
|
||||||
header[6] = masking_key[2];
|
header[4] = masking_key[0];
|
||||||
header[7] = masking_key[3];
|
header[5] = masking_key[1];
|
||||||
|
header[6] = masking_key[2];
|
||||||
|
header[7] = masking_key[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // TODO: run coverage testing here
|
{ // TODO: run coverage testing here
|
||||||
header[1] = 127 | 0x80;
|
header[1] = 127 | (_useMask ? 0x80 : 0);
|
||||||
header[2] = (message_size >> 56) & 0xff;
|
header[2] = (message_size >> 56) & 0xff;
|
||||||
header[3] = (message_size >> 48) & 0xff;
|
header[3] = (message_size >> 48) & 0xff;
|
||||||
header[4] = (message_size >> 40) & 0xff;
|
header[4] = (message_size >> 40) & 0xff;
|
||||||
@ -650,10 +710,13 @@ namespace ix
|
|||||||
header[8] = (message_size >> 8) & 0xff;
|
header[8] = (message_size >> 8) & 0xff;
|
||||||
header[9] = (message_size >> 0) & 0xff;
|
header[9] = (message_size >> 0) & 0xff;
|
||||||
|
|
||||||
header[10] = masking_key[0];
|
if (_useMask)
|
||||||
header[11] = masking_key[1];
|
{
|
||||||
header[12] = masking_key[2];
|
header[10] = masking_key[0];
|
||||||
header[13] = masking_key[3];
|
header[11] = masking_key[1];
|
||||||
|
header[12] = masking_key[2];
|
||||||
|
header[13] = masking_key[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _txbuf will keep growing until it can be transmitted over the socket:
|
// _txbuf will keep growing until it can be transmitted over the socket:
|
||||||
@ -679,6 +742,15 @@ namespace ix
|
|||||||
_enablePerMessageDeflate, onProgressCallback);
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebSocketSendInfo WebSocketTransport::sendText(
|
||||||
|
const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback)
|
||||||
|
|
||||||
|
{
|
||||||
|
return sendData(wsheader_type::TEXT_FRAME, message,
|
||||||
|
_enablePerMessageDeflate, onProgressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketTransport::sendOnSocket()
|
void WebSocketTransport::sendOnSocket()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(_txbufMutex);
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||||
@ -727,8 +799,18 @@ namespace ix
|
|||||||
sendData(wsheader_type::CLOSE, normalClosure, compress);
|
sendData(wsheader_type::CLOSE, normalClosure, compress);
|
||||||
setReadyState(CLOSING);
|
setReadyState(CLOSING);
|
||||||
|
|
||||||
_socket->wakeUpFromPoll();
|
_socket->wakeUpFromPoll(Socket::kCloseRequest);
|
||||||
_socket->close();
|
_socket->close();
|
||||||
|
|
||||||
|
_closeCode = 1000;
|
||||||
|
_closeReason = "Normal Closure";
|
||||||
|
setReadyState(CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t WebSocketTransport::bufferedAmount() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(_txbufMutex);
|
||||||
|
return _txbuf.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -30,6 +30,13 @@ namespace ix
|
|||||||
{
|
{
|
||||||
class Socket;
|
class Socket;
|
||||||
|
|
||||||
|
enum class SendMessageKind
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Binary,
|
||||||
|
Ping
|
||||||
|
};
|
||||||
|
|
||||||
class WebSocketTransport
|
class WebSocketTransport
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -45,7 +52,8 @@ namespace ix
|
|||||||
{
|
{
|
||||||
MSG,
|
MSG,
|
||||||
PING,
|
PING,
|
||||||
PONG
|
PONG,
|
||||||
|
FRAGMENT
|
||||||
};
|
};
|
||||||
|
|
||||||
using OnMessageCallback = std::function<void(const std::string&,
|
using OnMessageCallback = std::function<void(const std::string&,
|
||||||
@ -60,7 +68,7 @@ namespace ix
|
|||||||
~WebSocketTransport();
|
~WebSocketTransport();
|
||||||
|
|
||||||
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
||||||
int hearBeatPeriod);
|
int heartBeatPeriod);
|
||||||
|
|
||||||
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
WebSocketInitResult connectToUrl(const std::string& url, // Client
|
||||||
int timeoutSecs);
|
int timeoutSecs);
|
||||||
@ -70,12 +78,15 @@ namespace ix
|
|||||||
void poll();
|
void poll();
|
||||||
WebSocketSendInfo sendBinary(const std::string& message,
|
WebSocketSendInfo sendBinary(const std::string& message,
|
||||||
const OnProgressCallback& onProgressCallback);
|
const OnProgressCallback& onProgressCallback);
|
||||||
|
WebSocketSendInfo sendText(const std::string& message,
|
||||||
|
const OnProgressCallback& onProgressCallback);
|
||||||
WebSocketSendInfo sendPing(const std::string& message);
|
WebSocketSendInfo sendPing(const std::string& message);
|
||||||
void close();
|
void close();
|
||||||
ReadyStateValues getReadyState() const;
|
ReadyStateValues getReadyState() const;
|
||||||
void setReadyState(ReadyStateValues readyStateValue);
|
void setReadyState(ReadyStateValues readyStateValue);
|
||||||
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
|
||||||
void dispatch(const OnMessageCallback& onMessageCallback);
|
void dispatch(const OnMessageCallback& onMessageCallback);
|
||||||
|
size_t bufferedAmount() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _url;
|
std::string _url;
|
||||||
@ -98,6 +109,10 @@ namespace ix
|
|||||||
uint8_t masking_key[4];
|
uint8_t masking_key[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tells whether we should mask the data we send.
|
||||||
|
// client should mask but server should not
|
||||||
|
bool _useMask;
|
||||||
|
|
||||||
// Buffer for reading from our socket. That buffer is never resized.
|
// Buffer for reading from our socket. That buffer is never resized.
|
||||||
std::vector<uint8_t> _readbuf;
|
std::vector<uint8_t> _readbuf;
|
||||||
|
|
||||||
@ -146,7 +161,7 @@ namespace ix
|
|||||||
mutable std::mutex _lastSendTimePointMutex;
|
mutable std::mutex _lastSendTimePointMutex;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
|
||||||
|
|
||||||
// No data was send through the socket for longer that the hearbeat period
|
// No data was send through the socket for longer than the heartbeat period
|
||||||
bool heartBeatPeriodExceeded();
|
bool heartBeatPeriodExceeded();
|
||||||
|
|
||||||
void sendOnSocket();
|
void sendOnSocket();
|
||||||
@ -172,7 +187,6 @@ namespace ix
|
|||||||
std::string::const_iterator end,
|
std::string::const_iterator end,
|
||||||
uint64_t message_size,
|
uint64_t message_size,
|
||||||
uint8_t masking_key[4]);
|
uint8_t masking_key[4]);
|
||||||
void appendToSendBuffer(const std::vector<uint8_t>& buffer);
|
|
||||||
|
|
||||||
unsigned getRandomUnsigned();
|
unsigned getRandomUnsigned();
|
||||||
void unmaskReceiveBuffer(const wsheader_type& ws);
|
void unmaskReceiveBuffer(const wsheader_type& ws);
|
||||||
|
25
makefile
@ -3,15 +3,29 @@
|
|||||||
#
|
#
|
||||||
all: brew
|
all: brew
|
||||||
|
|
||||||
|
install: brew
|
||||||
|
|
||||||
brew:
|
brew:
|
||||||
mkdir -p build && (cd build ; cmake .. ; make -j install)
|
mkdir -p build && (cd build ; cmake .. ; make -j install)
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
|
|
||||||
|
NAME := bsergean/ws
|
||||||
|
TAG := $(shell cat DOCKER_VERSION)
|
||||||
|
IMG := ${NAME}:${TAG}
|
||||||
|
LATEST := ${NAME}:latest
|
||||||
|
BUILD := ${NAME}:build
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t broadcast_server:latest .
|
docker build -t ${IMG} .
|
||||||
|
docker tag ${IMG} ${BUILD}
|
||||||
|
|
||||||
|
docker_push:
|
||||||
|
docker tag ${IMG} ${LATEST}
|
||||||
|
docker push ${LATEST}
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker run --cap-add sys_ptrace -it broadcast_server:latest bash
|
docker run --cap-add sys_ptrace -it ws:latest
|
||||||
|
|
||||||
# this is helpful to remove trailing whitespaces
|
# this is helpful to remove trailing whitespaces
|
||||||
trail:
|
trail:
|
||||||
@ -36,6 +50,9 @@ test_server:
|
|||||||
test:
|
test:
|
||||||
python test/run.py
|
python test/run.py
|
||||||
|
|
||||||
|
ws_test: all
|
||||||
|
(cd ws ; bash test_ws.sh)
|
||||||
|
|
||||||
# For the fork that is configured with appveyor
|
# For the fork that is configured with appveyor
|
||||||
rebase_upstream:
|
rebase_upstream:
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
@ -43,5 +60,9 @@ rebase_upstream:
|
|||||||
git reset --hard upstream/master
|
git reset --hard upstream/master
|
||||||
git push origin master --force
|
git push origin master --force
|
||||||
|
|
||||||
|
install_cmake_for_linux:
|
||||||
|
mkdir -p /tmp/cmake
|
||||||
|
(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0-rc4/cmake-3.14.0-rc4-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-rc4-Linux-x86_64.tar.gz)
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
|
@ -29,6 +29,7 @@ set (SOURCES
|
|||||||
|
|
||||||
IXDNSLookupTest.cpp
|
IXDNSLookupTest.cpp
|
||||||
IXSocketTest.cpp
|
IXSocketTest.cpp
|
||||||
|
IXSocketConnectTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
|
43
test/IXSocketConnectTest.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketConnectTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
#include "IXTest.h"
|
||||||
|
#include <ixwebsocket/IXSocketConnect.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("socket_connect", "[net]")
|
||||||
|
{
|
||||||
|
SECTION("Test connecting to a known hostname")
|
||||||
|
{
|
||||||
|
std::string errMsg;
|
||||||
|
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return false; });
|
||||||
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
|
REQUIRE(fd != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Test connecting to a non-existing hostname")
|
||||||
|
{
|
||||||
|
std::string errMsg;
|
||||||
|
std::string hostname("12313lhjlkjhopiupoijlkasdckljqwehrlkqjwehraospidcuaposidcasdc");
|
||||||
|
int fd = SocketConnect::connect(hostname, 80, errMsg, [] { return false; });
|
||||||
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
|
REQUIRE(fd == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Test connecting to a good hostname, with cancellation")
|
||||||
|
{
|
||||||
|
std::string errMsg;
|
||||||
|
// The callback returning true means we are requesting cancellation
|
||||||
|
int fd = SocketConnect::connect("www.google.com", 80, errMsg, [] { return true; });
|
||||||
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
|
REQUIRE(fd == -1);
|
||||||
|
}
|
||||||
|
}
|
@ -5,19 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
#include <ixwebsocket/IXCancellationRequest.h>
|
#include <ixwebsocket/IXCancellationRequest.h>
|
||||||
|
|
||||||
#if defined(__APPLE__) or defined(__linux__)
|
|
||||||
# ifdef __APPLE__
|
|
||||||
# include <ixwebsocket/IXSocketAppleSSL.h>
|
|
||||||
# else
|
|
||||||
# include <ixwebsocket/IXSocketOpenSSL.h>
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "IXTest.h"
|
#include "IXTest.h"
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
using namespace ix;
|
using namespace ix;
|
||||||
|
|
||||||
@ -39,16 +33,15 @@ namespace ix
|
|||||||
Logger() << "errMsg: " << errMsg;
|
Logger() << "errMsg: " << errMsg;
|
||||||
REQUIRE(success);
|
REQUIRE(success);
|
||||||
|
|
||||||
std::cout << "Sending request: " << request
|
Logger() << "Sending request: " << request
|
||||||
<< "to " << host << ":" << port
|
<< "to " << host << ":" << port;
|
||||||
<< std::endl;
|
|
||||||
REQUIRE(socket->writeBytes(request, isCancellationRequested));
|
REQUIRE(socket->writeBytes(request, isCancellationRequested));
|
||||||
|
|
||||||
auto lineResult = socket->readLine(isCancellationRequested);
|
auto lineResult = socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
auto line = lineResult.second;
|
auto line = lineResult.second;
|
||||||
|
|
||||||
std::cout << "read error: " << strerror(Socket::getErrno()) << std::endl;
|
Logger() << "read error: " << strerror(Socket::getErrno());
|
||||||
|
|
||||||
REQUIRE(lineValid);
|
REQUIRE(lineValid);
|
||||||
|
|
||||||
@ -62,10 +55,18 @@ TEST_CASE("socket", "[socket]")
|
|||||||
{
|
{
|
||||||
SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
|
SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
|
||||||
{
|
{
|
||||||
std::shared_ptr<Socket> socket(new Socket);
|
std::string errMsg;
|
||||||
|
bool tls = false;
|
||||||
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::string host("www.google.com");
|
std::string host("www.google.com");
|
||||||
int port = 80;
|
int port = 80;
|
||||||
std::string request("GET / HTTP/1.1\r\n\r\n");
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "GET / HTTP/1.1\r\n";
|
||||||
|
ss << "Host: " << host << "\r\n";
|
||||||
|
ss << "\r\n";
|
||||||
|
std::string request(ss.str());
|
||||||
|
|
||||||
int expectedStatus = 200;
|
int expectedStatus = 200;
|
||||||
int timeoutSecs = 3;
|
int timeoutSecs = 3;
|
||||||
|
|
||||||
@ -75,11 +76,9 @@ TEST_CASE("socket", "[socket]")
|
|||||||
#if defined(__APPLE__) or defined(__linux__)
|
#if defined(__APPLE__) or defined(__linux__)
|
||||||
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
|
SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
|
||||||
{
|
{
|
||||||
# ifdef __APPLE__
|
std::string errMsg;
|
||||||
std::shared_ptr<Socket> socket = std::make_shared<SocketAppleSSL>();
|
bool tls = true;
|
||||||
# else
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
std::shared_ptr<Socket> socket = std::make_shared<SocketOpenSSL>();
|
|
||||||
# endif
|
|
||||||
std::string host("www.google.com");
|
std::string host("www.google.com");
|
||||||
int port = 443;
|
int port = 443;
|
||||||
std::string request("GET / HTTP/1.1\r\n\r\n");
|
std::string request("GET / HTTP/1.1\r\n\r\n");
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
@ -69,10 +70,15 @@ namespace ix
|
|||||||
Logger() << msg;
|
Logger() << msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getAnyFreePortSimple()
|
||||||
|
{
|
||||||
|
static int defaultPort = 8090;
|
||||||
|
return defaultPort++;
|
||||||
|
}
|
||||||
|
|
||||||
int getAnyFreePort()
|
int getAnyFreePort()
|
||||||
{
|
{
|
||||||
int defaultPort = 8090;
|
int defaultPort = 8090;
|
||||||
|
|
||||||
int sockfd;
|
int sockfd;
|
||||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
{
|
{
|
||||||
@ -122,8 +128,15 @@ namespace ix
|
|||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
#if defined(__has_feature)
|
||||||
|
# if __has_feature(address_sanitizer)
|
||||||
|
int port = getAnyFreePortSimple();
|
||||||
|
# else
|
||||||
int port = getAnyFreePort();
|
int port = getAnyFreePort();
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
int port = getAnyFreePort();
|
||||||
|
#endif
|
||||||
//
|
//
|
||||||
// Only port above 1024 can be used by non root users, but for some
|
// Only port above 1024 can be used by non root users, but for some
|
||||||
// reason I got port 7 returned with macOS when binding on port 0...
|
// reason I got port 7 returned with macOS when binding on port 0...
|
||||||
@ -136,4 +149,21 @@ namespace ix
|
|||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void hexDump(const std::string& prefix,
|
||||||
|
const std::string& s)
|
||||||
|
{
|
||||||
|
std::ostringstream ss;
|
||||||
|
bool upper_case = false;
|
||||||
|
|
||||||
|
for (std::string::size_type i = 0; i < s.length(); ++i)
|
||||||
|
{
|
||||||
|
ss << std::hex
|
||||||
|
<< std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< (upper_case ? std::uppercase : std::nouppercase) << (int)s[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << prefix << ": " << s << " => " << ss.str() << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ namespace
|
|||||||
_webSocket.setUrl(url);
|
_webSocket.setUrl(url);
|
||||||
|
|
||||||
// The important bit for this test.
|
// The important bit for this test.
|
||||||
// Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second
|
// Set a 1 second heartbeat ; if no traffic is present on the connection for 1 second
|
||||||
// a ping message will be sent by the client.
|
// a ping message will be sent by the client.
|
||||||
_webSocket.setHeartBeatPeriod(1);
|
_webSocket.setHeartBeatPeriod(1);
|
||||||
|
|
||||||
@ -128,10 +128,11 @@ namespace
|
|||||||
{
|
{
|
||||||
// A dev/null server
|
// A dev/null server
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server, &receivedPingMessages](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server, &receivedPingMessages](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -141,6 +142,7 @@ namespace
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New server connection";
|
Logger() << "New server connection";
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
@ -210,6 +212,10 @@ TEST_CASE("Websocket_heartbeat", "[heartbeat]")
|
|||||||
webSocketClientA.stop();
|
webSocketClientA.stop();
|
||||||
webSocketClientB.stop();
|
webSocketClientB.stop();
|
||||||
|
|
||||||
|
|
||||||
|
// Here we test heart beat period exceeded for clientA
|
||||||
|
// but it should not be exceeded for clientB which has sent data.
|
||||||
|
// -> expected ping messages == 2, but add a small buffer to make this more reliable.
|
||||||
REQUIRE(serverReceivedPingMessages >= 2);
|
REQUIRE(serverReceivedPingMessages >= 2);
|
||||||
REQUIRE(serverReceivedPingMessages <= 4);
|
REQUIRE(serverReceivedPingMessages <= 4);
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
#include <ixwebsocket/IXWebSocket.h>
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
|
|
||||||
#include "IXTest.h"
|
#include "IXTest.h"
|
||||||
|
|
||||||
@ -17,13 +18,32 @@ using namespace ix;
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
bool startServer(ix::WebSocketServer& server)
|
// Test that we can override the connectionState impl to provide our own
|
||||||
|
class ConnectionStateCustom : public ConnectionState
|
||||||
{
|
{
|
||||||
|
void computeId()
|
||||||
|
{
|
||||||
|
// a very boring invariant id that we can test against in the unittest
|
||||||
|
_id = "foobarConnectionId";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool startServer(ix::WebSocketServer& server,
|
||||||
|
std::string& connectionId)
|
||||||
|
{
|
||||||
|
auto factory = []() -> std::shared_ptr<ConnectionState>
|
||||||
|
{
|
||||||
|
return std::make_shared<ConnectionStateCustom>();
|
||||||
|
};
|
||||||
|
server.setConnectionStateFactory(factory);
|
||||||
|
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server, &connectionId](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState,
|
||||||
|
&connectionId, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -33,12 +53,16 @@ namespace ix
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
|
connectionState->computeId();
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
{
|
{
|
||||||
Logger() << it.first << ": " << it.second;
|
Logger() << it.first << ": " << it.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionId = connectionState->getId();
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocket_MessageType_Close)
|
else if (messageType == ix::WebSocket_MessageType_Close)
|
||||||
{
|
{
|
||||||
@ -77,19 +101,21 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
Socket socket;
|
|
||||||
std::string host("localhost");
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
bool tls = false;
|
||||||
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
|
std::string host("localhost");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
bool success = socket.connect(host, port, errMsg, isCancellationRequested);
|
bool success = socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
REQUIRE(success);
|
REQUIRE(success);
|
||||||
|
|
||||||
auto lineResult = socket.readLine(isCancellationRequested);
|
auto lineResult = socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
auto line = lineResult.second;
|
auto line = lineResult.second;
|
||||||
|
|
||||||
@ -109,22 +135,24 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
Socket socket;
|
|
||||||
std::string host("localhost");
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
bool tls = false;
|
||||||
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
|
std::string host("localhost");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
bool success = socket.connect(host, port, errMsg, isCancellationRequested);
|
bool success = socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
REQUIRE(success);
|
REQUIRE(success);
|
||||||
|
|
||||||
Logger() << "writeBytes";
|
Logger() << "writeBytes";
|
||||||
socket.writeBytes("GET /\r\n", isCancellationRequested);
|
socket->writeBytes("GET /\r\n", isCancellationRequested);
|
||||||
|
|
||||||
auto lineResult = socket.readLine(isCancellationRequested);
|
auto lineResult = socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
auto line = lineResult.second;
|
auto line = lineResult.second;
|
||||||
|
|
||||||
@ -144,26 +172,28 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
{
|
{
|
||||||
int port = getFreePort();
|
int port = getFreePort();
|
||||||
ix::WebSocketServer server(port);
|
ix::WebSocketServer server(port);
|
||||||
REQUIRE(startServer(server));
|
std::string connectionId;
|
||||||
|
REQUIRE(startServer(server, connectionId));
|
||||||
|
|
||||||
Socket socket;
|
|
||||||
std::string host("localhost");
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
|
bool tls = false;
|
||||||
|
std::shared_ptr<Socket> socket = createSocket(tls, errMsg);
|
||||||
|
std::string host("localhost");
|
||||||
auto isCancellationRequested = []() -> bool
|
auto isCancellationRequested = []() -> bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
bool success = socket.connect(host, port, errMsg, isCancellationRequested);
|
bool success = socket->connect(host, port, errMsg, isCancellationRequested);
|
||||||
REQUIRE(success);
|
REQUIRE(success);
|
||||||
|
|
||||||
socket.writeBytes("GET / HTTP/1.1\r\n"
|
socket->writeBytes("GET / HTTP/1.1\r\n"
|
||||||
"Upgrade: websocket\r\n"
|
"Upgrade: websocket\r\n"
|
||||||
"Sec-WebSocket-Version: 13\r\n"
|
"Sec-WebSocket-Version: 13\r\n"
|
||||||
"Sec-WebSocket-Key: foobar\r\n"
|
"Sec-WebSocket-Key: foobar\r\n"
|
||||||
"\r\n",
|
"\r\n",
|
||||||
isCancellationRequested);
|
isCancellationRequested);
|
||||||
|
|
||||||
auto lineResult = socket.readLine(isCancellationRequested);
|
auto lineResult = socket->readLine(isCancellationRequested);
|
||||||
auto lineValid = lineResult.first;
|
auto lineValid = lineResult.first;
|
||||||
auto line = lineResult.second;
|
auto line = lineResult.second;
|
||||||
|
|
||||||
@ -174,6 +204,8 @@ TEST_CASE("Websocket_server", "[websocket_server]")
|
|||||||
// Give us 500ms for the server to notice that clients went away
|
// Give us 500ms for the server to notice that clients went away
|
||||||
ix::msleep(500);
|
ix::msleep(500);
|
||||||
|
|
||||||
|
REQUIRE(connectionId == "foobarConnectionId");
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
REQUIRE(server.getClients().size() == 0);
|
REQUIRE(server.getClients().size() == 0);
|
||||||
}
|
}
|
||||||
|
@ -164,10 +164,21 @@ namespace
|
|||||||
ss << "cmd_websocket_chat: Error ! " << error.reason;
|
ss << "cmd_websocket_chat: Error ! " << error.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Ping)
|
||||||
|
{
|
||||||
|
log("cmd_websocket_chat: received ping message");
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Pong)
|
||||||
|
{
|
||||||
|
log("cmd_websocket_chat: received pong message");
|
||||||
|
}
|
||||||
|
else if (messageType == ix::WebSocket_MessageType_Fragment)
|
||||||
|
{
|
||||||
|
log("cmd_websocket_chat: received message fragment");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// FIXME: missing ping/pong messages
|
ss << "Unexpected ix::WebSocketMessageType";
|
||||||
ss << "Invalid ix::WebSocketMessageType";
|
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -206,10 +217,11 @@ namespace
|
|||||||
bool startServer(ix::WebSocketServer& server)
|
bool startServer(ix::WebSocketServer& server)
|
||||||
{
|
{
|
||||||
server.setOnConnectionCallback(
|
server.setOnConnectionCallback(
|
||||||
[&server](std::shared_ptr<ix::WebSocket> webSocket)
|
[&server](std::shared_ptr<ix::WebSocket> webSocket,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
||||||
const std::string& str,
|
const std::string& str,
|
||||||
size_t wireSize,
|
size_t wireSize,
|
||||||
const ix::WebSocketErrorInfo& error,
|
const ix::WebSocketErrorInfo& error,
|
||||||
@ -219,6 +231,7 @@ namespace
|
|||||||
if (messageType == ix::WebSocket_MessageType_Open)
|
if (messageType == ix::WebSocket_MessageType_Open)
|
||||||
{
|
{
|
||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : openInfo.headers)
|
||||||
|
47
test/run.py
@ -2,14 +2,47 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
"""Run system commands with timeout
|
||||||
|
|
||||||
|
From http://www.bo-yang.net/2016/12/01/python-run-command-with-timeout
|
||||||
|
Python3 might have a builtin way to do that.
|
||||||
|
"""
|
||||||
|
def __init__(self, cmd):
|
||||||
|
self.cmd = cmd
|
||||||
|
self.process = None
|
||||||
|
|
||||||
|
def run_command(self, capture = False):
|
||||||
|
self.process = subprocess.Popen(self.cmd, shell=True)
|
||||||
|
self.process.communicate()
|
||||||
|
|
||||||
|
def run(self, timeout = 5 * 60):
|
||||||
|
'''5 minutes default timeout'''
|
||||||
|
thread = threading.Thread(target=self.run_command, args=())
|
||||||
|
thread.start()
|
||||||
|
thread.join(timeout)
|
||||||
|
|
||||||
|
if thread.is_alive():
|
||||||
|
print('Command timeout, kill it: ' + self.cmd)
|
||||||
|
self.process.terminate()
|
||||||
|
thread.join()
|
||||||
|
return False, 255
|
||||||
|
else:
|
||||||
|
return True, self.process.returncode
|
||||||
|
|
||||||
|
|
||||||
osName = platform.system()
|
osName = platform.system()
|
||||||
print('os name = {}'.format(osName))
|
print('os name = {}'.format(osName))
|
||||||
|
|
||||||
root = os.path.dirname(os.path.realpath(__file__))
|
root = os.path.dirname(os.path.realpath(__file__))
|
||||||
buildDir = os.path.join(root, 'build')
|
buildDir = os.path.join(root, 'build', osName)
|
||||||
|
|
||||||
if not os.path.exists(buildDir):
|
if not os.path.exists(buildDir):
|
||||||
os.mkdir(buildDir)
|
os.makedirs(buildDir)
|
||||||
|
|
||||||
os.chdir(buildDir)
|
os.chdir(buildDir)
|
||||||
|
|
||||||
@ -38,7 +71,7 @@ sanitizerFlags = sanitizersFlags[sanitizer]
|
|||||||
# os.environ['CC'] = 'clang-cl'
|
# os.environ['CC'] = 'clang-cl'
|
||||||
# os.environ['CXX'] = 'clang-cl'
|
# os.environ['CXX'] = 'clang-cl'
|
||||||
|
|
||||||
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ..'.format(generator, sanitizerFlags)
|
cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug {} {} ../..'.format(generator, sanitizerFlags)
|
||||||
print(cmakeCmd)
|
print(cmakeCmd)
|
||||||
ret = os.system(cmakeCmd)
|
ret = os.system(cmakeCmd)
|
||||||
assert ret == 0, 'CMake failed, exiting'
|
assert ret == 0, 'CMake failed, exiting'
|
||||||
@ -67,6 +100,7 @@ def findFiles(prefix):
|
|||||||
|
|
||||||
# We need to copy the zlib DLL in the current work directory
|
# We need to copy the zlib DLL in the current work directory
|
||||||
shutil.copy(os.path.join(
|
shutil.copy(os.path.join(
|
||||||
|
'..',
|
||||||
'..',
|
'..',
|
||||||
'..',
|
'..',
|
||||||
'third_party',
|
'third_party',
|
||||||
@ -77,6 +111,9 @@ shutil.copy(os.path.join(
|
|||||||
'bin',
|
'bin',
|
||||||
'zlib.dll'), '.')
|
'zlib.dll'), '.')
|
||||||
|
|
||||||
testCommand = '{} {}'.format(testBinary, os.getenv('TEST', ''))
|
# lldb = "lldb --batch -o 'run' -k 'thread backtrace all' -k 'quit 1'"
|
||||||
ret = os.system(testCommand)
|
lldb = "" # Disabled for now
|
||||||
|
testCommand = '{} {} {}'.format(lldb, testBinary, os.getenv('TEST', ''))
|
||||||
|
command = Command(testCommand)
|
||||||
|
timedout, ret = command.run()
|
||||||
assert ret == 0, 'Test command failed'
|
assert ret == 0, 'Test command failed'
|
||||||
|
@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
ix::Socket::init(); // for Windows
|
|
||||||
|
|
||||||
int result = Catch::Session().run(argc, argv);
|
int result = Catch::Session().run(argc, argv);
|
||||||
|
|
||||||
ix::Socket::cleanup(); // for Windows
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
1
third_party/README.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Except ZLIB on Windows (whose port is currently broken...) all dependencies here are for the ws command line tools, not for the IXWebSockets library which is standalone.
|
@ -1,2 +1,3 @@
|
|||||||
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
find . -type f -name '*.cpp' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
find . -type f -name '*.h' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
||||||
|
find . -type f -name '*.md' -exec sed -i '' 's/[[:space:]]*$//' {} \+
|
1
ws/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
build
|
build
|
||||||
|
node_modules
|
||||||
|
@ -23,6 +23,8 @@ add_executable(ws
|
|||||||
ixcrypto/IXHash.cpp
|
ixcrypto/IXHash.cpp
|
||||||
ixcrypto/IXUuid.cpp
|
ixcrypto/IXUuid.cpp
|
||||||
|
|
||||||
|
IXRedisClient.cpp
|
||||||
|
|
||||||
ws_http_client.cpp
|
ws_http_client.cpp
|
||||||
ws_ping_pong.cpp
|
ws_ping_pong.cpp
|
||||||
ws_broadcast_server.cpp
|
ws_broadcast_server.cpp
|
||||||
@ -32,11 +34,13 @@ add_executable(ws
|
|||||||
ws_transfer.cpp
|
ws_transfer.cpp
|
||||||
ws_send.cpp
|
ws_send.cpp
|
||||||
ws_receive.cpp
|
ws_receive.cpp
|
||||||
|
ws_redis_publish.cpp
|
||||||
|
ws_redis_subscribe.cpp
|
||||||
ws.cpp)
|
ws.cpp)
|
||||||
|
|
||||||
if (APPLE AND USE_TLS)
|
if (APPLE AND USE_TLS)
|
||||||
target_link_libraries(ws "-framework foundation" "-framework security")
|
target_link_libraries(ws "-framework foundation" "-framework security")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(ws ixwebsocket)
|
target_link_libraries(ws ixwebsocket cpp_redis tacopie)
|
||||||
install(TARGETS ws RUNTIME DESTINATION bin)
|
install(TARGETS ws RUNTIME DESTINATION bin)
|
||||||
|
242
ws/IXRedisClient.cpp
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* IXRedisClient.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXRedisClient.h"
|
||||||
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <cpp_redis/cpp_redis>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
bool RedisClient::connect(const std::string& hostname, int port)
|
||||||
|
{
|
||||||
|
_sub.connect(hostname, port, []
|
||||||
|
(const std::string& host, std::size_t port, cpp_redis::connect_state status) {
|
||||||
|
if (status == cpp_redis::connect_state::dropped) {
|
||||||
|
std::cout << "client disconnected from " << host << ":" << port << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// also subscribe the old way
|
||||||
|
bool tls = false;
|
||||||
|
std::string errorMsg;
|
||||||
|
_socket = createSocket(tls, errorMsg);
|
||||||
|
|
||||||
|
if (!_socket)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string errMsg;
|
||||||
|
return _socket->connect(hostname, port, errMsg, nullptr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RedisClient::auth(const std::string& password,
|
||||||
|
std::string& response)
|
||||||
|
{
|
||||||
|
// authentication if server-server requires it
|
||||||
|
// _sub.auth(password, [&response](const cpp_redis::reply& reply) {
|
||||||
|
// if (reply.is_error()) { std::cerr << "Authentication failed: " << reply.as_string() << std::endl; }
|
||||||
|
// else {
|
||||||
|
// std::cout << "successful authentication" << std::endl;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#if 0
|
||||||
|
response.clear();
|
||||||
|
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "AUTH ";
|
||||||
|
ss << password;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
response = line;
|
||||||
|
return lineValid;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool RedisClient::publish(const std::string& channel,
|
||||||
|
const std::string& message)
|
||||||
|
{
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "PUBLISH ";
|
||||||
|
ss << channel;
|
||||||
|
ss << " ";
|
||||||
|
ss << message;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
return lineValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// FIXME: we assume that redis never return errors...
|
||||||
|
//
|
||||||
|
bool RedisClient::subscribe(const std::string& channel,
|
||||||
|
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||||
|
const OnRedisSubscribeCallback& callback)
|
||||||
|
{
|
||||||
|
_sub.subscribe(channel, [&callback](const std::string& chan, const std::string& msg) {
|
||||||
|
callback(msg);
|
||||||
|
});
|
||||||
|
_sub.commit();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
auto duration = std::chrono::seconds(1);
|
||||||
|
std::this_thread::sleep_for(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#if 0
|
||||||
|
if (!_socket) return false;
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "SUBSCRIBE ";
|
||||||
|
ss << channel;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
bool sent = _socket->writeBytes(ss.str(), nullptr);
|
||||||
|
if (!sent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait 1s for the response
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the response as a single string
|
||||||
|
std::stringstream oss;
|
||||||
|
|
||||||
|
// Read the first line of the response
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
oss << line;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
// There are 5 items for the subscribe repply
|
||||||
|
for (int i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
oss << line;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseCallback(oss.str());
|
||||||
|
|
||||||
|
// Wait indefinitely for new messages
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Wait until something is ready to read
|
||||||
|
auto pollResult = _socket->isReadyToRead(-1);
|
||||||
|
if (pollResult == PollResultType::Error)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first line of the response describe the return type,
|
||||||
|
// => *3 (an array of 3 elements)
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
int arraySize;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << line.substr(1, line.size()-1);
|
||||||
|
ss >> arraySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are 6 items for each received message
|
||||||
|
for (int i = 0; i < arraySize; ++i)
|
||||||
|
{
|
||||||
|
auto lineResult = _socket->readLine(nullptr);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
if (!lineValid) return false;
|
||||||
|
|
||||||
|
// Messages are string, which start with a string size
|
||||||
|
// => $7 (7 bytes)
|
||||||
|
int stringSize;
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << line.substr(1, line.size()-1);
|
||||||
|
ss >> stringSize;
|
||||||
|
|
||||||
|
auto readResult = _socket->readBytes(stringSize, nullptr, nullptr);
|
||||||
|
if (!readResult.first) return false;
|
||||||
|
|
||||||
|
if (i == 2)
|
||||||
|
{
|
||||||
|
// The message is the 3rd element.
|
||||||
|
callback(readResult.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read last 2 bytes (\r\n)
|
||||||
|
char c;
|
||||||
|
_socket->readByte(&c, nullptr);
|
||||||
|
_socket->readByte(&c, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
44
ws/IXRedisClient.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* IXRedisClient.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <cpp_redis/cpp_redis>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class Socket;
|
||||||
|
|
||||||
|
class RedisClient {
|
||||||
|
public:
|
||||||
|
using OnRedisSubscribeResponseCallback = std::function<void(const std::string&)>;
|
||||||
|
using OnRedisSubscribeCallback = std::function<void(const std::string&)>;
|
||||||
|
|
||||||
|
RedisClient() = default;
|
||||||
|
~RedisClient() = default;
|
||||||
|
|
||||||
|
bool connect(const std::string& hostname,
|
||||||
|
int port);
|
||||||
|
|
||||||
|
bool auth(const std::string& password,
|
||||||
|
std::string& response);
|
||||||
|
|
||||||
|
bool publish(const std::string& channel,
|
||||||
|
const std::string& message);
|
||||||
|
|
||||||
|
bool subscribe(const std::string& channel,
|
||||||
|
const OnRedisSubscribeResponseCallback& responseCallback,
|
||||||
|
const OnRedisSubscribeCallback& callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
cpp_redis::subscriber _sub;
|
||||||
|
|
||||||
|
std::shared_ptr<Socket> _socket;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
60
ws/README.md
@ -1,10 +1,64 @@
|
|||||||
|
# General
|
||||||
|
|
||||||
|
ws is a command line tool that should exercise most of the IXWebSocket code, and provide example code.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ws --help
|
||||||
|
ws is a websocket tool
|
||||||
|
Usage: ws [OPTIONS] SUBCOMMAND
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h,--help Print this help message and exit
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
send Send a file
|
||||||
|
receive Receive a file
|
||||||
|
transfer Broadcasting server
|
||||||
|
connect Connect to a remote server
|
||||||
|
chat Group chat
|
||||||
|
echo_server Echo server
|
||||||
|
broadcast_server Broadcasting server
|
||||||
|
ping Ping pong
|
||||||
|
curl HTTP Client
|
||||||
|
redis_publish Redis publisher
|
||||||
|
redis_subscribe Redis subscriber
|
||||||
|
```
|
||||||
|
|
||||||
|
## file transfer
|
||||||
|
|
||||||
```
|
```
|
||||||
# Start transfer server, which is just a broadcast server at this point
|
# Start transfer server, which is just a broadcast server at this point
|
||||||
./ws transfer # running on port 8080.
|
ws transfer # running on port 8080.
|
||||||
|
|
||||||
# Start receiver first
|
# Start receiver first
|
||||||
./ws receive ws://localhost:8080
|
ws receive ws://localhost:8080
|
||||||
|
|
||||||
# Then send a file. File will be received and written to disk by the receiver process
|
# Then send a file. File will be received and written to disk by the receiver process
|
||||||
./ws send ws://localhost:8080 /file/to/path
|
ws send ws://localhost:8080 /file/to/path
|
||||||
|
```
|
||||||
|
|
||||||
|
## curl
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ws curl --help
|
||||||
|
HTTP Client
|
||||||
|
Usage: ws curl [OPTIONS] url
|
||||||
|
|
||||||
|
Positionals:
|
||||||
|
url TEXT REQUIRED Connection url
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h,--help Print this help message and exit
|
||||||
|
-d TEXT Form data
|
||||||
|
-F TEXT Form data
|
||||||
|
-H TEXT Header
|
||||||
|
--output TEXT Output file
|
||||||
|
-I Send a HEAD request
|
||||||
|
-L Follow redirects
|
||||||
|
--max-redirects INT Max Redirects
|
||||||
|
-v Verbose
|
||||||
|
-O Save output to disk
|
||||||
|
--compress Enable gzip compression
|
||||||
|
--connect-timeout INT Connection timeout
|
||||||
|
--transfer-timeout INT Transfer timeout
|
||||||
```
|
```
|
||||||
|
@ -13,6 +13,7 @@ g++ --std=c++14 \
|
|||||||
../ixwebsocket/IXSocket.cpp \
|
../ixwebsocket/IXSocket.cpp \
|
||||||
../ixwebsocket/IXSocketServer.cpp \
|
../ixwebsocket/IXSocketServer.cpp \
|
||||||
../ixwebsocket/IXSocketConnect.cpp \
|
../ixwebsocket/IXSocketConnect.cpp \
|
||||||
|
../ixwebsocket/IXSocketFactory.cpp \
|
||||||
../ixwebsocket/IXDNSLookup.cpp \
|
../ixwebsocket/IXDNSLookup.cpp \
|
||||||
../ixwebsocket/IXCancellationRequest.cpp \
|
../ixwebsocket/IXCancellationRequest.cpp \
|
||||||
../ixwebsocket/IXWebSocket.cpp \
|
../ixwebsocket/IXWebSocket.cpp \
|
||||||
@ -22,12 +23,16 @@ g++ --std=c++14 \
|
|||||||
../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \
|
../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \
|
||||||
../ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp \
|
../ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp \
|
||||||
../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \
|
../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \
|
||||||
|
../ixwebsocket/IXWebSocketHttpHeaders.cpp \
|
||||||
|
../ixwebsocket/IXHttpClient.cpp \
|
||||||
|
../ixwebsocket/IXUrlParser.cpp \
|
||||||
../ixwebsocket/IXSocketOpenSSL.cpp \
|
../ixwebsocket/IXSocketOpenSSL.cpp \
|
||||||
../ixwebsocket/linux/IXSetThreadName_linux.cpp \
|
../ixwebsocket/linux/IXSetThreadName_linux.cpp \
|
||||||
../third_party/jsoncpp/jsoncpp.cpp \
|
../third_party/msgpack11/msgpack11.cpp \
|
||||||
ixcrypto/IXBase64.cpp \
|
ixcrypto/IXBase64.cpp \
|
||||||
ixcrypto/IXHash.cpp \
|
ixcrypto/IXHash.cpp \
|
||||||
ixcrypto/IXUuid.cpp \
|
ixcrypto/IXUuid.cpp \
|
||||||
|
ws_http_client.cpp \
|
||||||
ws_ping_pong.cpp \
|
ws_ping_pong.cpp \
|
||||||
ws_broadcast_server.cpp \
|
ws_broadcast_server.cpp \
|
||||||
ws_echo_server.cpp \
|
ws_echo_server.cpp \
|
||||||
|
19
ws/package-lock.json
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"async-limiter": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w==",
|
||||||
|
"requires": {
|
||||||
|
"async-limiter": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
ws/test_ws.sh
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Handle Ctrl-C by killing all sub-processing AND exiting
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
kill `cat /tmp/ws_test/pidfile.transfer`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.receive`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.send`
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rm -rf /tmp/ws_test
|
||||||
|
mkdir -p /tmp/ws_test
|
||||||
|
|
||||||
|
# Start a transport server
|
||||||
|
cd /tmp/ws_test
|
||||||
|
ws transfer --port 8090 --pidfile /tmp/ws_test/pidfile.transfer &
|
||||||
|
|
||||||
|
# Wait until the transfer server is up
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
nc -zv 127.0.0.1 8090 && {
|
||||||
|
echo "Transfer server up and running"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
echo "sleep ... wait for transfer server"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Start a receiver
|
||||||
|
mkdir -p /tmp/ws_test/receive
|
||||||
|
cd /tmp/ws_test/receive
|
||||||
|
ws receive --delay 10 ws://127.0.0.1:8090 --pidfile /tmp/ws_test/pidfile.receive &
|
||||||
|
|
||||||
|
mkdir /tmp/ws_test/send
|
||||||
|
cd /tmp/ws_test/send
|
||||||
|
dd if=/dev/urandom of=20M_file count=20000 bs=1024
|
||||||
|
|
||||||
|
# Start the sender job
|
||||||
|
ws send --pidfile /tmp/ws_test/pidfile.send ws://127.0.0.1:8090 20M_file
|
||||||
|
|
||||||
|
# Wait until the file has been written to disk
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
if test -f /tmp/ws_test/receive/20M_file ; then
|
||||||
|
echo "Received file does exists, exiting loop"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "sleep ... wait for output file"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
cksum /tmp/ws_test/send/20M_file
|
||||||
|
cksum /tmp/ws_test/receive/20M_file
|
||||||
|
|
||||||
|
# Give some time to ws receive to terminate
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
kill `cat /tmp/ws_test/pidfile.transfer`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.receive`
|
||||||
|
kill `cat /tmp/ws_test/pidfile.send`
|
||||||
|
|
25
ws/test_ws_redis.sh
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Handle Ctrl-C by killing all sub-processing AND exiting
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
kill `cat /tmp/pidfile.subscribe`
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIS_HOST=${REDIS_HOST:=localhost}
|
||||||
|
|
||||||
|
ws redis_subscribe --pidfile /tmp/pidfile.subscribe --host $REDIS_HOST foo &
|
||||||
|
|
||||||
|
# Wait for the subscriber to be ready
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
# Now publish messages
|
||||||
|
ws redis_publish -c 100000 --host ${REDIS_HOST} foo bar
|
||||||
|
|
||||||
|
# Wait a little for all messages to be received
|
||||||
|
sleep 1.5
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
cleanup
|
67
ws/ws.cpp
@ -16,6 +16,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <cli11/CLI11.hpp>
|
#include <cli11/CLI11.hpp>
|
||||||
#include <ixwebsocket/IXSocket.h>
|
#include <ixwebsocket/IXSocket.h>
|
||||||
@ -31,25 +33,40 @@ int main(int argc, char** argv)
|
|||||||
std::string data;
|
std::string data;
|
||||||
std::string headers;
|
std::string headers;
|
||||||
std::string output;
|
std::string output;
|
||||||
|
std::string hostname("127.0.0.1");
|
||||||
|
std::string pidfile;
|
||||||
|
std::string channel;
|
||||||
|
std::string message;
|
||||||
|
std::string password;
|
||||||
bool headersOnly = false;
|
bool headersOnly = false;
|
||||||
bool followRedirects = false;
|
bool followRedirects = false;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
bool save = false;
|
bool save = false;
|
||||||
bool compress = false;
|
bool compress = false;
|
||||||
int port = 8080;
|
int port = 8080;
|
||||||
|
int redisPort = 6379;
|
||||||
int connectTimeOut = 60;
|
int connectTimeOut = 60;
|
||||||
int transferTimeout = 1800;
|
int transferTimeout = 1800;
|
||||||
int maxRedirects = 5;
|
int maxRedirects = 5;
|
||||||
|
int delayMs = -1;
|
||||||
|
int count = 1;
|
||||||
|
|
||||||
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
CLI::App* sendApp = app.add_subcommand("send", "Send a file");
|
||||||
sendApp->add_option("url", url, "Connection url")->required();
|
sendApp->add_option("url", url, "Connection url")->required();
|
||||||
sendApp->add_option("path", path, "Path to the file to send")
|
sendApp->add_option("path", path, "Path to the file to send")
|
||||||
->required()->check(CLI::ExistingPath);
|
->required()->check(CLI::ExistingPath);
|
||||||
|
sendApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file");
|
CLI::App* receiveApp = app.add_subcommand("receive", "Receive a file");
|
||||||
receiveApp->add_option("url", url, "Connection url")->required();
|
receiveApp->add_option("url", url, "Connection url")->required();
|
||||||
|
receiveApp->add_option("--delay", delayMs, "Delay (ms) to wait after receiving a fragment"
|
||||||
|
" to artificially slow down the receiver");
|
||||||
|
receiveApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server");
|
CLI::App* transferApp = app.add_subcommand("transfer", "Broadcasting server");
|
||||||
transferApp->add_option("--port", port, "Connection url");
|
transferApp->add_option("--port", port, "Connection url");
|
||||||
|
transferApp->add_option("--host", hostname, "Hostname");
|
||||||
|
transferApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
|
CLI::App* connectApp = app.add_subcommand("connect", "Connect to a remote server");
|
||||||
connectApp->add_option("url", url, "Connection url")->required();
|
connectApp->add_option("url", url, "Connection url")->required();
|
||||||
@ -59,10 +76,12 @@ int main(int argc, char** argv)
|
|||||||
chatApp->add_option("user", user, "User name")->required();
|
chatApp->add_option("user", user, "User name")->required();
|
||||||
|
|
||||||
CLI::App* echoServerApp = app.add_subcommand("echo_server", "Echo server");
|
CLI::App* echoServerApp = app.add_subcommand("echo_server", "Echo server");
|
||||||
echoServerApp->add_option("--port", port, "Connection url");
|
echoServerApp->add_option("--port", port, "Port");
|
||||||
|
echoServerApp->add_option("--host", hostname, "Hostname");
|
||||||
|
|
||||||
CLI::App* broadcastServerApp = app.add_subcommand("broadcast_server", "Broadcasting server");
|
CLI::App* broadcastServerApp = app.add_subcommand("broadcast_server", "Broadcasting server");
|
||||||
broadcastServerApp->add_option("--port", port, "Connection url");
|
broadcastServerApp->add_option("--port", port, "Port");
|
||||||
|
broadcastServerApp->add_option("--host", hostname, "Hostname");
|
||||||
|
|
||||||
CLI::App* pingPongApp = app.add_subcommand("ping", "Ping pong");
|
CLI::App* pingPongApp = app.add_subcommand("ping", "Ping pong");
|
||||||
pingPongApp->add_option("url", url, "Connection url")->required();
|
pingPongApp->add_option("url", url, "Connection url")->required();
|
||||||
@ -82,13 +101,38 @@ int main(int argc, char** argv)
|
|||||||
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
httpClientApp->add_option("--connect-timeout", connectTimeOut, "Connection timeout");
|
||||||
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
httpClientApp->add_option("--transfer-timeout", transferTimeout, "Transfer timeout");
|
||||||
|
|
||||||
|
CLI::App* redisPublishApp = app.add_subcommand("redis_publish", "Redis publisher");
|
||||||
|
redisPublishApp->add_option("--port", redisPort, "Port");
|
||||||
|
redisPublishApp->add_option("--host", hostname, "Hostname");
|
||||||
|
redisPublishApp->add_option("--password", password, "Password");
|
||||||
|
redisPublishApp->add_option("channel", channel, "Channel")->required();
|
||||||
|
redisPublishApp->add_option("message", message, "Message")->required();
|
||||||
|
redisPublishApp->add_option("-c", count, "Count");
|
||||||
|
|
||||||
|
CLI::App* redisSubscribeApp = app.add_subcommand("redis_subscribe", "Redis subscriber");
|
||||||
|
redisSubscribeApp->add_option("--port", redisPort, "Port");
|
||||||
|
redisSubscribeApp->add_option("--host", hostname, "Hostname");
|
||||||
|
redisSubscribeApp->add_option("--password", password, "Password");
|
||||||
|
redisSubscribeApp->add_option("channel", channel, "Channel")->required();
|
||||||
|
redisSubscribeApp->add_flag("-v", verbose, "Verbose");
|
||||||
|
redisSubscribeApp->add_option("--pidfile", pidfile, "Pid file");
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
ix::Socket::init();
|
// pid file handling
|
||||||
|
if (!pidfile.empty())
|
||||||
|
{
|
||||||
|
unlink(pidfile.c_str());
|
||||||
|
|
||||||
|
std::ofstream f;
|
||||||
|
f.open(pidfile);
|
||||||
|
f << getpid();
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (app.got_subcommand("transfer"))
|
if (app.got_subcommand("transfer"))
|
||||||
{
|
{
|
||||||
return ix::ws_transfer_main(port);
|
return ix::ws_transfer_main(port, hostname);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("send"))
|
else if (app.got_subcommand("send"))
|
||||||
{
|
{
|
||||||
@ -97,7 +141,7 @@ int main(int argc, char** argv)
|
|||||||
else if (app.got_subcommand("receive"))
|
else if (app.got_subcommand("receive"))
|
||||||
{
|
{
|
||||||
bool enablePerMessageDeflate = false;
|
bool enablePerMessageDeflate = false;
|
||||||
return ix::ws_receive_main(url, enablePerMessageDeflate);
|
return ix::ws_receive_main(url, enablePerMessageDeflate, delayMs);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("connect"))
|
else if (app.got_subcommand("connect"))
|
||||||
{
|
{
|
||||||
@ -109,11 +153,11 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
else if (app.got_subcommand("echo_server"))
|
else if (app.got_subcommand("echo_server"))
|
||||||
{
|
{
|
||||||
return ix::ws_echo_server_main(port);
|
return ix::ws_echo_server_main(port, hostname);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("broadcast_server"))
|
else if (app.got_subcommand("broadcast_server"))
|
||||||
{
|
{
|
||||||
return ix::ws_broadcast_server_main(port);
|
return ix::ws_broadcast_server_main(port, hostname);
|
||||||
}
|
}
|
||||||
else if (app.got_subcommand("ping"))
|
else if (app.got_subcommand("ping"))
|
||||||
{
|
{
|
||||||
@ -126,6 +170,15 @@ int main(int argc, char** argv)
|
|||||||
followRedirects, maxRedirects, verbose,
|
followRedirects, maxRedirects, verbose,
|
||||||
save, output, compress);
|
save, output, compress);
|
||||||
}
|
}
|
||||||
|
else if (app.got_subcommand("redis_publish"))
|
||||||
|
{
|
||||||
|
return ix::ws_redis_publish_main(hostname, redisPort, password,
|
||||||
|
channel, message, count);
|
||||||
|
}
|
||||||
|
else if (app.got_subcommand("redis_subscribe"))
|
||||||
|
{
|
||||||
|
return ix::ws_redis_subscribe_main(hostname, redisPort, password, channel, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|