Compare commits
22 Commits
v4.0.0
...
feature/ht
Author | SHA1 | Date | |
---|---|---|---|
fcfb7c1739 | |||
92c2a7756d | |||
f458cd297e | |||
19a959dc31 | |||
cfc1b8f1c2 | |||
22b8fc831c | |||
15fa328a65 | |||
74cc6b815a | |||
44f37a4140 | |||
43deaba547 | |||
2d02ae0f0c | |||
a8879da4fc | |||
5f4a430845 | |||
b9231be305 | |||
7cb5cc05e4 | |||
750a752ac0 | |||
61e5f52286 | |||
ce0b716f54 | |||
aae8e5ec65 | |||
2723e8466e | |||
f13c610352 | |||
55c65b08bf |
44
.travis.yml
44
.travis.yml
@ -12,30 +12,30 @@ matrix:
|
|||||||
- python test/run.py
|
- python test/run.py
|
||||||
- make ws
|
- make ws
|
||||||
|
|
||||||
# # Linux
|
# Linux
|
||||||
# - os: linux
|
|
||||||
# dist: xenial
|
|
||||||
# script:
|
|
||||||
# - python test/run.py
|
|
||||||
# - make ws
|
|
||||||
# env:
|
|
||||||
# - CC=gcc
|
|
||||||
# - CXX=g++
|
|
||||||
|
|
||||||
# Clang + Linux disabled for now
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: xenial
|
dist: xenial
|
||||||
script: python test/run.py
|
script:
|
||||||
|
- python test/run.py
|
||||||
|
- make ws
|
||||||
env:
|
env:
|
||||||
- CC=clang
|
- CC=gcc
|
||||||
- CXX=clang++
|
- CXX=g++
|
||||||
|
|
||||||
|
# Clang + Linux disabled for now
|
||||||
|
# - os: linux
|
||||||
|
# dist: xenial
|
||||||
|
# script: python test/run.py
|
||||||
|
# env:
|
||||||
|
# - CC=clang
|
||||||
|
# - CXX=clang++
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
- os: windows
|
# - os: windows
|
||||||
env:
|
# env:
|
||||||
- CMAKE_PATH="/c/Program Files/CMake/bin"
|
# - CMAKE_PATH="/c/Program Files/CMake/bin"
|
||||||
script:
|
# script:
|
||||||
- export PATH=$CMAKE_PATH:$PATH
|
# - export PATH=$CMAKE_PATH:$PATH
|
||||||
- cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .
|
# # - cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .
|
||||||
- cmake --build --parallel .
|
# # - cmake --build --parallel .
|
||||||
- python test/run.py
|
# - python test/run.py
|
||||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,11 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased] - 2019-06-xx
|
## [5.0.0] - 2019-06-23
|
||||||
### Changed
|
### Changed
|
||||||
|
- New HTTP server / still very early. ws gained a new command, httpd can run a simple webserver serving local files.
|
||||||
|
- IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved
|
||||||
|
- cobra_to_sentry / backtraces are reversed and line number is not extracted correctly
|
||||||
|
- mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found
|
||||||
|
- travis CI uses g++ on Linux
|
||||||
|
|
||||||
|
## [4.0.0] - 2019-06-09
|
||||||
|
### Changed
|
||||||
|
- WebSocket::send() sends message in TEXT mode by default
|
||||||
|
- WebSocketMessage sets a new binary field, which tells whether the received incoming message is binary or text
|
||||||
- WebSocket::send takes a third arg, binary which default to true (can be text too)
|
- WebSocket::send takes a third arg, binary which default to true (can be text too)
|
||||||
- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg
|
- WebSocket callback only take one object, a const ix::WebSocketMessagePtr& msg
|
||||||
- Add explicite WebSocket::sendBinary
|
- Add explicit WebSocket::sendBinary method
|
||||||
- New headers + WebSocketMessage class to hold message data, still not used across the board
|
- New headers + WebSocketMessage class to hold message data, still not used across the board
|
||||||
- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility.
|
- Add test/compatibility folder with small servers and clients written in different languages and different libraries to test compatibility.
|
||||||
- ws echo_server has a -g option to print a greeting message on connect
|
- ws echo_server has a -g option to print a greeting message on connect
|
||||||
|
13
CMake/FindMbedTLS.cmake
Normal file
13
CMake/FindMbedTLS.cmake
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h)
|
||||||
|
|
||||||
|
find_library(MBEDTLS_LIBRARY mbedtls)
|
||||||
|
find_library(MBEDX509_LIBRARY mbedx509)
|
||||||
|
find_library(MBEDCRYPTO_LIBRARY mbedcrypto)
|
||||||
|
|
||||||
|
set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}")
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(MBEDTLS DEFAULT_MSG
|
||||||
|
MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
||||||
|
|
||||||
|
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY)
|
102
CMakeLists.txt
102
CMakeLists.txt
@ -4,6 +4,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.4.1)
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
|
||||||
|
|
||||||
project(ixwebsocket C CXX)
|
project(ixwebsocket C CXX)
|
||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
@ -20,64 +22,68 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_SOURCES
|
set( IXWEBSOCKET_SOURCES
|
||||||
|
ixwebsocket/IXCancellationRequest.cpp
|
||||||
|
ixwebsocket/IXConnectionState.cpp
|
||||||
|
ixwebsocket/IXDNSLookup.cpp
|
||||||
|
ixwebsocket/IXHttp.cpp
|
||||||
|
ixwebsocket/IXHttpClient.cpp
|
||||||
|
ixwebsocket/IXHttpServer.cpp
|
||||||
|
ixwebsocket/IXNetSystem.cpp
|
||||||
|
ixwebsocket/IXSelectInterrupt.cpp
|
||||||
|
ixwebsocket/IXSelectInterruptFactory.cpp
|
||||||
ixwebsocket/IXSocket.cpp
|
ixwebsocket/IXSocket.cpp
|
||||||
ixwebsocket/IXSocketServer.cpp
|
|
||||||
ixwebsocket/IXSocketConnect.cpp
|
ixwebsocket/IXSocketConnect.cpp
|
||||||
ixwebsocket/IXSocketFactory.cpp
|
ixwebsocket/IXSocketFactory.cpp
|
||||||
ixwebsocket/IXDNSLookup.cpp
|
ixwebsocket/IXSocketServer.cpp
|
||||||
ixwebsocket/IXCancellationRequest.cpp
|
ixwebsocket/IXUrlParser.cpp
|
||||||
ixwebsocket/IXNetSystem.cpp
|
|
||||||
ixwebsocket/IXWebSocket.cpp
|
ixwebsocket/IXWebSocket.cpp
|
||||||
ixwebsocket/IXWebSocketServer.cpp
|
ixwebsocket/IXWebSocketCloseConstants.cpp
|
||||||
ixwebsocket/IXWebSocketTransport.cpp
|
|
||||||
ixwebsocket/IXWebSocketHandshake.cpp
|
ixwebsocket/IXWebSocketHandshake.cpp
|
||||||
|
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.cpp
|
ixwebsocket/IXWebSocketServer.cpp
|
||||||
ixwebsocket/IXHttpClient.cpp
|
ixwebsocket/IXWebSocketTransport.cpp
|
||||||
ixwebsocket/IXUrlParser.cpp
|
|
||||||
ixwebsocket/LUrlParser.cpp
|
ixwebsocket/LUrlParser.cpp
|
||||||
ixwebsocket/IXSelectInterrupt.cpp
|
|
||||||
ixwebsocket/IXSelectInterruptFactory.cpp
|
|
||||||
ixwebsocket/IXConnectionState.cpp
|
|
||||||
ixwebsocket/IXWebSocketCloseConstants.cpp
|
|
||||||
ixwebsocket/IXWebSocketMessageQueue.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set( IXWEBSOCKET_HEADERS
|
set( IXWEBSOCKET_HEADERS
|
||||||
ixwebsocket/IXSocket.h
|
|
||||||
ixwebsocket/IXSocketServer.h
|
|
||||||
ixwebsocket/IXSocketConnect.h
|
|
||||||
ixwebsocket/IXSocketFactory.h
|
|
||||||
ixwebsocket/IXSetThreadName.h
|
|
||||||
ixwebsocket/IXDNSLookup.h
|
|
||||||
ixwebsocket/IXCancellationRequest.h
|
ixwebsocket/IXCancellationRequest.h
|
||||||
|
ixwebsocket/IXConnectionState.h
|
||||||
|
ixwebsocket/IXDNSLookup.h
|
||||||
|
ixwebsocket/IXHttp.h
|
||||||
|
ixwebsocket/IXHttpClient.h
|
||||||
|
ixwebsocket/IXHttpServer.h
|
||||||
ixwebsocket/IXNetSystem.h
|
ixwebsocket/IXNetSystem.h
|
||||||
ixwebsocket/IXProgressCallback.h
|
ixwebsocket/IXProgressCallback.h
|
||||||
|
ixwebsocket/IXSelectInterrupt.h
|
||||||
|
ixwebsocket/IXSelectInterruptFactory.h
|
||||||
|
ixwebsocket/IXSetThreadName.h
|
||||||
|
ixwebsocket/IXSocket.h
|
||||||
|
ixwebsocket/IXSocketConnect.h
|
||||||
|
ixwebsocket/IXSocketFactory.h
|
||||||
|
ixwebsocket/IXSocketServer.h
|
||||||
|
ixwebsocket/IXUrlParser.h
|
||||||
ixwebsocket/IXWebSocket.h
|
ixwebsocket/IXWebSocket.h
|
||||||
ixwebsocket/IXWebSocketServer.h
|
ixwebsocket/IXWebSocketCloseConstants.h
|
||||||
ixwebsocket/IXWebSocketTransport.h
|
|
||||||
ixwebsocket/IXWebSocketHandshake.h
|
|
||||||
ixwebsocket/IXWebSocketSendInfo.h
|
|
||||||
ixwebsocket/IXWebSocketErrorInfo.h
|
|
||||||
ixwebsocket/IXWebSocketCloseInfo.h
|
ixwebsocket/IXWebSocketCloseInfo.h
|
||||||
ixwebsocket/IXWebSocketOpenInfo.h
|
ixwebsocket/IXWebSocketErrorInfo.h
|
||||||
ixwebsocket/IXWebSocketMessageType.h
|
ixwebsocket/IXWebSocketHandshake.h
|
||||||
|
ixwebsocket/IXWebSocketHttpHeaders.h
|
||||||
ixwebsocket/IXWebSocketMessage.h
|
ixwebsocket/IXWebSocketMessage.h
|
||||||
|
ixwebsocket/IXWebSocketMessageQueue.h
|
||||||
|
ixwebsocket/IXWebSocketMessageType.h
|
||||||
|
ixwebsocket/IXWebSocketOpenInfo.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
ixwebsocket/IXWebSocketPerMessageDeflate.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
|
||||||
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
|
||||||
ixwebsocket/IXWebSocketHttpHeaders.h
|
ixwebsocket/IXWebSocketSendInfo.h
|
||||||
ixwebsocket/libwshandshake.hpp
|
ixwebsocket/IXWebSocketServer.h
|
||||||
ixwebsocket/IXHttpClient.h
|
ixwebsocket/IXWebSocketTransport.h
|
||||||
ixwebsocket/IXUrlParser.h
|
|
||||||
ixwebsocket/LUrlParser.h
|
ixwebsocket/LUrlParser.h
|
||||||
ixwebsocket/IXSelectInterrupt.h
|
ixwebsocket/libwshandshake.hpp
|
||||||
ixwebsocket/IXSelectInterruptFactory.h
|
|
||||||
ixwebsocket/IXConnectionState.h
|
|
||||||
ixwebsocket/IXWebSocketCloseConstants.h
|
|
||||||
ixwebsocket/IXWebSocketMessageQueue.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
@ -132,6 +138,11 @@ if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
|
|||||||
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (UNIX)
|
||||||
|
find_package(Threads)
|
||||||
|
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
endif()
|
||||||
|
|
||||||
if (USE_OPEN_SSL)
|
if (USE_OPEN_SSL)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL REQUIRED)
|
||||||
add_definitions(${OPENSSL_DEFINITIONS})
|
add_definitions(${OPENSSL_DEFINITIONS})
|
||||||
@ -141,25 +152,28 @@ if (USE_OPEN_SSL)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_MBED_TLS)
|
if (USE_MBED_TLS)
|
||||||
|
if (USE_VENDORED_THIRD_PARTY)
|
||||||
set (ENABLE_PROGRAMS OFF)
|
set (ENABLE_PROGRAMS OFF)
|
||||||
add_subdirectory(third_party/mbedtls)
|
add_subdirectory(third_party/mbedtls)
|
||||||
include_directories(third_party/mbedtls/include)
|
include_directories(third_party/mbedtls/include)
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket mbedtls)
|
target_link_libraries(ixwebsocket mbedtls)
|
||||||
|
else()
|
||||||
|
find_package(MbedTLS REQUIRED)
|
||||||
|
include_directories(${MBEDTLS_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(ixwebsocket ${MBEDTLS_LIBRARIES})
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
find_package(ZLIB REQUIRED)
|
||||||
|
if (ZLIB_FOUND)
|
||||||
|
include_directories(${ZLIB_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(ixwebsocket ${ZLIB_LIBRARIES})
|
||||||
|
else()
|
||||||
add_subdirectory(third_party/zlib)
|
add_subdirectory(third_party/zlib)
|
||||||
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
|
||||||
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
|
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
|
||||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
else()
|
|
||||||
# gcc/Linux needs -pthread
|
|
||||||
find_package(Threads)
|
|
||||||
|
|
||||||
target_link_libraries(ixwebsocket
|
|
||||||
z ${CMAKE_THREAD_LIBS_INIT})
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set( IXWEBSOCKET_INCLUDE_DIRS
|
set( IXWEBSOCKET_INCLUDE_DIRS
|
||||||
|
@ -1 +1 @@
|
|||||||
3.1.2
|
4.0.4
|
||||||
|
125
README.md
125
README.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex and bi-directionnal communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
|
[*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 and server 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
|
||||||
@ -33,27 +33,22 @@ webSocket.disablePerMessageDeflate();
|
|||||||
|
|
||||||
// Setup a callback to be fired when a message or an event (open, close, error) is received
|
// Setup a callback to be fired when a message or an event (open, close, error) is received
|
||||||
webSocket.setOnMessageCallback(
|
webSocket.setOnMessageCallback(
|
||||||
[](ix::WebSocketMessageType messageType,
|
[](const ix::WebSocketMessagePtr& msg)
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocketMessageType::Message)
|
if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
std::cout << str << std::endl;
|
std::cout << msg->str << std::endl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 (default to BINARY mode)
|
// Send a message to the server (default to TEXT mode)
|
||||||
webSocket.send("hello world");
|
webSocket.send("hello world");
|
||||||
|
|
||||||
// The message can be sent in TEXT mode
|
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
|
||||||
webSocket.sendText("hello again");
|
webSocket.sendBinary("some serialized binary data");
|
||||||
|
|
||||||
// ... finally ...
|
// ... finally ...
|
||||||
|
|
||||||
@ -73,14 +68,9 @@ server.setOnConnectionCallback(
|
|||||||
std::shared_ptr<ConnectionState> connectionState)
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
{
|
{
|
||||||
webSocket->setOnMessageCallback(
|
webSocket->setOnMessageCallback(
|
||||||
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
|
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
std::cerr << "New connection" << std::endl;
|
std::cerr << "New connection" << std::endl;
|
||||||
|
|
||||||
@ -91,19 +81,21 @@ server.setOnConnectionCallback(
|
|||||||
std::cerr << "id: " << connectionState->getId() << std::endl;
|
std::cerr << "id: " << connectionState->getId() << std::endl;
|
||||||
|
|
||||||
// The uri the client did connect to.
|
// The uri the client did connect to.
|
||||||
std::cerr << "Uri: " << openInfo.uri << std::endl;
|
std::cerr << "Uri: " << msg->openInfo.uri << std::endl;
|
||||||
|
|
||||||
std::cerr << "Headers:" << std::endl;
|
std::cerr << "Headers:" << std::endl;
|
||||||
for (auto it : openInfo.headers)
|
for (auto it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
std::cerr << it.first << ": " << it.second << std::endl;
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocketMessageType::Message)
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
// For an echo server, we just send back to the client whatever was received by the server
|
// For an echo server, we just send back to the client whatever was received by the server
|
||||||
// All connected clients are available in an std::set. See the broadcast cpp example.
|
// All connected clients are available in an std::set. See the broadcast cpp example.
|
||||||
webSocket->send(str);
|
// Second parameter tells whether we are sending the message in binary or text mode.
|
||||||
|
// Here we send it in the same mode as it was received.
|
||||||
|
webSocket->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -125,7 +117,7 @@ server.wait();
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Here is what the HTTP client API looks like. Note that HTTP client support is very recent and subject to changes.
|
Here is what the HTTP client API looks like.
|
||||||
|
|
||||||
```
|
```
|
||||||
//
|
//
|
||||||
@ -204,6 +196,44 @@ bool ok = httpClient.performRequest(args, [](const HttpResponsePtr& response)
|
|||||||
// ok will be false if your httpClient is not async
|
// ok will be false if your httpClient is not async
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here is what the HTTP server API looks like. Note that HTTP server support is very, very recent and subject to changes.
|
||||||
|
|
||||||
|
```
|
||||||
|
ix::HttpServer server(port, hostname);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
std::cerr << res.second << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.wait();
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to handle how requests are processed, implement the setOnConnectionCallback callback, which takes an HttpRequestPtr as input, and returns an HttpResponsePtr. You can look at HttpServer::setDefaultConnectionCallback for a slightly more advanced callback example.
|
||||||
|
|
||||||
|
```
|
||||||
|
setOnConnectionCallback(
|
||||||
|
[this](HttpRequestPtr request,
|
||||||
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||||
|
{
|
||||||
|
// Build a string for the response
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << request->method
|
||||||
|
<< " "
|
||||||
|
<< request->uri;
|
||||||
|
|
||||||
|
std::string content = ss.str();
|
||||||
|
|
||||||
|
return std::make_shared<HttpResponse>(200, "OK",
|
||||||
|
HttpErrorCode::Ok,
|
||||||
|
WebSocketHttpHeaders(),
|
||||||
|
content);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
|
CMakefiles for the library and the examples are available. This library has few dependencies, so it is possible to just add the source files into your project. Otherwise the usual way will suffice.
|
||||||
@ -334,32 +364,27 @@ The onMessage event will be fired when the connection is opened or closed. This
|
|||||||
|
|
||||||
```
|
```
|
||||||
webSocket.setOnMessageCallback(
|
webSocket.setOnMessageCallback(
|
||||||
[](ix::WebSocketMessageType messageType,
|
[](const ix::WebSocketMessagePtr& msg)
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocketMessageType::Open)
|
if (msg->type == ix::WebSocketMessageType::Open)
|
||||||
{
|
{
|
||||||
std::cout << "send greetings" << std::endl;
|
std::cout << "send greetings" << std::endl;
|
||||||
|
|
||||||
// Headers can be inspected (pairs of string/string)
|
// Headers can be inspected (pairs of string/string)
|
||||||
std::cout << "Handshake Headers:" << std::endl;
|
std::cout << "Handshake Headers:" << std::endl;
|
||||||
for (auto it : headers)
|
for (auto it : msg->headers)
|
||||||
{
|
{
|
||||||
std::cout << it.first << ": " << it.second << std::endl;
|
std::cout << it.first << ": " << it.second << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (messageType == ix::WebSocketMessageType::Close)
|
else if (msg->type == ix::WebSocketMessageType::Close)
|
||||||
{
|
{
|
||||||
std::cout << "disconnected" << std::endl;
|
std::cout << "disconnected" << std::endl;
|
||||||
|
|
||||||
// The server can send an explicit code and reason for closing.
|
// The server can send an explicit code and reason for closing.
|
||||||
// This data can be accessed through the closeInfo object.
|
// This data can be accessed through the closeInfo object.
|
||||||
std::cout << closeInfo.code << std::endl;
|
std::cout << msg->closeInfo.code << std::endl;
|
||||||
std::cout << closeInfo.reason << std::endl;
|
std::cout << msg->closeInfo.reason << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -371,20 +396,15 @@ A message will be fired when there is an error with the connection. The message
|
|||||||
|
|
||||||
```
|
```
|
||||||
webSocket.setOnMessageCallback(
|
webSocket.setOnMessageCallback(
|
||||||
[](ix::WebSocketMessageType messageType,
|
[](const ix::WebSocketMessagePtr& msg)
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocketMessageType::Error)
|
if (msg->type == ix::WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Error: " << error.reason << std::endl;
|
ss << "Error: " << msg->errorInfo.reason << std::endl;
|
||||||
ss << "#retries: " << event.retries << std::endl;
|
ss << "#retries: " << msg->eventInfo.retries << std::endl;
|
||||||
ss << "Wait time(ms): " << event.wait_time << std::endl;
|
ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
|
||||||
ss << "HTTP Status: " << event.http_status << std::endl;
|
ss << "HTTP Status: " << msg->eventInfo.http_status << std::endl;
|
||||||
std::cout << ss.str() << std::endl;
|
std::cout << ss.str() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,17 +431,12 @@ Ping/pong messages are used to implement keep-alive. 2 message types exists to i
|
|||||||
|
|
||||||
```
|
```
|
||||||
webSocket.setOnMessageCallback(
|
webSocket.setOnMessageCallback(
|
||||||
[](ix::WebSocketMessageType messageType,
|
[](const ix::WebSocketMessagePtr& msg)
|
||||||
const std::string& str,
|
|
||||||
size_t wireSize,
|
|
||||||
const ix::WebSocketErrorInfo& error,
|
|
||||||
const ix::WebSocketOpenInfo& openInfo,
|
|
||||||
const ix::WebSocketCloseInfo& closeInfo)
|
|
||||||
{
|
{
|
||||||
if (messageType == ix::WebSocketMessageType::Ping ||
|
if (msg->type == ix::WebSocketMessageType::Ping ||
|
||||||
messageType == ix::WebSocketMessageType::Pong)
|
msg->type == ix::WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
std::cout << "pong data: " << str << std::endl;
|
std::cout << "pong data: " << msg->str << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -14,27 +14,15 @@ namespace ix
|
|||||||
{
|
{
|
||||||
const int64_t DNSLookup::kDefaultWait = 10; // ms
|
const int64_t DNSLookup::kDefaultWait = 10; // ms
|
||||||
|
|
||||||
std::atomic<uint64_t> DNSLookup::_nextId(0);
|
|
||||||
std::set<uint64_t> DNSLookup::_activeJobs;
|
|
||||||
std::mutex DNSLookup::_activeJobsMutex;
|
|
||||||
|
|
||||||
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
|
||||||
_port(port),
|
_port(port),
|
||||||
_wait(wait),
|
_wait(wait),
|
||||||
_res(nullptr),
|
_res(nullptr),
|
||||||
_done(false),
|
_done(false)
|
||||||
_id(_nextId++)
|
|
||||||
{
|
{
|
||||||
setHostname(hostname);
|
setHostname(hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
DNSLookup::~DNSLookup()
|
|
||||||
{
|
|
||||||
// Remove this job from the active jobs list
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
_activeJobs.erase(_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
|
||||||
int port,
|
int port,
|
||||||
std::string& errMsg)
|
std::string& errMsg)
|
||||||
@ -94,17 +82,14 @@ namespace ix
|
|||||||
// if you need a second lookup.
|
// if you need a second lookup.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record job in the active Job set
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
_activeJobs.insert(_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Good resource on thread forced termination
|
// Good resource on thread forced termination
|
||||||
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
|
||||||
//
|
//
|
||||||
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
|
auto ptr = shared_from_this();
|
||||||
|
std::weak_ptr<DNSLookup> self(ptr);
|
||||||
|
|
||||||
|
_thread = std::thread(&DNSLookup::run, this, self, getHostname(), _port);
|
||||||
_thread.detach();
|
_thread.detach();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
|
||||||
@ -138,7 +123,7 @@ namespace ix
|
|||||||
return getRes();
|
return getRes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
|
void DNSLookup::run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port) // thread runner
|
||||||
{
|
{
|
||||||
// We don't want to read or write into members variables of an object that could be
|
// We don't want to read or write into members variables of an object that could be
|
||||||
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
// gone, so we use temporary variables (res) or we pass in by copy everything that
|
||||||
@ -146,15 +131,8 @@ namespace ix
|
|||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
|
||||||
|
|
||||||
// if this isn't an active job, and the control thread is gone
|
if (self.lock())
|
||||||
// there is nothing to do, and we don't want to touch the defunct
|
|
||||||
// object data structure such as _errMsg or _condition
|
|
||||||
std::lock_guard<std::mutex> lock(_activeJobsMutex);
|
|
||||||
if (_activeJobs.count(id) == 0)
|
|
||||||
{
|
{
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy result into the member variables
|
// Copy result into the member variables
|
||||||
setRes(res);
|
setRes(res);
|
||||||
setErrMsg(errMsg);
|
setErrMsg(errMsg);
|
||||||
@ -162,6 +140,7 @@ namespace ix
|
|||||||
_condition.notify_one();
|
_condition.notify_one();
|
||||||
_done = true;
|
_done = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DNSLookup::setHostname(const std::string& hostname)
|
void DNSLookup::setHostname(const std::string& hostname)
|
||||||
{
|
{
|
||||||
|
@ -16,16 +16,17 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct addrinfo;
|
struct addrinfo;
|
||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
class DNSLookup
|
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
|
||||||
~DNSLookup();
|
~DNSLookup() = default;
|
||||||
|
|
||||||
struct addrinfo* resolve(std::string& errMsg,
|
struct addrinfo* resolve(std::string& errMsg,
|
||||||
const CancellationRequest& isCancellationRequested,
|
const CancellationRequest& isCancellationRequested,
|
||||||
@ -41,7 +42,7 @@ namespace ix
|
|||||||
int port,
|
int port,
|
||||||
std::string& errMsg);
|
std::string& errMsg);
|
||||||
|
|
||||||
void run(uint64_t id, const std::string& hostname, int port); // thread runner
|
void run(std::weak_ptr<DNSLookup> self, const std::string& hostname, int port); // thread runner
|
||||||
|
|
||||||
void setHostname(const std::string& hostname);
|
void setHostname(const std::string& hostname);
|
||||||
const std::string& getHostname();
|
const std::string& getHostname();
|
||||||
@ -69,11 +70,6 @@ namespace ix
|
|||||||
std::condition_variable _condition;
|
std::condition_variable _condition;
|
||||||
std::mutex _conditionVariableMutex;
|
std::mutex _conditionVariableMutex;
|
||||||
|
|
||||||
uint64_t _id;
|
|
||||||
static std::atomic<uint64_t> _nextId;
|
|
||||||
static std::set<uint64_t> _activeJobs;
|
|
||||||
static std::mutex _activeJobsMutex;
|
|
||||||
|
|
||||||
const static int64_t kDefaultWait;
|
const static int64_t kDefaultWait;
|
||||||
};
|
};
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
138
ixwebsocket/IXHttp.cpp
Normal file
138
ixwebsocket/IXHttp.cpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* IXHttp.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXHttp.h"
|
||||||
|
#include "IXCancellationRequest.h"
|
||||||
|
#include "IXSocket.h"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
std::string Http::trim(const std::string& str)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
for (auto c : str)
|
||||||
|
{
|
||||||
|
if (c != ' ' && c != '\n' && c != '\r')
|
||||||
|
{
|
||||||
|
out += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<std::string, std::string, std::string> Http::parseRequestLine(const std::string& line)
|
||||||
|
{
|
||||||
|
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
||||||
|
std::string token;
|
||||||
|
std::stringstream tokenStream(line);
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
|
||||||
|
// Split by ' '
|
||||||
|
while (std::getline(tokenStream, token, ' '))
|
||||||
|
{
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string method;
|
||||||
|
if (tokens.size() >= 1)
|
||||||
|
{
|
||||||
|
method = trim(tokens[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string requestUri;
|
||||||
|
if (tokens.size() >= 2)
|
||||||
|
{
|
||||||
|
requestUri = trim(tokens[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string httpVersion;
|
||||||
|
if (tokens.size() >= 3)
|
||||||
|
{
|
||||||
|
httpVersion = trim(tokens[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(method, requestUri, httpVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<bool, std::string, HttpRequestPtr> Http::parseRequest(std::shared_ptr<Socket> socket)
|
||||||
|
{
|
||||||
|
HttpRequestPtr httpRequest;
|
||||||
|
|
||||||
|
std::atomic<bool> requestInitCancellation(false);
|
||||||
|
|
||||||
|
int timeoutSecs = 5; // FIXME
|
||||||
|
|
||||||
|
auto isCancellationRequested =
|
||||||
|
makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
|
||||||
|
|
||||||
|
// Read first line
|
||||||
|
auto lineResult = socket->readLine(isCancellationRequested);
|
||||||
|
auto lineValid = lineResult.first;
|
||||||
|
auto line = lineResult.second;
|
||||||
|
|
||||||
|
if (!lineValid)
|
||||||
|
{
|
||||||
|
return std::make_tuple(false, "Error reading HTTP request line", httpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request line (GET /foo HTTP/1.1\r\n)
|
||||||
|
auto requestLine = Http::parseRequestLine(line);
|
||||||
|
auto method = std::get<0>(requestLine);
|
||||||
|
auto uri = std::get<1>(requestLine);
|
||||||
|
auto httpVersion = std::get<2>(requestLine);
|
||||||
|
|
||||||
|
// Retrieve and validate HTTP headers
|
||||||
|
auto result = parseHttpHeaders(socket, isCancellationRequested);
|
||||||
|
auto headersValid = result.first;
|
||||||
|
auto headers = result.second;
|
||||||
|
|
||||||
|
if (!headersValid)
|
||||||
|
{
|
||||||
|
return std::make_tuple(false, "Error parsing HTTP headers", httpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRequest = std::make_shared<HttpRequest>(uri, method, httpVersion, headers);
|
||||||
|
return std::make_tuple(true, "", httpRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Http::sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket)
|
||||||
|
{
|
||||||
|
// Write the response to the socket
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "HTTP/1.1 ";
|
||||||
|
ss << response->statusCode;
|
||||||
|
ss << " ";
|
||||||
|
ss << response->description;
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
if (!socket->writeBytes(ss.str(), nullptr))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write headers
|
||||||
|
ss.str("");
|
||||||
|
ss << "Content-Length: " << response->payload.size() << "\r\n";
|
||||||
|
for (auto&& it : response->headers)
|
||||||
|
{
|
||||||
|
ss << it.first << ": " << it.second << "\r\n";
|
||||||
|
}
|
||||||
|
ss << "\r\n";
|
||||||
|
|
||||||
|
if (!socket->writeBytes(ss.str(), nullptr))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response->payload.empty()
|
||||||
|
? true
|
||||||
|
: socket->writeBytes(response->payload, nullptr);
|
||||||
|
}
|
||||||
|
}
|
120
ixwebsocket/IXHttp.h
Normal file
120
ixwebsocket/IXHttp.h
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* IXHttp.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include "IXProgressCallback.h"
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
enum class HttpErrorCode : int
|
||||||
|
{
|
||||||
|
Ok = 0,
|
||||||
|
CannotConnect = 1,
|
||||||
|
Timeout = 2,
|
||||||
|
Gzip = 3,
|
||||||
|
UrlMalformed = 4,
|
||||||
|
CannotCreateSocket = 5,
|
||||||
|
SendError = 6,
|
||||||
|
ReadError = 7,
|
||||||
|
CannotReadStatusLine = 8,
|
||||||
|
MissingStatus = 9,
|
||||||
|
HeaderParsingError = 10,
|
||||||
|
MissingLocation = 11,
|
||||||
|
TooManyRedirects = 12,
|
||||||
|
ChunkReadError = 13,
|
||||||
|
CannotReadBody = 14,
|
||||||
|
Invalid = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HttpResponse
|
||||||
|
{
|
||||||
|
int statusCode;
|
||||||
|
std::string description;
|
||||||
|
HttpErrorCode errorCode;
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
std::string payload;
|
||||||
|
std::string errorMsg;
|
||||||
|
uint64_t uploadSize;
|
||||||
|
uint64_t downloadSize;
|
||||||
|
|
||||||
|
HttpResponse(int s = 0,
|
||||||
|
const std::string& des = std::string(),
|
||||||
|
const HttpErrorCode& c = HttpErrorCode::Ok,
|
||||||
|
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
||||||
|
const std::string& p = std::string(),
|
||||||
|
const std::string& e = std::string(),
|
||||||
|
uint64_t u = 0,
|
||||||
|
uint64_t d = 0)
|
||||||
|
: statusCode(s)
|
||||||
|
, description(des)
|
||||||
|
, errorCode(c)
|
||||||
|
, headers(h)
|
||||||
|
, payload(p)
|
||||||
|
, errorMsg(e)
|
||||||
|
, uploadSize(u)
|
||||||
|
, downloadSize(d)
|
||||||
|
{
|
||||||
|
;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
||||||
|
using HttpParameters = std::map<std::string, std::string>;
|
||||||
|
using Logger = std::function<void(const std::string&)>;
|
||||||
|
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
||||||
|
|
||||||
|
struct HttpRequestArgs
|
||||||
|
{
|
||||||
|
std::string url;
|
||||||
|
std::string verb;
|
||||||
|
WebSocketHttpHeaders extraHeaders;
|
||||||
|
std::string body;
|
||||||
|
int connectTimeout;
|
||||||
|
int transferTimeout;
|
||||||
|
bool followRedirects;
|
||||||
|
int maxRedirects;
|
||||||
|
bool verbose;
|
||||||
|
bool compress;
|
||||||
|
Logger logger;
|
||||||
|
OnProgressCallback onProgressCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
||||||
|
|
||||||
|
struct HttpRequest
|
||||||
|
{
|
||||||
|
std::string uri;
|
||||||
|
std::string method;
|
||||||
|
std::string version;
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
HttpRequest(const std::string& u,
|
||||||
|
const std::string& m,
|
||||||
|
const std::string& v,
|
||||||
|
const WebSocketHttpHeaders& h = WebSocketHttpHeaders())
|
||||||
|
: uri(u)
|
||||||
|
, method(m)
|
||||||
|
, version(v)
|
||||||
|
, headers(h)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
||||||
|
|
||||||
|
class Http
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::tuple<bool, std::string, HttpRequestPtr> parseRequest(std::shared_ptr<Socket> socket);
|
||||||
|
static bool sendResponse(HttpResponsePtr response, std::shared_ptr<Socket> socket);
|
||||||
|
|
||||||
|
static std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
||||||
|
static std::string trim(const std::string& str);
|
||||||
|
};
|
||||||
|
}
|
@ -118,6 +118,7 @@ namespace ix
|
|||||||
int code = 0;
|
int code = 0;
|
||||||
WebSocketHttpHeaders headers;
|
WebSocketHttpHeaders headers;
|
||||||
std::string payload;
|
std::string payload;
|
||||||
|
std::string description;
|
||||||
|
|
||||||
std::string protocol, host, path, query;
|
std::string protocol, host, path, query;
|
||||||
int port;
|
int port;
|
||||||
@ -126,7 +127,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot parse url: " << url;
|
ss << "Cannot parse url: " << url;
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::UrlMalformed,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ namespace ix
|
|||||||
|
|
||||||
if (!_socket)
|
if (!_socket)
|
||||||
{
|
{
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotCreateSocket,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -200,7 +201,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
ss << "Cannot connect to url: " << url << " / error : " << errMsg;
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotConnect,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotConnect,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -226,7 +227,7 @@ namespace ix
|
|||||||
if (!_socket->writeBytes(req, isCancellationRequested))
|
if (!_socket->writeBytes(req, isCancellationRequested))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot send request");
|
std::string errorMsg("Cannot send request");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::SendError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::SendError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -240,7 +241,7 @@ namespace ix
|
|||||||
if (!lineValid)
|
if (!lineValid)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot retrieve status line");
|
std::string errorMsg("Cannot retrieve status line");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadStatusLine,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadStatusLine,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -255,7 +256,7 @@ namespace ix
|
|||||||
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot parse response code from status line");
|
std::string errorMsg("Cannot parse response code from status line");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingStatus,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingStatus,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -267,7 +268,7 @@ namespace ix
|
|||||||
if (!headersValid)
|
if (!headersValid)
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot parse http headers");
|
std::string errorMsg("Cannot parse http headers");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::HeaderParsingError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::HeaderParsingError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -278,7 +279,7 @@ namespace ix
|
|||||||
if (headers.find("Location") == headers.end())
|
if (headers.find("Location") == headers.end())
|
||||||
{
|
{
|
||||||
std::string errorMsg("Missing location header for redirect");
|
std::string errorMsg("Missing location header for redirect");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingLocation,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingLocation,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -287,7 +288,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Too many redirects: " << redirects;
|
ss << "Too many redirects: " << redirects;
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::TooManyRedirects,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::TooManyRedirects,
|
||||||
headers, payload, ss.str(),
|
headers, payload, ss.str(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -299,7 +300,7 @@ namespace ix
|
|||||||
|
|
||||||
if (verb == "HEAD")
|
if (verb == "HEAD")
|
||||||
{
|
{
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||||
headers, payload, std::string(),
|
headers, payload, std::string(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -320,7 +321,7 @@ namespace ix
|
|||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -338,7 +339,7 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -365,7 +366,7 @@ namespace ix
|
|||||||
if (!chunkResult.first)
|
if (!chunkResult.first)
|
||||||
{
|
{
|
||||||
errorMsg = "Cannot read chunk";
|
errorMsg = "Cannot read chunk";
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -376,7 +377,7 @@ namespace ix
|
|||||||
|
|
||||||
if (!lineResult.first)
|
if (!lineResult.first)
|
||||||
{
|
{
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -391,7 +392,7 @@ namespace ix
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string errorMsg("Cannot read http body");
|
std::string errorMsg("Cannot read http body");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotReadBody,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotReadBody,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
@ -405,14 +406,14 @@ namespace ix
|
|||||||
if (!gzipInflate(payload, decompressedPayload))
|
if (!gzipInflate(payload, decompressedPayload))
|
||||||
{
|
{
|
||||||
std::string errorMsg("Error decompressing payload");
|
std::string errorMsg("Error decompressing payload");
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Gzip,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Gzip,
|
||||||
headers, payload, errorMsg,
|
headers, payload, errorMsg,
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
payload = decompressedPayload;
|
payload = decompressedPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
|
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
|
||||||
headers, payload, std::string(),
|
headers, payload, std::string(),
|
||||||
uploadSize, downloadSize);
|
uploadSize, downloadSize);
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "IXSocket.h"
|
#include "IXSocket.h"
|
||||||
#include "IXWebSocketHttpHeaders.h"
|
#include "IXWebSocketHttpHeaders.h"
|
||||||
|
#include "IXHttp.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@ -20,78 +21,6 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
enum class HttpErrorCode : int
|
|
||||||
{
|
|
||||||
Ok = 0,
|
|
||||||
CannotConnect = 1,
|
|
||||||
Timeout = 2,
|
|
||||||
Gzip = 3,
|
|
||||||
UrlMalformed = 4,
|
|
||||||
CannotCreateSocket = 5,
|
|
||||||
SendError = 6,
|
|
||||||
ReadError = 7,
|
|
||||||
CannotReadStatusLine = 8,
|
|
||||||
MissingStatus = 9,
|
|
||||||
HeaderParsingError = 10,
|
|
||||||
MissingLocation = 11,
|
|
||||||
TooManyRedirects = 12,
|
|
||||||
ChunkReadError = 13,
|
|
||||||
CannotReadBody = 14,
|
|
||||||
Invalid = 100
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpResponse
|
|
||||||
{
|
|
||||||
int statusCode;
|
|
||||||
HttpErrorCode errorCode;
|
|
||||||
WebSocketHttpHeaders headers;
|
|
||||||
std::string payload;
|
|
||||||
std::string errorMsg;
|
|
||||||
uint64_t uploadSize;
|
|
||||||
uint64_t downloadSize;
|
|
||||||
|
|
||||||
HttpResponse(int s = 0,
|
|
||||||
const HttpErrorCode& c = HttpErrorCode::Ok,
|
|
||||||
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
|
|
||||||
const std::string& p = std::string(),
|
|
||||||
const std::string& e = std::string(),
|
|
||||||
uint64_t u = 0,
|
|
||||||
uint64_t d = 0)
|
|
||||||
: statusCode(s)
|
|
||||||
, errorCode(c)
|
|
||||||
, headers(h)
|
|
||||||
, payload(p)
|
|
||||||
, errorMsg(e)
|
|
||||||
, uploadSize(u)
|
|
||||||
, downloadSize(d)
|
|
||||||
{
|
|
||||||
;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
|
||||||
using HttpParameters = std::map<std::string, std::string>;
|
|
||||||
using Logger = std::function<void(const std::string&)>;
|
|
||||||
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
|
|
||||||
|
|
||||||
struct HttpRequestArgs
|
|
||||||
{
|
|
||||||
std::string url;
|
|
||||||
std::string verb;
|
|
||||||
WebSocketHttpHeaders extraHeaders;
|
|
||||||
std::string body;
|
|
||||||
int connectTimeout;
|
|
||||||
int transferTimeout;
|
|
||||||
bool followRedirects;
|
|
||||||
int maxRedirects;
|
|
||||||
bool verbose;
|
|
||||||
bool compress;
|
|
||||||
Logger logger;
|
|
||||||
OnProgressCallback onProgressCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
|
|
||||||
|
|
||||||
class HttpClient
|
class HttpClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
158
ixwebsocket/IXHttpServer.cpp
Normal file
158
ixwebsocket/IXHttpServer.cpp
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* IXHttpServer.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXHttpServer.h"
|
||||||
|
#include "IXSocketConnect.h"
|
||||||
|
#include "IXSocketFactory.h"
|
||||||
|
#include "IXNetSystem.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::pair<bool, std::vector<uint8_t>> load(const std::string& path)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> memblock;
|
||||||
|
|
||||||
|
std::ifstream file(path);
|
||||||
|
if (!file.is_open()) return std::make_pair(false, memblock);
|
||||||
|
|
||||||
|
file.seekg(0, file.end);
|
||||||
|
std::streamoff size = file.tellg();
|
||||||
|
file.seekg(0, file.beg);
|
||||||
|
|
||||||
|
memblock.resize((size_t) size);
|
||||||
|
file.read((char*)&memblock.front(), static_cast<std::streamsize>(size));
|
||||||
|
|
||||||
|
return std::make_pair(true, memblock);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, std::string> readAsString(const std::string& path)
|
||||||
|
{
|
||||||
|
auto res = load(path);
|
||||||
|
auto vec = res.second;
|
||||||
|
return std::make_pair(res.first, std::string(vec.begin(), vec.end()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
HttpServer::HttpServer(int port,
|
||||||
|
const std::string& host,
|
||||||
|
int backlog,
|
||||||
|
size_t maxConnections) : SocketServer(port, host, backlog, maxConnections),
|
||||||
|
_connectedClientsCount(0)
|
||||||
|
{
|
||||||
|
setDefaultConnectionCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServer::~HttpServer()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServer::stop()
|
||||||
|
{
|
||||||
|
stopAcceptingConnections();
|
||||||
|
|
||||||
|
// FIXME: cancelling / closing active clients ...
|
||||||
|
|
||||||
|
SocketServer::stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServer::setOnConnectionCallback(const OnConnectionCallback& callback)
|
||||||
|
{
|
||||||
|
_onConnectionCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServer::handleConnection(
|
||||||
|
int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState)
|
||||||
|
{
|
||||||
|
_connectedClientsCount++;
|
||||||
|
|
||||||
|
std::string errorMsg;
|
||||||
|
auto socket = createSocket(fd, errorMsg);
|
||||||
|
|
||||||
|
// Set the socket to non blocking mode + other tweaks
|
||||||
|
SocketConnect::configure(fd);
|
||||||
|
|
||||||
|
auto ret = Http::parseRequest(socket);
|
||||||
|
// FIXME: handle errors in parseRequest
|
||||||
|
|
||||||
|
if (std::get<0>(ret))
|
||||||
|
{
|
||||||
|
auto response = _onConnectionCallback(std::get<2>(ret), connectionState);
|
||||||
|
if (!Http::sendResponse(response, socket))
|
||||||
|
{
|
||||||
|
logError("Cannot send response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectionState->setTerminated();
|
||||||
|
|
||||||
|
_connectedClientsCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HttpServer::getConnectedClientsCount()
|
||||||
|
{
|
||||||
|
return _connectedClientsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpServer::setDefaultConnectionCallback()
|
||||||
|
{
|
||||||
|
setOnConnectionCallback(
|
||||||
|
[this](HttpRequestPtr request,
|
||||||
|
std::shared_ptr<ConnectionState> /*connectionState*/) -> HttpResponsePtr
|
||||||
|
{
|
||||||
|
std::string uri(request->uri);
|
||||||
|
if (uri.empty() || uri == "/")
|
||||||
|
{
|
||||||
|
uri = "/index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path("." + uri);
|
||||||
|
auto res = readAsString(path);
|
||||||
|
bool found = res.first;
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
return std::make_shared<HttpResponse>(404, "Not Found",
|
||||||
|
HttpErrorCode::Ok,
|
||||||
|
WebSocketHttpHeaders(),
|
||||||
|
std::string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content = res.second;
|
||||||
|
|
||||||
|
// Log request
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << request->method
|
||||||
|
<< " "
|
||||||
|
<< request->uri
|
||||||
|
<< " "
|
||||||
|
<< content.size();
|
||||||
|
logInfo(ss.str());
|
||||||
|
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
// FIXME: check extensions to set the content type
|
||||||
|
// headers["Content-Type"] = "application/octet-stream";
|
||||||
|
headers["Accept-Ranges"] = "none";
|
||||||
|
|
||||||
|
for (auto&& it : request->headers)
|
||||||
|
{
|
||||||
|
headers[it.first] = it.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<HttpResponse>(200, "OK",
|
||||||
|
HttpErrorCode::Ok,
|
||||||
|
headers,
|
||||||
|
content);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
ixwebsocket/IXHttpServer.h
Normal file
50
ixwebsocket/IXHttpServer.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* IXHttpServer.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IXSocketServer.h"
|
||||||
|
#include "IXWebSocket.h"
|
||||||
|
#include "IXHttp.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility> // pair
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
class HttpServer final : public SocketServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using OnConnectionCallback =
|
||||||
|
std::function<HttpResponsePtr(HttpRequestPtr, std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
|
HttpServer(int port = SocketServer::kDefaultPort,
|
||||||
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
|
size_t maxConnections = SocketServer::kDefaultMaxConnections);
|
||||||
|
virtual ~HttpServer();
|
||||||
|
virtual void stop() final;
|
||||||
|
|
||||||
|
void setOnConnectionCallback(const OnConnectionCallback& callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Member variables
|
||||||
|
OnConnectionCallback _onConnectionCallback;
|
||||||
|
std::atomic<int> _connectedClientsCount;
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
virtual void handleConnection(int fd,
|
||||||
|
std::shared_ptr<ConnectionState> connectionState) final;
|
||||||
|
virtual size_t getConnectedClientsCount() final;
|
||||||
|
|
||||||
|
void setDefaultConnectionCallback();
|
||||||
|
};
|
||||||
|
} // namespace ix
|
||||||
|
|
@ -232,19 +232,28 @@ namespace ix
|
|||||||
bool Socket::writeBytes(const std::string& str,
|
bool Socket::writeBytes(const std::string& str,
|
||||||
const CancellationRequest& isCancellationRequested)
|
const CancellationRequest& isCancellationRequested)
|
||||||
{
|
{
|
||||||
|
char* buffer = const_cast<char*>(str.c_str());
|
||||||
|
int len = (int) str.size();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isCancellationRequested && isCancellationRequested()) return false;
|
if (isCancellationRequested && isCancellationRequested()) return false;
|
||||||
|
|
||||||
char* buffer = const_cast<char*>(str.c_str());
|
|
||||||
int len = (int) str.size();
|
|
||||||
|
|
||||||
ssize_t ret = send(buffer, len);
|
ssize_t ret = send(buffer, len);
|
||||||
|
|
||||||
// We wrote some bytes, as needed, all good.
|
// We wrote some bytes, as needed, all good.
|
||||||
if (ret > 0)
|
if (ret > 0)
|
||||||
{
|
{
|
||||||
return ret == len;
|
if (ret == len)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer += ret;
|
||||||
|
len -= ret;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// There is possibly something to be writen, try again
|
// There is possibly something to be writen, try again
|
||||||
else if (ret < 0 && Socket::isWaitNeeded())
|
else if (ret < 0 && Socket::isWaitNeeded())
|
||||||
|
@ -128,8 +128,8 @@ namespace ix
|
|||||||
//
|
//
|
||||||
// First do DNS resolution
|
// First do DNS resolution
|
||||||
//
|
//
|
||||||
DNSLookup dnsLookup(hostname, port);
|
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
|
||||||
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
|
struct addrinfo *res = dnsLookup->resolve(errMsg, isCancellationRequested);
|
||||||
if (res == nullptr)
|
if (res == nullptr)
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <future>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
@ -325,8 +325,8 @@ namespace ix
|
|||||||
WebSocketMessageType webSocketMessageType;
|
WebSocketMessageType webSocketMessageType;
|
||||||
switch (messageKind)
|
switch (messageKind)
|
||||||
{
|
{
|
||||||
default:
|
case WebSocketTransport::MessageKind::MSG_TEXT:
|
||||||
case WebSocketTransport::MessageKind::MSG:
|
case WebSocketTransport::MessageKind::MSG_BINARY:
|
||||||
{
|
{
|
||||||
webSocketMessageType = WebSocketMessageType::Message;
|
webSocketMessageType = WebSocketMessageType::Message;
|
||||||
} break;
|
} break;
|
||||||
@ -350,11 +350,13 @@ namespace ix
|
|||||||
WebSocketErrorInfo webSocketErrorInfo;
|
WebSocketErrorInfo webSocketErrorInfo;
|
||||||
webSocketErrorInfo.decompressionError = decompressionError;
|
webSocketErrorInfo.decompressionError = decompressionError;
|
||||||
|
|
||||||
|
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
|
||||||
|
|
||||||
_onMessageCallback(
|
_onMessageCallback(
|
||||||
std::make_shared<WebSocketMessage>(
|
std::make_shared<WebSocketMessage>(
|
||||||
webSocketMessageType, msg, wireSize,
|
webSocketMessageType, msg, wireSize,
|
||||||
webSocketErrorInfo, WebSocketOpenInfo(),
|
webSocketErrorInfo, WebSocketOpenInfo(),
|
||||||
WebSocketCloseInfo()));
|
WebSocketCloseInfo(), binary));
|
||||||
|
|
||||||
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
|
||||||
});
|
});
|
||||||
@ -385,8 +387,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebSocketSendInfo WebSocket::send(const std::string& data,
|
WebSocketSendInfo WebSocket::send(const std::string& data,
|
||||||
const OnProgressCallback& onProgressCallback,
|
bool binary,
|
||||||
bool binary)
|
const OnProgressCallback& onProgressCallback)
|
||||||
{
|
{
|
||||||
return sendMessage(data,
|
return sendMessage(data,
|
||||||
(binary) ? SendMessageKind::Binary: SendMessageKind::Text,
|
(binary) ? SendMessageKind::Binary: SendMessageKind::Text,
|
||||||
|
@ -66,8 +66,8 @@ namespace ix
|
|||||||
|
|
||||||
// send is in binary mode by default
|
// send is in binary mode by default
|
||||||
WebSocketSendInfo send(const std::string& data,
|
WebSocketSendInfo send(const std::string& data,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr,
|
bool binary = false,
|
||||||
bool binary = true);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo sendBinary(const std::string& text,
|
WebSocketSendInfo sendBinary(const std::string& text,
|
||||||
const OnProgressCallback& onProgressCallback = nullptr);
|
const OnProgressCallback& onProgressCallback = nullptr);
|
||||||
WebSocketSendInfo sendText(const std::string& text,
|
WebSocketSendInfo sendText(const std::string& text,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "IXWebSocketHandshake.h"
|
#include "IXWebSocketHandshake.h"
|
||||||
#include "IXSocketConnect.h"
|
#include "IXSocketConnect.h"
|
||||||
#include "IXUrlParser.h"
|
#include "IXUrlParser.h"
|
||||||
|
#include "IXHttp.h"
|
||||||
|
|
||||||
#include "libwshandshake.hpp"
|
#include "libwshandshake.hpp"
|
||||||
|
|
||||||
@ -31,15 +32,6 @@ namespace ix
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WebSocketHandshake::trim(const std::string& str)
|
|
||||||
{
|
|
||||||
std::string out(str);
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), '\r'), out.end());
|
|
||||||
out.erase(std::remove(out.begin(), out.end(), '\n'), out.end());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
||||||
{
|
{
|
||||||
return std::equal(a.begin(), a.end(),
|
return std::equal(a.begin(), a.end(),
|
||||||
@ -50,40 +42,6 @@ namespace ix
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
|
|
||||||
{
|
|
||||||
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
|
|
||||||
std::string token;
|
|
||||||
std::stringstream tokenStream(line);
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
|
|
||||||
// Split by ' '
|
|
||||||
while (std::getline(tokenStream, token, ' '))
|
|
||||||
{
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string method;
|
|
||||||
if (tokens.size() >= 1)
|
|
||||||
{
|
|
||||||
method = trim(tokens[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string requestUri;
|
|
||||||
if (tokens.size() >= 2)
|
|
||||||
{
|
|
||||||
requestUri = trim(tokens[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string httpVersion;
|
|
||||||
if (tokens.size() >= 3)
|
|
||||||
{
|
|
||||||
httpVersion = trim(tokens[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_tuple(method, requestUri, httpVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketHandshake::genRandomString(const int len)
|
std::string WebSocketHandshake::genRandomString(const int len)
|
||||||
{
|
{
|
||||||
std::string alphanum =
|
std::string alphanum =
|
||||||
@ -294,7 +252,7 @@ namespace ix
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate request line (GET /foo HTTP/1.1\r\n)
|
// Validate request line (GET /foo HTTP/1.1\r\n)
|
||||||
auto requestLine = parseRequestLine(line);
|
auto requestLine = Http::parseRequestLine(line);
|
||||||
auto method = std::get<0>(requestLine);
|
auto method = std::get<0>(requestLine);
|
||||||
auto uri = std::get<1>(requestLine);
|
auto uri = std::get<1>(requestLine);
|
||||||
auto httpVersion = std::get<2>(requestLine);
|
auto httpVersion = std::get<2>(requestLine);
|
||||||
|
@ -64,8 +64,6 @@ namespace ix
|
|||||||
// Parse HTTP headers
|
// Parse HTTP headers
|
||||||
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
|
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
|
||||||
|
|
||||||
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
|
|
||||||
std::string trim(const std::string& str);
|
|
||||||
bool insensitiveStringCompare(const std::string& a, const std::string& b);
|
bool insensitiveStringCompare(const std::string& a, const std::string& b);
|
||||||
|
|
||||||
std::atomic<bool>& _requestInitCancellation;
|
std::atomic<bool>& _requestInitCancellation;
|
||||||
|
@ -31,13 +31,15 @@ namespace ix
|
|||||||
size_t w,
|
size_t w,
|
||||||
WebSocketErrorInfo e,
|
WebSocketErrorInfo e,
|
||||||
WebSocketOpenInfo o,
|
WebSocketOpenInfo o,
|
||||||
WebSocketCloseInfo c)
|
WebSocketCloseInfo c,
|
||||||
|
bool b = false)
|
||||||
: type(t)
|
: type(t)
|
||||||
, str(std::move(s))
|
, str(std::move(s))
|
||||||
, wireSize(w)
|
, wireSize(w)
|
||||||
, errorInfo(e)
|
, errorInfo(e)
|
||||||
, openInfo(o)
|
, openInfo(o)
|
||||||
, closeInfo(c)
|
, closeInfo(c)
|
||||||
|
, binary(b)
|
||||||
{
|
{
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,12 @@
|
|||||||
|
|
||||||
namespace ix
|
namespace ix
|
||||||
{
|
{
|
||||||
using OnConnectionCallback =
|
|
||||||
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
|
||||||
|
|
||||||
class WebSocketServer final : public SocketServer
|
class WebSocketServer final : public SocketServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using OnConnectionCallback =
|
||||||
|
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
|
||||||
|
|
||||||
WebSocketServer(int port = SocketServer::kDefaultPort,
|
WebSocketServer(int port = SocketServer::kDefaultPort,
|
||||||
const std::string& host = SocketServer::kDefaultHost,
|
const std::string& host = SocketServer::kDefaultHost,
|
||||||
int backlog = SocketServer::kDefaultTcpBacklog,
|
int backlog = SocketServer::kDefaultTcpBacklog,
|
||||||
|
@ -542,12 +542,17 @@ namespace ix
|
|||||||
) {
|
) {
|
||||||
unmaskReceiveBuffer(ws);
|
unmaskReceiveBuffer(ws);
|
||||||
|
|
||||||
|
MessageKind messageKind =
|
||||||
|
(ws.opcode == wsheader_type::TEXT_FRAME)
|
||||||
|
? MessageKind::MSG_TEXT
|
||||||
|
: MessageKind::MSG_BINARY;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Usual case. Small unfragmented messages
|
// Usual case. Small unfragmented messages
|
||||||
//
|
//
|
||||||
if (ws.fin && _chunks.empty())
|
if (ws.fin && _chunks.empty())
|
||||||
{
|
{
|
||||||
emitMessage(MessageKind::MSG,
|
emitMessage(messageKind,
|
||||||
std::string(_rxbuf.begin()+ws.header_size,
|
std::string(_rxbuf.begin()+ws.header_size,
|
||||||
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
|
||||||
ws,
|
ws,
|
||||||
@ -567,7 +572,7 @@ namespace ix
|
|||||||
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
|
||||||
if (ws.fin)
|
if (ws.fin)
|
||||||
{
|
{
|
||||||
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
|
emitMessage(messageKind, getMergedChunks(), ws, onMessageCallback);
|
||||||
_chunks.clear();
|
_chunks.clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -50,7 +50,8 @@ namespace ix
|
|||||||
|
|
||||||
enum class MessageKind
|
enum class MessageKind
|
||||||
{
|
{
|
||||||
MSG,
|
MSG_TEXT,
|
||||||
|
MSG_BINARY,
|
||||||
PING,
|
PING,
|
||||||
PONG,
|
PONG,
|
||||||
FRAGMENT
|
FRAGMENT
|
||||||
|
4
makefile
4
makefile
@ -9,10 +9,10 @@ install: brew
|
|||||||
# on osx it is good practice to make /usr/local user writable
|
# on osx it is good practice to make /usr/local user writable
|
||||||
# sudo chown -R `whoami`/staff /usr/local
|
# sudo chown -R `whoami`/staff /usr/local
|
||||||
brew:
|
brew:
|
||||||
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install)
|
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 .. ; make -j install)
|
||||||
|
|
||||||
ws:
|
ws:
|
||||||
mkdir -p build && (cd build ; cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .. ; make -j)
|
mkdir -p build && (cd build ; cmake -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .. ; make -j)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
xargs rm -fv < build/install_manifest.txt
|
xargs rm -fv < build/install_manifest.txt
|
||||||
|
@ -7,7 +7,7 @@ project (ixwebsocket_unittest)
|
|||||||
|
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (MAC)
|
||||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||||
find_package(Sanitizers)
|
find_package(Sanitizers)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
|
||||||
@ -28,6 +28,7 @@ include_directories(
|
|||||||
set (SOURCES
|
set (SOURCES
|
||||||
test_runner.cpp
|
test_runner.cpp
|
||||||
IXTest.cpp
|
IXTest.cpp
|
||||||
|
IXGetFreePort.cpp
|
||||||
../third_party/msgpack11/msgpack11.cpp
|
../third_party/msgpack11/msgpack11.cpp
|
||||||
../ws/ixcore/utils/IXCoreLogger.cpp
|
../ws/ixcore/utils/IXCoreLogger.cpp
|
||||||
|
|
||||||
@ -38,6 +39,8 @@ set (SOURCES
|
|||||||
IXUrlParserTest.cpp
|
IXUrlParserTest.cpp
|
||||||
IXWebSocketServerTest.cpp
|
IXWebSocketServerTest.cpp
|
||||||
IXHttpClientTest.cpp
|
IXHttpClientTest.cpp
|
||||||
|
IXHttpServerTest.cpp
|
||||||
|
IXUnityBuildsTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Some unittest don't work on windows yet
|
# Some unittest don't work on windows yet
|
||||||
@ -64,7 +67,7 @@ endif()
|
|||||||
|
|
||||||
add_executable(ixwebsocket_unittest ${SOURCES})
|
add_executable(ixwebsocket_unittest ${SOURCES})
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (MAC)
|
||||||
add_sanitizers(ixwebsocket_unittest)
|
add_sanitizers(ixwebsocket_unittest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -17,33 +17,33 @@ TEST_CASE("dns", "[net]")
|
|||||||
{
|
{
|
||||||
SECTION("Test resolving a known hostname")
|
SECTION("Test resolving a known hostname")
|
||||||
{
|
{
|
||||||
DNSLookup dnsLookup("www.google.com", 80);
|
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res;
|
struct addrinfo* res;
|
||||||
|
|
||||||
res = dnsLookup.resolve(errMsg, [] { return false; });
|
res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res != nullptr);
|
REQUIRE(res != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Test resolving a non-existing hostname")
|
SECTION("Test resolving a non-existing hostname")
|
||||||
{
|
{
|
||||||
DNSLookup dnsLookup("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
|
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return false; });
|
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return false; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res == nullptr);
|
REQUIRE(res == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Test resolving a good hostname, with cancellation")
|
SECTION("Test resolving a good hostname, with cancellation")
|
||||||
{
|
{
|
||||||
DNSLookup dnsLookup("www.google.com", 80, 1);
|
auto dnsLookup = std::make_shared<DNSLookup>("www.google.com", 80, 1);
|
||||||
|
|
||||||
std::string errMsg;
|
std::string errMsg;
|
||||||
// The callback returning true means we are requesting cancellation
|
// The callback returning true means we are requesting cancellation
|
||||||
struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return true; });
|
struct addrinfo* res = dnsLookup->resolve(errMsg, [] { return true; });
|
||||||
std::cerr << "Error message: " << errMsg << std::endl;
|
std::cerr << "Error message: " << errMsg << std::endl;
|
||||||
REQUIRE(res == nullptr);
|
REQUIRE(res == nullptr);
|
||||||
}
|
}
|
||||||
|
93
test/IXGetFreePort.cpp
Normal file
93
test/IXGetFreePort.cpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* IXGetFreePort.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "IXGetFreePort.h"
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int getAnyFreePortRandom()
|
||||||
|
{
|
||||||
|
std::random_device rd;
|
||||||
|
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
|
||||||
|
|
||||||
|
return dist(rd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getAnyFreePort()
|
||||||
|
{
|
||||||
|
int defaultPort = 8090;
|
||||||
|
int sockfd;
|
||||||
|
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
|
{
|
||||||
|
return getAnyFreePortRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
int enable = 1;
|
||||||
|
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
|
||||||
|
(char*) &enable, sizeof(enable)) < 0)
|
||||||
|
{
|
||||||
|
return getAnyFreePortRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind to port 0. This is the standard way to get a free port.
|
||||||
|
struct sockaddr_in server; // server address information
|
||||||
|
server.sin_family = AF_INET;
|
||||||
|
server.sin_port = htons(0);
|
||||||
|
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||||
|
|
||||||
|
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
|
||||||
|
{
|
||||||
|
Socket::closeSocket(sockfd);
|
||||||
|
return getAnyFreePortRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in sa; // server address information
|
||||||
|
socklen_t len = sizeof(sa);
|
||||||
|
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
||||||
|
{
|
||||||
|
Socket::closeSocket(sockfd);
|
||||||
|
return getAnyFreePortRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
int port = ntohs(sa.sin_port);
|
||||||
|
Socket::closeSocket(sockfd);
|
||||||
|
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFreePort()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
#if defined(__has_feature)
|
||||||
|
# if __has_feature(address_sanitizer)
|
||||||
|
int port = getAnyFreePortRandom();
|
||||||
|
# else
|
||||||
|
int port = getAnyFreePort();
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
int port = getAnyFreePort();
|
||||||
|
#endif
|
||||||
|
//
|
||||||
|
// 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...
|
||||||
|
//
|
||||||
|
if (port > 1024)
|
||||||
|
{
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} // namespace ix
|
||||||
|
|
||||||
|
|
12
test/IXGetFreePort.h
Normal file
12
test/IXGetFreePort.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* IXGetFreePort.h
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int getFreePort();
|
||||||
|
} // namespace ix
|
70
test/IXHttpServerTest.cpp
Normal file
70
test/IXHttpServerTest.cpp
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* IXSocketTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
#include "IXGetFreePort.h"
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
TEST_CASE("http server", "[httpd]")
|
||||||
|
{
|
||||||
|
SECTION("Connect to a local HTTP server")
|
||||||
|
{
|
||||||
|
int port = getFreePort();
|
||||||
|
ix::HttpServer server(port, "127.0.0.1");
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
REQUIRE(res.first);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
HttpClient httpClient;
|
||||||
|
WebSocketHttpHeaders headers;
|
||||||
|
|
||||||
|
std::string url("http://127.0.0.1:");
|
||||||
|
url += std::to_string(port);
|
||||||
|
url += "/data/foo.txt";
|
||||||
|
auto args = httpClient.createRequest(url);
|
||||||
|
|
||||||
|
args->extraHeaders = headers;
|
||||||
|
args->connectTimeout = 60;
|
||||||
|
args->transferTimeout = 60;
|
||||||
|
args->followRedirects = true;
|
||||||
|
args->maxRedirects = 10;
|
||||||
|
args->verbose = true;
|
||||||
|
args->compress = true;
|
||||||
|
args->logger = [](const std::string& msg)
|
||||||
|
{
|
||||||
|
std::cout << msg;
|
||||||
|
};
|
||||||
|
args->onProgressCallback = [](int current, int total) -> bool
|
||||||
|
{
|
||||||
|
std::cerr << "\r" << "Downloaded "
|
||||||
|
<< current << " bytes out of " << total;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto response = httpClient.get(url, args);
|
||||||
|
|
||||||
|
for (auto it : response->headers)
|
||||||
|
{
|
||||||
|
std::cerr << it.first << ": " << it.second << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Upload size: " << response->uploadSize << std::endl;
|
||||||
|
std::cerr << "Download size: " << response->downloadSize << std::endl;
|
||||||
|
std::cerr << "Status: " << response->statusCode << std::endl;
|
||||||
|
std::cerr << "Error message: " << response->errorMsg << std::endl;
|
||||||
|
|
||||||
|
REQUIRE(response->errorCode == HttpErrorCode::Ok);
|
||||||
|
REQUIRE(response->statusCode == 200);
|
||||||
|
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
@ -72,88 +72,6 @@ namespace ix
|
|||||||
Logger() << msg;
|
Logger() << msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getAnyFreePortRandom()
|
|
||||||
{
|
|
||||||
std::random_device rd;
|
|
||||||
std::uniform_int_distribution<int> dist(1024 + 1, 65535);
|
|
||||||
|
|
||||||
return dist(rd);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getAnyFreePort()
|
|
||||||
{
|
|
||||||
int defaultPort = 8090;
|
|
||||||
int sockfd;
|
|
||||||
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
|
|
||||||
{
|
|
||||||
log("Cannot compute a free port. socket error.");
|
|
||||||
return getAnyFreePortRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
int enable = 1;
|
|
||||||
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
|
|
||||||
(char*) &enable, sizeof(enable)) < 0)
|
|
||||||
{
|
|
||||||
log("Cannot compute a free port. setsockopt error.");
|
|
||||||
return getAnyFreePortRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind to port 0. This is the standard way to get a free port.
|
|
||||||
struct sockaddr_in server; // server address information
|
|
||||||
server.sin_family = AF_INET;
|
|
||||||
server.sin_port = htons(0);
|
|
||||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
|
||||||
|
|
||||||
if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
|
|
||||||
{
|
|
||||||
log("Cannot compute a free port. bind error.");
|
|
||||||
|
|
||||||
Socket::closeSocket(sockfd);
|
|
||||||
return getAnyFreePortRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in sa; // server address information
|
|
||||||
socklen_t len = sizeof(sa);
|
|
||||||
if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
|
|
||||||
{
|
|
||||||
log("Cannot compute a free port. getsockname error.");
|
|
||||||
|
|
||||||
Socket::closeSocket(sockfd);
|
|
||||||
return getAnyFreePortRandom();
|
|
||||||
}
|
|
||||||
|
|
||||||
int port = ntohs(sa.sin_port);
|
|
||||||
Socket::closeSocket(sockfd);
|
|
||||||
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getFreePort()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
#if defined(__has_feature)
|
|
||||||
# if __has_feature(address_sanitizer)
|
|
||||||
int port = getAnyFreePortRandom();
|
|
||||||
# else
|
|
||||||
int port = getAnyFreePort();
|
|
||||||
# endif
|
|
||||||
#else
|
|
||||||
int port = getAnyFreePort();
|
|
||||||
#endif
|
|
||||||
//
|
|
||||||
// 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...
|
|
||||||
//
|
|
||||||
if (port > 1024)
|
|
||||||
{
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hexDump(const std::string& prefix,
|
void hexDump(const std::string& prefix,
|
||||||
const std::string& s)
|
const std::string& s)
|
||||||
{
|
{
|
||||||
@ -200,7 +118,7 @@ namespace ix
|
|||||||
{
|
{
|
||||||
if (client != webSocket)
|
if (client != webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str);
|
client->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <ixwebsocket/IXWebSocketServer.h>
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
#include "IXGetFreePort.h"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -46,7 +47,5 @@ namespace ix
|
|||||||
|
|
||||||
void log(const std::string& msg);
|
void log(const std::string& msg);
|
||||||
|
|
||||||
int getFreePort();
|
|
||||||
|
|
||||||
bool startWebSocketEchoServer(ix::WebSocketServer& server);
|
bool startWebSocketEchoServer(ix::WebSocketServer& server);
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
52
test/IXUnityBuildsTest.cpp
Normal file
52
test/IXUnityBuildsTest.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* IXUnityBuildsTest.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2019 Machine Zone. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ixwebsocket/IXCancellationRequest.h>
|
||||||
|
#include <ixwebsocket/IXConnectionState.h>
|
||||||
|
#include <ixwebsocket/IXDNSLookup.h>
|
||||||
|
#include <ixwebsocket/IXHttp.h>
|
||||||
|
#include <ixwebsocket/IXHttpClient.h>
|
||||||
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
#include <ixwebsocket/IXNetSystem.h>
|
||||||
|
#include <ixwebsocket/IXProgressCallback.h>
|
||||||
|
#include <ixwebsocket/IXSelectInterrupt.h>
|
||||||
|
#include <ixwebsocket/IXSelectInterruptFactory.h>
|
||||||
|
#include <ixwebsocket/IXSetThreadName.h>
|
||||||
|
#include <ixwebsocket/IXSocket.h>
|
||||||
|
#include <ixwebsocket/IXSocketConnect.h>
|
||||||
|
#include <ixwebsocket/IXSocketFactory.h>
|
||||||
|
#include <ixwebsocket/IXSocketServer.h>
|
||||||
|
#include <ixwebsocket/IXUrlParser.h>
|
||||||
|
#include <ixwebsocket/IXWebSocket.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketCloseConstants.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketCloseInfo.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketErrorInfo.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketHandshake.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketHttpHeaders.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketMessage.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketMessageQueue.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketMessageType.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketOpenInfo.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketPerMessageDeflate.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateCodec.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketPerMessageDeflateOptions.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketSendInfo.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketServer.h>
|
||||||
|
#include <ixwebsocket/IXWebSocketTransport.h>
|
||||||
|
#include <ixwebsocket/LUrlParser.h>
|
||||||
|
#include <ixwebsocket/libwshandshake.hpp>
|
||||||
|
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
using namespace ix;
|
||||||
|
|
||||||
|
TEST_CASE("unity build", "[unity_build]")
|
||||||
|
{
|
||||||
|
SECTION("dummy test")
|
||||||
|
{
|
||||||
|
REQUIRE(true);
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ namespace
|
|||||||
Logger() << "New connection";
|
Logger() << "New connection";
|
||||||
connectionState->computeId();
|
connectionState->computeId();
|
||||||
Logger() << "id: " << connectionState->getId();
|
Logger() << "id: " << connectionState->getId();
|
||||||
Logger() << "Uri: " << openInfo.uri;
|
Logger() << "Uri: " << msg->openInfo.uri;
|
||||||
Logger() << "Headers:";
|
Logger() << "Headers:";
|
||||||
for (auto&& it : msg->openInfo.headers)
|
for (auto&& it : msg->openInfo.headers)
|
||||||
{
|
{
|
||||||
@ -43,11 +43,11 @@ namespace
|
|||||||
}
|
}
|
||||||
else if (msg->type == ix::WebSocketMessageType::Message)
|
else if (msg->type == ix::WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
Logger() << "Message received: " << str;
|
Logger() << "Message received: " << msg->str;
|
||||||
|
|
||||||
for (auto&& client : server.getClients())
|
for (auto&& client : server.getClients())
|
||||||
{
|
{
|
||||||
client->send(str);
|
client->send(msg->str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,25 +89,25 @@ namespace
|
|||||||
}
|
}
|
||||||
else if (msg->type == WebSocketMessageType::Error)
|
else if (msg->type == WebSocketMessageType::Error)
|
||||||
{
|
{
|
||||||
ss << "Error ! " << error.reason;
|
ss << "Error ! " << msg->errorInfo.reason;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
testDone = true;
|
testDone = true;
|
||||||
}
|
}
|
||||||
else if (msg->type == WebSocketMessageType::Pong)
|
else if (msg->type == WebSocketMessageType::Pong)
|
||||||
{
|
{
|
||||||
ss << "Received pong message " << str;
|
ss << "Received pong message " << msg->str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (msg->type == WebSocketMessageType::Ping)
|
else if (msg->type == WebSocketMessageType::Ping)
|
||||||
{
|
{
|
||||||
ss << "Received ping message " << str;
|
ss << "Received ping message " << msg->str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
}
|
}
|
||||||
else if (msg->type == WebSocketMessageType::Message)
|
else if (msg->type == WebSocketMessageType::Message)
|
||||||
{
|
{
|
||||||
REQUIRE(str.compare("Hey dude!") == 0);
|
REQUIRE(msg->str.compare("Hey dude!") == 0);
|
||||||
++receivedCount;
|
++receivedCount;
|
||||||
ss << "Received message " << str;
|
ss << "Received message " << msg->str;
|
||||||
log(ss.str());
|
log(ss.str());
|
||||||
sendNextMessage();
|
sendNextMessage();
|
||||||
}
|
}
|
||||||
|
1
test/data/foo.txt
Normal file
1
test/data/foo.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hello world
|
@ -99,8 +99,10 @@ def runCMake(sanitizer, buildDir):
|
|||||||
#generator = '"NMake Makefiles"'
|
#generator = '"NMake Makefiles"'
|
||||||
#generator = '"Visual Studio 16 2019"'
|
#generator = '"Visual Studio 16 2019"'
|
||||||
generator = '"Visual Studio 15 2017"'
|
generator = '"Visual Studio 15 2017"'
|
||||||
|
USE_VENDORED_THIRD_PARTY = 'ON'
|
||||||
else:
|
else:
|
||||||
generator = '"Unix Makefiles"'
|
generator = '"Unix Makefiles"'
|
||||||
|
USE_VENDORED_THIRD_PARTY = 'ON'
|
||||||
|
|
||||||
CMAKE_BUILD_TYPE = BUILD_TYPE
|
CMAKE_BUILD_TYPE = BUILD_TYPE
|
||||||
|
|
||||||
@ -109,7 +111,9 @@ def runCMake(sanitizer, buildDir):
|
|||||||
-B"{buildDir}" \
|
-B"{buildDir}" \
|
||||||
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
|
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
|
||||||
-DUSE_TLS=1 \
|
-DUSE_TLS=1 \
|
||||||
|
-DUSE_MBED_TLS=1 \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
|
-DUSE_VENDORED_THIRD_PARTY={USE_VENDORED_THIRD_PARTY} \
|
||||||
-G{generator}'
|
-G{generator}'
|
||||||
|
|
||||||
cmakeCmd = fmt.format(**locals())
|
cmakeCmd = fmt.format(**locals())
|
||||||
|
@ -68,6 +68,7 @@ add_executable(ws
|
|||||||
ws_cobra_to_statsd.cpp
|
ws_cobra_to_statsd.cpp
|
||||||
ws_cobra_to_sentry.cpp
|
ws_cobra_to_sentry.cpp
|
||||||
ws_snake.cpp
|
ws_snake.cpp
|
||||||
|
ws_httpd.cpp
|
||||||
ws.cpp)
|
ws.cpp)
|
||||||
|
|
||||||
target_link_libraries(ws ixwebsocket)
|
target_link_libraries(ws ixwebsocket)
|
||||||
|
@ -72,7 +72,6 @@ namespace ix
|
|||||||
std::string line;
|
std::string line;
|
||||||
std::stringstream tokenStream(stack);
|
std::stringstream tokenStream(stack);
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
std::smatch group;
|
std::smatch group;
|
||||||
|
|
||||||
while (std::getline(tokenStream, line))
|
while (std::getline(tokenStream, line))
|
||||||
@ -84,6 +83,7 @@ namespace ix
|
|||||||
const auto linenoStr = group.str(2);
|
const auto linenoStr = group.str(2);
|
||||||
const auto function = group.str(3);
|
const auto function = group.str(3);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
ss << linenoStr;
|
ss << linenoStr;
|
||||||
uint64_t lineno;
|
uint64_t lineno;
|
||||||
ss >> lineno;
|
ss >> lineno;
|
||||||
@ -97,6 +97,8 @@ namespace ix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::reverse(frames.begin(), frames.end());
|
||||||
|
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +216,10 @@ int main(int argc, char** argv)
|
|||||||
->check(CLI::ExistingPath);
|
->check(CLI::ExistingPath);
|
||||||
runApp->add_flag("-v", verbose, "Verbose");
|
runApp->add_flag("-v", verbose, "Verbose");
|
||||||
|
|
||||||
|
CLI::App* httpServerApp = app.add_subcommand("httpd", "HTTP server");
|
||||||
|
httpServerApp->add_option("--port", port, "Port");
|
||||||
|
httpServerApp->add_option("--host", hostname, "Hostname");
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
|
||||||
// pid file handling
|
// pid file handling
|
||||||
@ -313,6 +317,10 @@ int main(int argc, char** argv)
|
|||||||
redisPassword, verbose,
|
redisPassword, verbose,
|
||||||
appsConfigPath);
|
appsConfigPath);
|
||||||
}
|
}
|
||||||
|
else if (app.got_subcommand("httpd"))
|
||||||
|
{
|
||||||
|
ret = ix::ws_httpd_main(port, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
ix::uninitNetSystem();
|
ix::uninitNetSystem();
|
||||||
return ret;
|
return ret;
|
||||||
|
2
ws/ws.h
2
ws/ws.h
@ -93,4 +93,6 @@ namespace ix
|
|||||||
const std::string& redisPassword,
|
const std::string& redisPassword,
|
||||||
bool verbose,
|
bool verbose,
|
||||||
const std::string& appsConfigPath);
|
const std::string& appsConfigPath);
|
||||||
|
|
||||||
|
int ws_httpd_main(int port, const std::string& hostname);
|
||||||
} // namespace ix
|
} // namespace ix
|
||||||
|
@ -62,6 +62,7 @@ namespace ix
|
|||||||
if (client != webSocket)
|
if (client != webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str,
|
client->send(msg->str,
|
||||||
|
msg->binary,
|
||||||
[](int current, int total) -> bool
|
[](int current, int total) -> bool
|
||||||
{
|
{
|
||||||
std::cerr << "Step " << current
|
std::cerr << "Step " << current
|
||||||
|
@ -59,7 +59,7 @@ namespace ix
|
|||||||
std::cerr << "Received "
|
std::cerr << "Received "
|
||||||
<< msg->wireSize << " bytes"
|
<< msg->wireSize << " bytes"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
webSocket->send(msg->str);
|
webSocket->send(msg->str, msg->binary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
34
ws/ws_httpd.cpp
Normal file
34
ws/ws_httpd.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* ws_httpd.cpp
|
||||||
|
* Author: Benjamin Sergeant
|
||||||
|
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <ixwebsocket/IXHttpServer.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
namespace ix
|
||||||
|
{
|
||||||
|
int ws_httpd_main(int port, const std::string& hostname)
|
||||||
|
{
|
||||||
|
spdlog::info("Listening on {}:{}", hostname, port);
|
||||||
|
|
||||||
|
ix::HttpServer server(port, hostname);
|
||||||
|
|
||||||
|
auto res = server.listen();
|
||||||
|
if (!res.first)
|
||||||
|
{
|
||||||
|
std::cerr << res.second << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
server.wait();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,7 @@ namespace ix
|
|||||||
if (client != webSocket)
|
if (client != webSocket)
|
||||||
{
|
{
|
||||||
client->send(msg->str,
|
client->send(msg->str,
|
||||||
|
msg->binary,
|
||||||
[](int current, int total) -> bool
|
[](int current, int total) -> bool
|
||||||
{
|
{
|
||||||
std::cerr << "ws_transfer: Step " << current
|
std::cerr << "ws_transfer: Step " << current
|
||||||
|
Reference in New Issue
Block a user