Compare commits

...

17 Commits

Author SHA1 Message Date
967d679f17 disable Linux + mac 2019-06-23 18:33:08 -07:00
08006ddd97 try to activate CI on windows again 2019-06-23 18:32:18 -07:00
fa4aee6ddc bump docker version 2019-06-23 18:17:24 -07:00
691502d7ad Feature/httpd (#94)
* Stub code for http server

* can send a response, cannot process body yet

* write headers to the response

* remove commented code

* add simple test + set default http handler

* tweak CI + unittest

* add missing file

* rewrite http::trim in a simple way

* doc
2019-06-23 14:54:21 -07:00
43deaba547 IXDNSLookup. Uses weak pointer + smart_ptr + shared_from_this instead of static sets + mutex to handle object going away before dns lookup has resolved 2019-06-19 00:43:59 -07:00
2d02ae0f0c cobra_to_sentry / backtraces are reversed and line number is not extracted correctly 2019-06-13 10:18:40 -07:00
a8879da4fc disable windows on CI for now 2019-06-10 22:10:43 -07:00
5f4a430845 disable building ws on windows on travis 2019-06-10 22:01:19 -07:00
b9231be305 (cmake) missing find_package(Threads) on UNIX 2019-06-10 13:39:22 -07:00
7cb5cc05e4 Add -DUSE_VENDORED_THIRD_PARTY=1 to build ws 2019-06-10 13:26:41 -07:00
750a752ac0 - mbedtls and zlib are searched with find_package, and we use the vendored version if nothing is found 2019-06-10 11:18:27 -07:00
61e5f52286 - travis CI uses g++ on Linux 2019-06-09 14:27:45 -07:00
ce0b716f54 compile error in IXWebSocketMessageQTest 2019-06-09 12:25:36 -07:00
aae8e5ec65 fix IXWebSocketMessageQTest.cpp 2019-06-09 12:08:00 -07:00
2723e8466e fix changelog 2019-06-09 12:02:38 -07:00
f13c610352 update README to reflect the new API 2019-06-09 12:02:02 -07:00
55c65b08bf - 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
2019-06-09 11:56:47 -07:00
45 changed files with 1026 additions and 428 deletions

View File

@ -6,11 +6,11 @@ language: bash
matrix:
include:
# macOS
- os: osx
compiler: clang
script:
- python test/run.py
- make ws
# - os: osx
# compiler: clang
# script:
# - python test/run.py
# - make ws
# # Linux
# - os: linux
@ -23,19 +23,19 @@ matrix:
# - CXX=g++
# Clang + Linux disabled for now
- os: linux
dist: xenial
script: python test/run.py
env:
- CC=clang
- CXX=clang++
# - os: linux
# dist: xenial
# script: python test/run.py
# env:
# - CC=clang
# - CXX=clang++
# Windows
Windows
- os: windows
env:
- CMAKE_PATH="/c/Program Files/CMake/bin"
script:
- export PATH=$CMAKE_PATH:$PATH
- cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 .
- cmake --build --parallel .
# - cmake -DUSE_TLS=1 -DUSE_WS=1 -DUSE_MBED_TLS=1 -DUSE_VENDORED_THIRD_PARTY=1 .
# - cmake --build --parallel .
- python test/run.py

View File

@ -1,11 +1,21 @@
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased] - 2019-06-xx
## [5.0.0] - 2019-06-23
### 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 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
- 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

13
CMake/FindMbedTLS.cmake Normal file
View 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)

View File

@ -4,6 +4,8 @@
#
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
project(ixwebsocket C CXX)
set (CMAKE_CXX_STANDARD 14)
@ -20,64 +22,68 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
endif()
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/IXSocketServer.cpp
ixwebsocket/IXSocketConnect.cpp
ixwebsocket/IXSocketFactory.cpp
ixwebsocket/IXDNSLookup.cpp
ixwebsocket/IXCancellationRequest.cpp
ixwebsocket/IXNetSystem.cpp
ixwebsocket/IXSocketServer.cpp
ixwebsocket/IXUrlParser.cpp
ixwebsocket/IXWebSocket.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketHandshake.cpp
ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXWebSocketMessageQueue.cpp
ixwebsocket/IXWebSocketPerMessageDeflate.cpp
ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
ixwebsocket/IXWebSocketHttpHeaders.cpp
ixwebsocket/IXHttpClient.cpp
ixwebsocket/IXUrlParser.cpp
ixwebsocket/IXWebSocketServer.cpp
ixwebsocket/IXWebSocketTransport.cpp
ixwebsocket/LUrlParser.cpp
ixwebsocket/IXSelectInterrupt.cpp
ixwebsocket/IXSelectInterruptFactory.cpp
ixwebsocket/IXConnectionState.cpp
ixwebsocket/IXWebSocketCloseConstants.cpp
ixwebsocket/IXWebSocketMessageQueue.cpp
)
set( IXWEBSOCKET_HEADERS
ixwebsocket/IXSocket.h
ixwebsocket/IXSocketServer.h
ixwebsocket/IXSocketConnect.h
ixwebsocket/IXSocketFactory.h
ixwebsocket/IXSetThreadName.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXCancellationRequest.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXDNSLookup.h
ixwebsocket/IXHttp.h
ixwebsocket/IXHttpClient.h
ixwebsocket/IXHttpServer.h
ixwebsocket/IXNetSystem.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/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketCloseConstants.h
ixwebsocket/IXWebSocketCloseInfo.h
ixwebsocket/IXWebSocketOpenInfo.h
ixwebsocket/IXWebSocketMessageType.h
ixwebsocket/IXWebSocketErrorInfo.h
ixwebsocket/IXWebSocketHandshake.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/IXWebSocketMessage.h
ixwebsocket/IXWebSocketMessageQueue.h
ixwebsocket/IXWebSocketMessageType.h
ixwebsocket/IXWebSocketOpenInfo.h
ixwebsocket/IXWebSocketPerMessageDeflate.h
ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
ixwebsocket/IXWebSocketHttpHeaders.h
ixwebsocket/libwshandshake.hpp
ixwebsocket/IXHttpClient.h
ixwebsocket/IXUrlParser.h
ixwebsocket/IXWebSocketSendInfo.h
ixwebsocket/IXWebSocketServer.h
ixwebsocket/IXWebSocketTransport.h
ixwebsocket/LUrlParser.h
ixwebsocket/IXSelectInterrupt.h
ixwebsocket/IXSelectInterruptFactory.h
ixwebsocket/IXConnectionState.h
ixwebsocket/IXWebSocketCloseConstants.h
ixwebsocket/IXWebSocketMessageQueue.h
ixwebsocket/libwshandshake.hpp
)
if (UNIX)
@ -132,6 +138,11 @@ if (APPLE AND USE_TLS AND NOT USE_MBED_TLS)
target_link_libraries(ixwebsocket "-framework foundation" "-framework security")
endif()
if (UNIX)
find_package(Threads)
target_link_libraries(ixwebsocket ${CMAKE_THREAD_LIBS_INIT})
endif()
if (USE_OPEN_SSL)
find_package(OpenSSL REQUIRED)
add_definitions(${OPENSSL_DEFINITIONS})
@ -141,25 +152,28 @@ if (USE_OPEN_SSL)
endif()
if (USE_MBED_TLS)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)
include_directories(third_party/mbedtls/include)
if (USE_VENDORED_THIRD_PARTY)
set (ENABLE_PROGRAMS OFF)
add_subdirectory(third_party/mbedtls)
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()
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)
include_directories(third_party/zlib ${CMAKE_CURRENT_BINARY_DIR}/third_party/zlib)
target_link_libraries(ixwebsocket zlibstatic wsock32 ws2_32)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
else()
# gcc/Linux needs -pthread
find_package(Threads)
target_link_libraries(ixwebsocket
z ${CMAKE_THREAD_LIBS_INIT})
endif()
set( IXWEBSOCKET_INCLUDE_DIRS
@ -171,7 +185,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_compile_options(ixwebsocket PRIVATE /MP)
endif()
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
target_include_directories(ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS})
set_target_properties(ixwebsocket PROPERTIES PUBLIC_HEADER "${IXWEBSOCKET_HEADERS}")

View File

@ -1 +1 @@
3.1.2
5.0.0

125
README.md
View File

@ -4,7 +4,7 @@
## 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
* iOS
@ -33,27 +33,22 @@ webSocket.disablePerMessageDeflate();
// Setup a callback to be fired when a message or an event (open, close, error) is received
webSocket.setOnMessageCallback(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[](const ix::WebSocketMessagePtr& msg)
{
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
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");
// The message can be sent in TEXT mode
webSocket.sendText("hello again");
// The message can be sent in BINARY mode (useful if you send MsgPack data for example)
webSocket.sendBinary("some serialized binary data");
// ... finally ...
@ -73,14 +68,9 @@ server.setOnConnectionCallback(
std::shared_ptr<ConnectionState> connectionState)
{
webSocket->setOnMessageCallback(
[webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[webSocket, connectionState, &server](const ix::WebSocketMessagePtr msg)
{
if (messageType == ix::WebSocketMessageType::Open)
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cerr << "New connection" << std::endl;
@ -91,19 +81,21 @@ server.setOnConnectionCallback(
std::cerr << "id: " << connectionState->getId() << std::endl;
// 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;
for (auto it : openInfo.headers)
for (auto it : msg->openInfo.headers)
{
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
// 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
```
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
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(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocketMessageType::Open)
if (msg->type == ix::WebSocketMessageType::Open)
{
std::cout << "send greetings" << std::endl;
// Headers can be inspected (pairs of string/string)
std::cout << "Handshake Headers:" << std::endl;
for (auto it : headers)
for (auto it : msg->headers)
{
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;
// The server can send an explicit code and reason for closing.
// This data can be accessed through the closeInfo object.
std::cout << closeInfo.code << std::endl;
std::cout << closeInfo.reason << std::endl;
std::cout << msg->closeInfo.code << 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(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocketMessageType::Error)
if (msg->type == ix::WebSocketMessageType::Error)
{
std::stringstream ss;
ss << "Error: " << error.reason << std::endl;
ss << "#retries: " << event.retries << std::endl;
ss << "Wait time(ms): " << event.wait_time << std::endl;
ss << "HTTP Status: " << event.http_status << std::endl;
ss << "Error: " << msg->errorInfo.reason << std::endl;
ss << "#retries: " << msg->eventInfo.retries << std::endl;
ss << "Wait time(ms): " << msg->eventInfo.wait_time << std::endl;
ss << "HTTP Status: " << msg->eventInfo.http_status << 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(
[](ix::WebSocketMessageType messageType,
const std::string& str,
size_t wireSize,
const ix::WebSocketErrorInfo& error,
const ix::WebSocketOpenInfo& openInfo,
const ix::WebSocketCloseInfo& closeInfo)
[](const ix::WebSocketMessagePtr& msg)
{
if (messageType == ix::WebSocketMessageType::Ping ||
messageType == ix::WebSocketMessageType::Pong)
if (msg->type == ix::WebSocketMessageType::Ping ||
msg->type == ix::WebSocketMessageType::Pong)
{
std::cout << "pong data: " << str << std::endl;
std::cout << "pong data: " << msg->str << std::endl;
}
}
);

View File

@ -14,27 +14,15 @@ namespace ix
{
const int64_t DNSLookup::kDefaultWait = 10; // ms
std::atomic<uint64_t> DNSLookup::_nextId(0);
std::set<uint64_t> DNSLookup::_activeJobs;
std::mutex DNSLookup::_activeJobsMutex;
DNSLookup::DNSLookup(const std::string& hostname, int port, int64_t wait) :
_port(port),
_wait(wait),
_res(nullptr),
_done(false),
_id(_nextId++)
_done(false)
{
setHostname(hostname);
}
DNSLookup::~DNSLookup()
{
// Remove this job from the active jobs list
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.erase(_id);
}
struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
int port,
std::string& errMsg)
@ -94,17 +82,14 @@ namespace ix
// if you need a second lookup.
}
// Record job in the active Job set
{
std::lock_guard<std::mutex> lock(_activeJobsMutex);
_activeJobs.insert(_id);
}
//
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
_thread = std::thread(&DNSLookup::run, this, _id, getHostname(), _port);
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
_thread = std::thread(&DNSLookup::run, this, self, getHostname(), _port);
_thread.detach();
std::unique_lock<std::mutex> lock(_conditionVariableMutex);
@ -138,7 +123,7 @@ namespace ix
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
// gone, so we use temporary variables (res) or we pass in by copy everything that
@ -146,21 +131,15 @@ namespace ix
std::string errMsg;
struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
// if this isn't an active job, and the control thread is gone
// there is nothing to do, and we don't want to touch the defunct
// object data structure such as _errMsg or _condition
std::lock_guard<std::mutex> lock(_activeJobsMutex);
if (_activeJobs.count(id) == 0)
if (self.lock())
{
return;
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_condition.notify_one();
_done = true;
}
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_condition.notify_one();
_done = true;
}
void DNSLookup::setHostname(const std::string& hostname)

View File

@ -16,16 +16,17 @@
#include <set>
#include <string>
#include <thread>
#include <memory>
struct addrinfo;
namespace ix
{
class DNSLookup
class DNSLookup : public std::enable_shared_from_this<DNSLookup>
{
public:
DNSLookup(const std::string& hostname, int port, int64_t wait = DNSLookup::kDefaultWait);
~DNSLookup();
~DNSLookup() = default;
struct addrinfo* resolve(std::string& errMsg,
const CancellationRequest& isCancellationRequested,
@ -41,7 +42,7 @@ namespace ix
int port,
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);
const std::string& getHostname();
@ -69,11 +70,6 @@ namespace ix
std::condition_variable _condition;
std::mutex _conditionVariableMutex;
uint64_t _id;
static std::atomic<uint64_t> _nextId;
static std::set<uint64_t> _activeJobs;
static std::mutex _activeJobsMutex;
const static int64_t kDefaultWait;
};
} // namespace ix

138
ixwebsocket/IXHttp.cpp Normal file
View 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
View 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);
};
}

View File

@ -118,6 +118,7 @@ namespace ix
int code = 0;
WebSocketHttpHeaders headers;
std::string payload;
std::string description;
std::string protocol, host, path, query;
int port;
@ -126,9 +127,9 @@ namespace ix
{
std::stringstream ss;
ss << "Cannot parse url: " << url;
return std::make_shared<HttpResponse>(code, HttpErrorCode::UrlMalformed,
headers, payload, ss.str(),
uploadSize, downloadSize);
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::UrlMalformed,
headers, payload, ss.str(),
uploadSize, downloadSize);
}
bool tls = protocol == "https";
@ -137,9 +138,9 @@ namespace ix
if (!_socket)
{
return std::make_shared<HttpResponse>(code, HttpErrorCode::CannotCreateSocket,
headers, payload, errorMsg,
uploadSize, downloadSize);
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::CannotCreateSocket,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
// Build request string
@ -200,7 +201,7 @@ namespace ix
{
std::stringstream ss;
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(),
uploadSize, downloadSize);
}
@ -226,7 +227,7 @@ namespace ix
if (!_socket->writeBytes(req, isCancellationRequested))
{
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,
uploadSize, downloadSize);
}
@ -240,7 +241,7 @@ namespace ix
if (!lineValid)
{
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,
uploadSize, downloadSize);
}
@ -255,7 +256,7 @@ namespace ix
if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
{
std::string errorMsg("Cannot parse response code from status line");
return std::make_shared<HttpResponse>(code, HttpErrorCode::MissingStatus,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::MissingStatus,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@ -267,7 +268,7 @@ namespace ix
if (!headersValid)
{
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,
uploadSize, downloadSize);
}
@ -278,7 +279,7 @@ namespace ix
if (headers.find("Location") == headers.end())
{
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,
uploadSize, downloadSize);
}
@ -287,7 +288,7 @@ namespace ix
{
std::stringstream ss;
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(),
uploadSize, downloadSize);
}
@ -299,7 +300,7 @@ namespace ix
if (verb == "HEAD")
{
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}
@ -320,7 +321,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@ -338,7 +339,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@ -365,7 +366,7 @@ namespace ix
if (!chunkResult.first)
{
errorMsg = "Cannot read chunk";
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@ -376,7 +377,7 @@ namespace ix
if (!lineResult.first)
{
return std::make_shared<HttpResponse>(code, HttpErrorCode::ChunkReadError,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::ChunkReadError,
headers, payload, errorMsg,
uploadSize, downloadSize);
}
@ -391,7 +392,7 @@ namespace ix
else
{
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,
uploadSize, downloadSize);
}
@ -405,14 +406,14 @@ namespace ix
if (!gzipInflate(payload, decompressedPayload))
{
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,
uploadSize, downloadSize);
}
payload = decompressedPayload;
}
return std::make_shared<HttpResponse>(code, HttpErrorCode::Ok,
return std::make_shared<HttpResponse>(code, description, HttpErrorCode::Ok,
headers, payload, std::string(),
uploadSize, downloadSize);
}

View File

@ -8,6 +8,7 @@
#include "IXSocket.h"
#include "IXWebSocketHttpHeaders.h"
#include "IXHttp.h"
#include <algorithm>
#include <atomic>
#include <condition_variable>
@ -20,78 +21,6 @@
namespace ix
{
enum class HttpErrorCode : int
{
Ok = 0,
CannotConnect = 1,
Timeout = 2,
Gzip = 3,
UrlMalformed = 4,
CannotCreateSocket = 5,
SendError = 6,
ReadError = 7,
CannotReadStatusLine = 8,
MissingStatus = 9,
HeaderParsingError = 10,
MissingLocation = 11,
TooManyRedirects = 12,
ChunkReadError = 13,
CannotReadBody = 14,
Invalid = 100
};
struct HttpResponse
{
int statusCode;
HttpErrorCode errorCode;
WebSocketHttpHeaders headers;
std::string payload;
std::string errorMsg;
uint64_t uploadSize;
uint64_t downloadSize;
HttpResponse(int s = 0,
const HttpErrorCode& c = HttpErrorCode::Ok,
const WebSocketHttpHeaders& h = WebSocketHttpHeaders(),
const std::string& p = std::string(),
const std::string& e = std::string(),
uint64_t u = 0,
uint64_t d = 0)
: statusCode(s)
, errorCode(c)
, headers(h)
, payload(p)
, errorMsg(e)
, uploadSize(u)
, downloadSize(d)
{
;
}
};
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
using HttpParameters = std::map<std::string, std::string>;
using Logger = std::function<void(const std::string&)>;
using OnResponseCallback = std::function<void(const HttpResponsePtr&)>;
struct HttpRequestArgs
{
std::string url;
std::string verb;
WebSocketHttpHeaders extraHeaders;
std::string body;
int connectTimeout;
int transferTimeout;
bool followRedirects;
int maxRedirects;
bool verbose;
bool compress;
Logger logger;
OnProgressCallback onProgressCallback;
};
using HttpRequestArgsPtr = std::shared_ptr<HttpRequestArgs>;
class HttpClient
{
public:

View 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);
}
);
}
}

View 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

View File

@ -232,19 +232,28 @@ namespace ix
bool Socket::writeBytes(const std::string& str,
const CancellationRequest& isCancellationRequested)
{
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
while (true)
{
if (isCancellationRequested && isCancellationRequested()) return false;
char* buffer = const_cast<char*>(str.c_str());
int len = (int) str.size();
ssize_t ret = send(buffer, len);
// We wrote some bytes, as needed, all good.
if (ret > 0)
{
return ret == len;
if (ret == len)
{
return true;
}
else
{
buffer += ret;
len -= ret;
continue;
}
}
// There is possibly something to be writen, try again
else if (ret < 0 && Socket::isWaitNeeded())

View File

@ -128,8 +128,8 @@ namespace ix
//
// First do DNS resolution
//
DNSLookup dnsLookup(hostname, port);
struct addrinfo *res = dnsLookup.resolve(errMsg, isCancellationRequested);
auto dnsLookup = std::make_shared<DNSLookup>(hostname, port);
struct addrinfo *res = dnsLookup->resolve(errMsg, isCancellationRequested);
if (res == nullptr)
{
return -1;

View File

@ -11,7 +11,6 @@
#include <iostream>
#include <sstream>
#include <future>
#include <string.h>
#include <assert.h>

View File

@ -325,8 +325,8 @@ namespace ix
WebSocketMessageType webSocketMessageType;
switch (messageKind)
{
default:
case WebSocketTransport::MessageKind::MSG:
case WebSocketTransport::MessageKind::MSG_TEXT:
case WebSocketTransport::MessageKind::MSG_BINARY:
{
webSocketMessageType = WebSocketMessageType::Message;
} break;
@ -350,11 +350,13 @@ namespace ix
WebSocketErrorInfo webSocketErrorInfo;
webSocketErrorInfo.decompressionError = decompressionError;
bool binary = messageKind == WebSocketTransport::MessageKind::MSG_BINARY;
_onMessageCallback(
std::make_shared<WebSocketMessage>(
webSocketMessageType, msg, wireSize,
webSocketErrorInfo, WebSocketOpenInfo(),
WebSocketCloseInfo()));
WebSocketCloseInfo(), binary));
WebSocket::invokeTrafficTrackerCallback(msg.size(), true);
});
@ -385,8 +387,8 @@ namespace ix
}
WebSocketSendInfo WebSocket::send(const std::string& data,
const OnProgressCallback& onProgressCallback,
bool binary)
bool binary,
const OnProgressCallback& onProgressCallback)
{
return sendMessage(data,
(binary) ? SendMessageKind::Binary: SendMessageKind::Text,

View File

@ -66,8 +66,8 @@ namespace ix
// send is in binary mode by default
WebSocketSendInfo send(const std::string& data,
const OnProgressCallback& onProgressCallback = nullptr,
bool binary = true);
bool binary = false,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendBinary(const std::string& text,
const OnProgressCallback& onProgressCallback = nullptr);
WebSocketSendInfo sendText(const std::string& text,

View File

@ -7,6 +7,7 @@
#include "IXWebSocketHandshake.h"
#include "IXSocketConnect.h"
#include "IXUrlParser.h"
#include "IXHttp.h"
#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)
{
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 alphanum =
@ -294,7 +252,7 @@ namespace ix
}
// 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 uri = std::get<1>(requestLine);
auto httpVersion = std::get<2>(requestLine);

View File

@ -64,8 +64,6 @@ namespace ix
// Parse HTTP headers
WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
std::string trim(const std::string& str);
bool insensitiveStringCompare(const std::string& a, const std::string& b);
std::atomic<bool>& _requestInitCancellation;

View File

@ -31,13 +31,15 @@ namespace ix
size_t w,
WebSocketErrorInfo e,
WebSocketOpenInfo o,
WebSocketCloseInfo c)
WebSocketCloseInfo c,
bool b = false)
: type(t)
, str(std::move(s))
, wireSize(w)
, errorInfo(e)
, openInfo(o)
, closeInfo(c)
, binary(b)
{
;
}

View File

@ -19,12 +19,12 @@
namespace ix
{
using OnConnectionCallback =
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
class WebSocketServer final : public SocketServer
{
public:
using OnConnectionCallback =
std::function<void(std::shared_ptr<WebSocket>, std::shared_ptr<ConnectionState>)>;
WebSocketServer(int port = SocketServer::kDefaultPort,
const std::string& host = SocketServer::kDefaultHost,
int backlog = SocketServer::kDefaultTcpBacklog,

View File

@ -542,12 +542,17 @@ namespace ix
) {
unmaskReceiveBuffer(ws);
MessageKind messageKind =
(ws.opcode == wsheader_type::TEXT_FRAME)
? MessageKind::MSG_TEXT
: MessageKind::MSG_BINARY;
//
// Usual case. Small unfragmented messages
//
if (ws.fin && _chunks.empty())
{
emitMessage(MessageKind::MSG,
emitMessage(messageKind,
std::string(_rxbuf.begin()+ws.header_size,
_rxbuf.begin()+ws.header_size+(size_t) ws.N),
ws,
@ -567,7 +572,7 @@ namespace ix
_rxbuf.begin()+ws.header_size+(size_t)ws.N));
if (ws.fin)
{
emitMessage(MessageKind::MSG, getMergedChunks(), ws, onMessageCallback);
emitMessage(messageKind, getMergedChunks(), ws, onMessageCallback);
_chunks.clear();
}
else

View File

@ -50,7 +50,8 @@ namespace ix
enum class MessageKind
{
MSG,
MSG_TEXT,
MSG_BINARY,
PING,
PONG,
FRAGMENT

View File

@ -9,10 +9,10 @@ install: brew
# on osx it is good practice to make /usr/local user writable
# sudo chown -R `whoami`/staff /usr/local
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:
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:
xargs rm -fv < build/install_manifest.txt

View File

@ -7,7 +7,7 @@ project (ixwebsocket_unittest)
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})
find_package(Sanitizers)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread")
@ -28,6 +28,7 @@ include_directories(
set (SOURCES
test_runner.cpp
IXTest.cpp
IXGetFreePort.cpp
../third_party/msgpack11/msgpack11.cpp
../ws/ixcore/utils/IXCoreLogger.cpp
@ -38,6 +39,8 @@ set (SOURCES
IXUrlParserTest.cpp
IXWebSocketServerTest.cpp
IXHttpClientTest.cpp
IXHttpServerTest.cpp
IXUnityBuildsTest.cpp
)
# Some unittest don't work on windows yet
@ -64,7 +67,7 @@ endif()
add_executable(ixwebsocket_unittest ${SOURCES})
if (NOT WIN32)
if (MAC)
add_sanitizers(ixwebsocket_unittest)
endif()

View File

@ -17,33 +17,33 @@ TEST_CASE("dns", "[net]")
{
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;
struct addrinfo* res;
res = dnsLookup.resolve(errMsg, [] { return false; });
res = dnsLookup->resolve(errMsg, [] { return false; });
std::cerr << "Error message: " << errMsg << std::endl;
REQUIRE(res != nullptr);
}
SECTION("Test resolving a non-existing hostname")
{
DNSLookup dnsLookup("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
auto dnsLookup = std::make_shared<DNSLookup>("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
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;
REQUIRE(res == nullptr);
}
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;
// 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;
REQUIRE(res == nullptr);
}

93
test/IXGetFreePort.cpp Normal file
View 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
View 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
View 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();
}
}

View File

@ -72,88 +72,6 @@ namespace ix
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,
const std::string& s)
{
@ -200,7 +118,7 @@ namespace ix
{
if (client != webSocket)
{
client->send(msg->str);
client->send(msg->str, msg->binary);
}
}
}

View File

@ -8,6 +8,7 @@
#include <iostream>
#include <ixwebsocket/IXWebSocketServer.h>
#include "IXGetFreePort.h"
#include <mutex>
#include <spdlog/spdlog.h>
#include <sstream>
@ -46,7 +47,5 @@ namespace ix
void log(const std::string& msg);
int getFreePort();
bool startWebSocketEchoServer(ix::WebSocketServer& server);
} // namespace ix

View 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);
}
}

View File

@ -30,7 +30,7 @@ namespace
Logger() << "New connection";
connectionState->computeId();
Logger() << "id: " << connectionState->getId();
Logger() << "Uri: " << openInfo.uri;
Logger() << "Uri: " << msg->openInfo.uri;
Logger() << "Headers:";
for (auto&& it : msg->openInfo.headers)
{
@ -43,11 +43,11 @@ namespace
}
else if (msg->type == ix::WebSocketMessageType::Message)
{
Logger() << "Message received: " << str;
Logger() << "Message received: " << msg->str;
for (auto&& client : server.getClients())
{
client->send(str);
client->send(msg->str);
}
}
}
@ -89,25 +89,25 @@ namespace
}
else if (msg->type == WebSocketMessageType::Error)
{
ss << "Error ! " << error.reason;
ss << "Error ! " << msg->errorInfo.reason;
log(ss.str());
testDone = true;
}
else if (msg->type == WebSocketMessageType::Pong)
{
ss << "Received pong message " << str;
ss << "Received pong message " << msg->str;
log(ss.str());
}
else if (msg->type == WebSocketMessageType::Ping)
{
ss << "Received ping message " << str;
ss << "Received ping message " << msg->str;
log(ss.str());
}
else if (msg->type == WebSocketMessageType::Message)
{
REQUIRE(str.compare("Hey dude!") == 0);
REQUIRE(msg->str.compare("Hey dude!") == 0);
++receivedCount;
ss << "Received message " << str;
ss << "Received message " << msg->str;
log(ss.str());
sendNextMessage();
}

1
test/data/foo.txt Normal file
View File

@ -0,0 +1 @@
Hello world

View File

@ -99,8 +99,10 @@ def runCMake(sanitizer, buildDir):
#generator = '"NMake Makefiles"'
#generator = '"Visual Studio 16 2019"'
generator = '"Visual Studio 15 2017"'
USE_VENDORED_THIRD_PARTY = 'ON'
else:
generator = '"Unix Makefiles"'
USE_VENDORED_THIRD_PARTY = 'ON'
CMAKE_BUILD_TYPE = BUILD_TYPE
@ -109,7 +111,9 @@ def runCMake(sanitizer, buildDir):
-B"{buildDir}" \
-DCMAKE_BUILD_TYPE={CMAKE_BUILD_TYPE} \
-DUSE_TLS=1 \
-DUSE_MBED_TLS=1 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DUSE_VENDORED_THIRD_PARTY={USE_VENDORED_THIRD_PARTY} \
-G{generator}'
cmakeCmd = fmt.format(**locals())

View File

@ -68,6 +68,7 @@ add_executable(ws
ws_cobra_to_statsd.cpp
ws_cobra_to_sentry.cpp
ws_snake.cpp
ws_httpd.cpp
ws.cpp)
target_link_libraries(ws ixwebsocket)

View File

@ -72,7 +72,6 @@ namespace ix
std::string line;
std::stringstream tokenStream(stack);
std::stringstream ss;
std::smatch group;
while (std::getline(tokenStream, line))
@ -84,6 +83,7 @@ namespace ix
const auto linenoStr = group.str(2);
const auto function = group.str(3);
std::stringstream ss;
ss << linenoStr;
uint64_t lineno;
ss >> lineno;
@ -97,6 +97,8 @@ namespace ix
}
}
std::reverse(frames.begin(), frames.end());
return frames;
}

View File

@ -216,6 +216,10 @@ int main(int argc, char** argv)
->check(CLI::ExistingPath);
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);
// pid file handling
@ -313,6 +317,10 @@ int main(int argc, char** argv)
redisPassword, verbose,
appsConfigPath);
}
else if (app.got_subcommand("httpd"))
{
ret = ix::ws_httpd_main(port, hostname);
}
ix::uninitNetSystem();
return ret;

View File

@ -93,4 +93,6 @@ namespace ix
const std::string& redisPassword,
bool verbose,
const std::string& appsConfigPath);
int ws_httpd_main(int port, const std::string& hostname);
} // namespace ix

View File

@ -62,6 +62,7 @@ namespace ix
if (client != webSocket)
{
client->send(msg->str,
msg->binary,
[](int current, int total) -> bool
{
std::cerr << "Step " << current

View File

@ -59,7 +59,7 @@ namespace ix
std::cerr << "Received "
<< msg->wireSize << " bytes"
<< std::endl;
webSocket->send(msg->str);
webSocket->send(msg->str, msg->binary);
}
}
);

34
ws/ws_httpd.cpp Normal file
View 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;
}
}

View File

@ -62,6 +62,7 @@ namespace ix
if (client != webSocket)
{
client->send(msg->str,
msg->binary,
[](int current, int total) -> bool
{
std::cerr << "ws_transfer: Step " << current