Compare commits
	
		
			91 Commits
		
	
	
		
			user/bserg
			...
			feature/us
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5e1a4541bf | ||
| 
						 | 
					2e9c610ac9 | ||
| 
						 | 
					eb063ec60a | ||
| 
						 | 
					37fb14646d | ||
| 
						 | 
					ae543518d3 | ||
| 
						 | 
					c865d64608 | ||
| 
						 | 
					3004422cb6 | ||
| 
						 | 
					0c46a17443 | ||
| 
						 | 
					497373d976 | ||
| 
						 | 
					91198aca0d | ||
| 
						 | 
					b17a5e5f0b | ||
| 
						 | 
					3f0ef59f65 | ||
| 
						 | 
					1e96edc293 | ||
| 
						 | 
					0afb77393b | ||
| 
						 | 
					7614b642bb | ||
| 
						 | 
					bc89580dfe | ||
| 
						 | 
					358ae13a88 | ||
| 
						 | 
					ccf9dcba70 | ||
| 
						 | 
					94604fad61 | ||
| 
						 | 
					5c4cc7c50d | ||
| 
						 | 
					9ed961ec06 | ||
| 
						 | 
					e6bd8cc8c4 | ||
| 
						 | 
					ee25bd0f92 | ||
| 
						 | 
					e77b9176f3 | ||
| 
						 | 
					afe8b966ad | ||
| 
						 | 
					310724c961 | ||
| 
						 | 
					ceba8ae620 | ||
| 
						 | 
					fead661ab7 | ||
| 
						 | 
					9c8c17f577 | ||
| 
						 | 
					a04f83930f | ||
| 
						 | 
					c421d19800 | ||
| 
						 | 
					521f02c90e | ||
| 
						 | 
					c86b6074f2 | ||
| 
						 | 
					d5d1a2c5f4 | ||
| 
						 | 
					2a90e3f478 | ||
| 
						 | 
					1d49ba41ea | ||
| 
						 | 
					e1de1f6682 | ||
| 
						 | 
					47ed5e4d4d | ||
| 
						 | 
					d77f6f5659 | ||
| 
						 | 
					05f0045d5d | ||
| 
						 | 
					c4afb84f6e | ||
| 
						 | 
					b0b2f9b6d2 | ||
| 
						 | 
					ee37feb489 | ||
| 
						 | 
					6b8337596f | ||
| 
						 | 
					250665b92e | ||
| 
						 | 
					86b83c889e | ||
| 
						 | 
					c9c657c07b | ||
| 
						 | 
					4f2babaf54 | ||
| 
						 | 
					1b03bf4555 | ||
| 
						 | 
					977b995af9 | ||
| 
						 | 
					310ab990bd | ||
| 
						 | 
					d6b49b54d4 | ||
| 
						 | 
					f00cf39462 | ||
| 
						 | 
					18550cf1cb | ||
| 
						 | 
					168918f807 | ||
| 
						 | 
					2750df8aa7 | ||
| 
						 | 
					d6597d9f52 | ||
| 
						 | 
					892ea375e3 | ||
| 
						 | 
					03abe77b5f | ||
| 
						 | 
					e46eb8aa49 | ||
| 
						 | 
					2c4862e0f1 | ||
| 
						 | 
					fd69efa45c | ||
| 
						 | 
					e8aa15917f | ||
| 
						 | 
					b3d77f8902 | ||
| 
						 | 
					9c3b0b08ec | ||
| 
						 | 
					fe7d94194c | ||
| 
						 | 
					d6c26d6aa8 | ||
| 
						 | 
					8a74ddcd13 | ||
| 
						 | 
					18e7189a07 | ||
| 
						 | 
					785dd42c84 | ||
| 
						 | 
					0cff5065d9 | ||
| 
						 | 
					e881b82511 | ||
| 
						 | 
					d5551e5d68 | ||
| 
						 | 
					e8583000b8 | ||
| 
						 | 
					d642ef1a89 | ||
| 
						 | 
					2df118022d | ||
| 
						 | 
					95457c8f4c | ||
| 
						 | 
					0a45b7787f | ||
| 
						 | 
					b8c397e180 | ||
| 
						 | 
					90105fa2b3 | ||
| 
						 | 
					24859fef8a | ||
| 
						 | 
					73d7280723 | ||
| 
						 | 
					262de49c3c | ||
| 
						 | 
					3a77e96a05 | ||
| 
						 | 
					505dd6d50f | ||
| 
						 | 
					3f8027b65c | ||
| 
						 | 
					0f2c765f45 | ||
| 
						 | 
					49077f8f44 | ||
| 
						 | 
					6a23b8530f | ||
| 
						 | 
					ae841af91a | ||
| 
						 | 
					44f38849b2 | 
							
								
								
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
build
 | 
			
		||||
							
								
								
									
										1
									
								
								examples/ping_pong/.gitignore → .gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,2 +1 @@
 | 
			
		||||
venv
 | 
			
		||||
build
 | 
			
		||||
							
								
								
									
										0
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
							
								
								
									
										14
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						@@ -2,8 +2,16 @@ language: cpp
 | 
			
		||||
dist: xenial
 | 
			
		||||
 | 
			
		||||
compiler:
 | 
			
		||||
  - gcc
 | 
			
		||||
  - clang
 | 
			
		||||
#   - gcc
 | 
			
		||||
os:
 | 
			
		||||
  - linux
 | 
			
		||||
  - osx
 | 
			
		||||
 | 
			
		||||
    # os: osx
 | 
			
		||||
script: make test
 | 
			
		||||
matrix:
 | 
			
		||||
  exclude:
 | 
			
		||||
    # GCC fails on recent Travis OSX images.
 | 
			
		||||
    - compiler: gcc
 | 
			
		||||
      os: osx
 | 
			
		||||
 | 
			
		||||
script: python test/run.py
 | 
			
		||||
 
 | 
			
		||||
@@ -10,15 +10,20 @@ set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
set (CXX_STANDARD_REQUIRED ON)
 | 
			
		||||
set (CMAKE_CXX_EXTENSIONS OFF)
 | 
			
		||||
 | 
			
		||||
# -Wshorten-64-to-32 does not work with clang
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
 | 
			
		||||
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wshorten-64-to-32")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set( IXWEBSOCKET_SOURCES
 | 
			
		||||
    ixwebsocket/IXEventFd.cpp
 | 
			
		||||
    ixwebsocket/IXSocket.cpp
 | 
			
		||||
    ixwebsocket/IXSocketServer.cpp
 | 
			
		||||
    ixwebsocket/IXSocketConnect.cpp
 | 
			
		||||
    ixwebsocket/IXSocketFactory.cpp
 | 
			
		||||
    ixwebsocket/IXDNSLookup.cpp
 | 
			
		||||
    ixwebsocket/IXCancellationRequest.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocket.cpp
 | 
			
		||||
@@ -28,16 +33,24 @@ set( IXWEBSOCKET_SOURCES
 | 
			
		||||
    ixwebsocket/IXWebSocketPerMessageDeflate.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
 | 
			
		||||
    ixwebsocket/IXWebSocketHttpHeaders.cpp
 | 
			
		||||
    ixwebsocket/IXHttpClient.cpp
 | 
			
		||||
    ixwebsocket/IXUrlParser.cpp
 | 
			
		||||
    ixwebsocket/IXSelectInterrupt.cpp
 | 
			
		||||
    ixwebsocket/IXSelectInterruptPipe.cpp
 | 
			
		||||
    ixwebsocket/IXSelectInterruptFactory.cpp
 | 
			
		||||
    ixwebsocket/IXConnectionState.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set( IXWEBSOCKET_HEADERS
 | 
			
		||||
    ixwebsocket/IXEventFd.h
 | 
			
		||||
    ixwebsocket/IXSocket.h
 | 
			
		||||
    ixwebsocket/IXSocketServer.h
 | 
			
		||||
    ixwebsocket/IXSocketConnect.h
 | 
			
		||||
    ixwebsocket/IXSocketFactory.h
 | 
			
		||||
    ixwebsocket/IXSetThreadName.h
 | 
			
		||||
    ixwebsocket/IXDNSLookup.h
 | 
			
		||||
    ixwebsocket/IXCancellationRequest.h
 | 
			
		||||
    ixwebsocket/IXProgressCallback.h
 | 
			
		||||
    ixwebsocket/IXWebSocket.h
 | 
			
		||||
    ixwebsocket/IXWebSocketServer.h
 | 
			
		||||
    ixwebsocket/IXWebSocketTransport.h
 | 
			
		||||
@@ -49,6 +62,12 @@ set( IXWEBSOCKET_HEADERS
 | 
			
		||||
    ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
 | 
			
		||||
    ixwebsocket/IXWebSocketHttpHeaders.h
 | 
			
		||||
    ixwebsocket/libwshandshake.hpp
 | 
			
		||||
    ixwebsocket/IXHttpClient.h
 | 
			
		||||
    ixwebsocket/IXUrlParser.h
 | 
			
		||||
    ixwebsocket/IXSelectInterrupt.h
 | 
			
		||||
    ixwebsocket/IXSelectInterruptPipe.h
 | 
			
		||||
    ixwebsocket/IXSelectInterruptFactory.h
 | 
			
		||||
    ixwebsocket/IXConnectionState.h
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Platform specific code
 | 
			
		||||
@@ -58,6 +77,8 @@ elseif (WIN32)
 | 
			
		||||
    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
 | 
			
		||||
else()
 | 
			
		||||
    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
 | 
			
		||||
    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/IXSelectInterruptEventFd.cpp)
 | 
			
		||||
    list( APPEND IXWEBSOCKET_HEADERS ixwebsocket/IXSelectInterruptEventFd.h)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (USE_TLS)
 | 
			
		||||
@@ -112,3 +133,6 @@ set( IXWEBSOCKET_INCLUDE_DIRS
 | 
			
		||||
    .
 | 
			
		||||
    ../../shared/OpenSSL/include)
 | 
			
		||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
 | 
			
		||||
 | 
			
		||||
add_subdirectory(ws)
 | 
			
		||||
add_subdirectory(third_party/cpp_redis)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								DOCKER_VERSION
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
1.3.2
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
docker/Dockerfile.debian
 | 
			
		||||
Dockerfile.dev
 | 
			
		||||
							
								
								
									
										31
									
								
								Dockerfile.dev
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
			
		||||
FROM debian:stretch
 | 
			
		||||
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
RUN apt-get -y install g++ 
 | 
			
		||||
RUN apt-get -y install libssl-dev
 | 
			
		||||
RUN apt-get -y install gdb
 | 
			
		||||
RUN apt-get -y install screen
 | 
			
		||||
RUN apt-get -y install procps
 | 
			
		||||
RUN apt-get -y install lsof
 | 
			
		||||
RUN apt-get -y install libz-dev
 | 
			
		||||
RUN apt-get -y install vim
 | 
			
		||||
RUN apt-get -y install make
 | 
			
		||||
RUN apt-get -y install cmake
 | 
			
		||||
RUN apt-get -y install curl
 | 
			
		||||
RUN apt-get -y install python
 | 
			
		||||
RUN apt-get -y install netcat
 | 
			
		||||
 | 
			
		||||
# debian strech cmake is too old for building with Docker
 | 
			
		||||
COPY makefile .
 | 
			
		||||
RUN ["make", "install_cmake_for_linux"]
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-rc4-Linux-x86_64/bin
 | 
			
		||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
 | 
			
		||||
 | 
			
		||||
# RUN ["make"]
 | 
			
		||||
 | 
			
		||||
EXPOSE 8765
 | 
			
		||||
CMD ["/ws/ws", "transfer", "--port", "8765", "--host", "0.0.0.0"]
 | 
			
		||||
							
								
								
									
										30
									
								
								Dockerfile.prod
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,30 @@
 | 
			
		||||
FROM debian:buster
 | 
			
		||||
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
 | 
			
		||||
RUN apt-get -y install g++ 
 | 
			
		||||
RUN apt-get -y install libssl-dev
 | 
			
		||||
RUN apt-get -y install libz-dev
 | 
			
		||||
RUN apt-get -y install make
 | 
			
		||||
 | 
			
		||||
RUN apt-get -y install wget 
 | 
			
		||||
RUN mkdir -p /tmp/cmake
 | 
			
		||||
WORKDIR /tmp/cmake
 | 
			
		||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.0/cmake-3.14.0-Linux-x86_64.tar.gz 
 | 
			
		||||
RUN tar zxf cmake-3.14.0-Linux-x86_64.tar.gz
 | 
			
		||||
 | 
			
		||||
RUN adduser app 
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
ARG CMAKE_BIN_PATH=/tmp/cmake/cmake-3.14.0-Linux-x86_64/bin
 | 
			
		||||
ENV PATH="${CMAKE_BIN_PATH}:${PATH}"
 | 
			
		||||
 | 
			
		||||
RUN ["make"]
 | 
			
		||||
 | 
			
		||||
# Now run in usermode
 | 
			
		||||
USER app
 | 
			
		||||
 | 
			
		||||
EXPOSE 8765
 | 
			
		||||
CMD ["bash"]
 | 
			
		||||
							
								
								
									
										129
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -4,18 +4,18 @@
 | 
			
		||||
 | 
			
		||||
## Introduction
 | 
			
		||||
 | 
			
		||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
 | 
			
		||||
communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket 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 HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
 | 
			
		||||
 | 
			
		||||
* macOS
 | 
			
		||||
* iOS
 | 
			
		||||
* Linux
 | 
			
		||||
* Android 
 | 
			
		||||
* Windows (no TLS support yet)
 | 
			
		||||
* Android
 | 
			
		||||
 | 
			
		||||
The code was made to compile once on Windows but support is currently broken on this platform.
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
The examples folder countains a simple chat program, using a node.js broadcast server.
 | 
			
		||||
The [*ws*](https://github.com/machinezone/IXWebSocket/tree/master/ws) folder countains many interactive programs for chat, [file transfers](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_send.cpp), [curl like](https://github.com/machinezone/IXWebSocket/blob/master/ws/ws_http_client.cpp) http clients, demonstrating client and server usage.
 | 
			
		||||
 | 
			
		||||
Here is what the client API looks like.
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@ ix::WebSocket webSocket;
 | 
			
		||||
std::string url("ws://localhost:8080/");
 | 
			
		||||
webSocket.setUrl(url);
 | 
			
		||||
 | 
			
		||||
// Optional heart beat, sent every 45 seconds when there isn't any traffic
 | 
			
		||||
// Optional heart beat, sent every 45 seconds when there is not any traffic
 | 
			
		||||
// to make sure that load balancers do not kill an idle connection.
 | 
			
		||||
webSocket.setHeartBeatPeriod(45);
 | 
			
		||||
 | 
			
		||||
@@ -47,9 +47,12 @@ webSocket.setOnMessageCallback(
 | 
			
		||||
// Now that our callback is setup, we can start our background thread and receive messages
 | 
			
		||||
webSocket.start();
 | 
			
		||||
 | 
			
		||||
// Send a message to the server
 | 
			
		||||
// Send a message to the server (default to BINARY mode)
 | 
			
		||||
webSocket.send("hello world");
 | 
			
		||||
 | 
			
		||||
// The message can be sent in TEXT mode
 | 
			
		||||
webSocket.sendText("hello again");
 | 
			
		||||
 | 
			
		||||
// ... finally ...
 | 
			
		||||
 | 
			
		||||
// Stop the connection
 | 
			
		||||
@@ -64,10 +67,11 @@ Here is what the server API looks like. Note that server support is very recent
 | 
			
		||||
ix::WebSocketServer server(port);
 | 
			
		||||
 | 
			
		||||
server.setOnConnectionCallback(
 | 
			
		||||
    [&server](std::shared_ptr<ix::WebSocket> webSocket)
 | 
			
		||||
    [&server](std::shared_ptr<WebSocket> webSocket,
 | 
			
		||||
              std::shared_ptr<ConnectionState> connectionState)
 | 
			
		||||
    {
 | 
			
		||||
        webSocket->setOnMessageCallback(
 | 
			
		||||
            [webSocket, &server](ix::WebSocketMessageType messageType,
 | 
			
		||||
            [webSocket, connectionState, &server](ix::WebSocketMessageType messageType,
 | 
			
		||||
               const std::string& str,
 | 
			
		||||
               size_t wireSize,
 | 
			
		||||
               const ix::WebSocketErrorInfo& error,
 | 
			
		||||
@@ -77,7 +81,16 @@ server.setOnConnectionCallback(
 | 
			
		||||
                if (messageType == ix::WebSocket_MessageType_Open)
 | 
			
		||||
                {
 | 
			
		||||
                    std::cerr << "New connection" << std::endl;
 | 
			
		||||
 | 
			
		||||
                    // A connection state object is available, and has a default id
 | 
			
		||||
                    // You can subclass ConnectionState and pass an alternate factory
 | 
			
		||||
                    // to override it. It is useful if you want to store custom
 | 
			
		||||
                    // attributes per connection (authenticated bool flag, attributes, etc...)
 | 
			
		||||
                    std::cerr << "id: " << connectionState->getId() << std::endl;
 | 
			
		||||
 | 
			
		||||
                    // The uri the client did connect to.
 | 
			
		||||
                    std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
			
		||||
 | 
			
		||||
                    std::cerr << "Headers:" << std::endl;
 | 
			
		||||
                    for (auto it : openInfo.headers)
 | 
			
		||||
                    {
 | 
			
		||||
@@ -110,12 +123,81 @@ server.wait();
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Here is what the HTTP client API looks like. Note that HTTP client support is very recent and subject to changes.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
//
 | 
			
		||||
// Preparation
 | 
			
		||||
//
 | 
			
		||||
HttpClient httpClient;
 | 
			
		||||
HttpRequestArgs args;
 | 
			
		||||
 | 
			
		||||
// Custom headers can be set
 | 
			
		||||
WebSocketHttpHeaders headers;
 | 
			
		||||
headers["Foo"] = "bar";
 | 
			
		||||
args.extraHeaders = headers;
 | 
			
		||||
 | 
			
		||||
// Timeout options
 | 
			
		||||
args.connectTimeout = connectTimeout;
 | 
			
		||||
args.transferTimeout = transferTimeout;
 | 
			
		||||
 | 
			
		||||
// Redirect options
 | 
			
		||||
args.followRedirects = followRedirects;
 | 
			
		||||
args.maxRedirects = maxRedirects;
 | 
			
		||||
 | 
			
		||||
// Misc
 | 
			
		||||
args.compress = compress; // Enable gzip compression
 | 
			
		||||
args.verbose = verbose;
 | 
			
		||||
args.logger = [](const std::string& msg)
 | 
			
		||||
{
 | 
			
		||||
    std::cout << msg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Request
 | 
			
		||||
//
 | 
			
		||||
HttpResponse out;
 | 
			
		||||
std::string url = "https://www.google.com";
 | 
			
		||||
 | 
			
		||||
// HEAD request
 | 
			
		||||
out = httpClient.head(url, args);
 | 
			
		||||
 | 
			
		||||
// GET request
 | 
			
		||||
out = httpClient.get(url, args);
 | 
			
		||||
 | 
			
		||||
// POST request with parameters
 | 
			
		||||
HttpParameters httpParameters;
 | 
			
		||||
httpParameters["foo"] = "bar";
 | 
			
		||||
out = httpClient.post(url, httpParameters, args);
 | 
			
		||||
 | 
			
		||||
// POST request with a body
 | 
			
		||||
out = httpClient.post(url, std::string("foo=bar"), args);
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Result
 | 
			
		||||
//
 | 
			
		||||
auto statusCode = std::get<0>(out);
 | 
			
		||||
auto errorCode = std::get<1>(out);
 | 
			
		||||
auto responseHeaders = std::get<2>(out);
 | 
			
		||||
auto payload = std::get<3>(out);
 | 
			
		||||
auto errorMsg = std::get<4>(out);
 | 
			
		||||
auto uploadSize = std::get<5>(out);
 | 
			
		||||
auto downloadSize = std::get<6>(out);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
There is a Dockerfile for running some code on Linux, and a unittest which can be executed by typing `make test`.
 | 
			
		||||
 | 
			
		||||
You can build and install the ws command line tool with Homebrew.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
brew tap bsergean/IXWebSocket
 | 
			
		||||
brew install IXWebSocket
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Implementation details
 | 
			
		||||
 | 
			
		||||
### Per Message Deflate compression.
 | 
			
		||||
@@ -134,37 +216,31 @@ No manual polling to fetch data is required. Data is sent and received instantly
 | 
			
		||||
 | 
			
		||||
If the remote end (server) breaks the connection, the code will try to perpetually reconnect, by using an exponential backoff strategy, capped at one retry every 10 seconds.
 | 
			
		||||
 | 
			
		||||
### Large messages
 | 
			
		||||
 | 
			
		||||
Large frames are broken up into smaller chunks or messages to avoid filling up the os tcp buffers, which is permitted thanks to WebSocket [fragmentation](https://tools.ietf.org/html/rfc6455#section-5.4). Messages up to 1G were sent and received succesfully.
 | 
			
		||||
 | 
			
		||||
## Limitations
 | 
			
		||||
 | 
			
		||||
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
 | 
			
		||||
* No utf-8 validation is made when sending TEXT message with sendText()
 | 
			
		||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
 | 
			
		||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This isn't as scalable as strategies using epoll or kqueue.
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
1. Bring up a terminal and jump to the examples folder.
 | 
			
		||||
2. Compile the example C++ code. `sh build.sh`
 | 
			
		||||
3. Install node.js from [here](https://nodejs.org/en/download/).
 | 
			
		||||
4. Type `npm install` to install the node.js dependencies. Then `node broadcast-server.js` to run the server.
 | 
			
		||||
5. Bring up a second terminal. `./cmd_websocket_chat bob`
 | 
			
		||||
6. Bring up a third terminal. `./cmd_websocket_chat bill`
 | 
			
		||||
7. Start typing things in any of those terminals. Hopefully you should see your message being received on the other end.
 | 
			
		||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
 | 
			
		||||
 | 
			
		||||
## C++ code organization
 | 
			
		||||
 | 
			
		||||
Here's a simplistic diagram which explains how the code is structured in term of class/modules.
 | 
			
		||||
Here is a simplistic diagram which explains how the code is structured in term of class/modules.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
+-----------------------+ --- Public
 | 
			
		||||
|                       | Start the receiving Background thread. Auto reconnection. Simple websocket Ping.
 | 
			
		||||
|  IXWebSocket          | Interface used by C++ test clients. No IX dependencies.
 | 
			
		||||
|                       | 
 | 
			
		||||
|                       |
 | 
			
		||||
+-----------------------+
 | 
			
		||||
|                       |
 | 
			
		||||
|  IXWebSocketServer    | Run a server and give each connections its own WebSocket object.
 | 
			
		||||
|                       | Each connection is handled in a new OS thread.
 | 
			
		||||
|                       |
 | 
			
		||||
+-----------------------+ --- Private 
 | 
			
		||||
+-----------------------+ --- Private
 | 
			
		||||
|                       |
 | 
			
		||||
|  IXWebSocketTransport | Low level websocket code, framing, managing raw socket. Adapted from easywsclient.
 | 
			
		||||
|                       |
 | 
			
		||||
@@ -204,7 +280,7 @@ If the connection was closed and sending failed, the return value will be set to
 | 
			
		||||
1. WebSocket_ReadyState_Connecting - The connection is not yet open.
 | 
			
		||||
2. WebSocket_ReadyState_Open       - The connection is open and ready to communicate.
 | 
			
		||||
3. WebSocket_ReadyState_Closing    - The connection is in the process of closing.
 | 
			
		||||
4. WebSocket_MessageType_Close     - The connection is closed or couldn't be opened.
 | 
			
		||||
4. WebSocket_MessageType_Close     - The connection is closed or could not be opened.
 | 
			
		||||
 | 
			
		||||
### Open and Close notifications
 | 
			
		||||
 | 
			
		||||
@@ -309,11 +385,12 @@ A ping message can be sent to the server, with an optional data string.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Heartbeat.
 | 
			
		||||
 | 
			
		||||
You can configure an optional heart beat / keep-alive, sent every 45 seconds
 | 
			
		||||
when there isn't any traffic to make sure that load balancers do not kill an
 | 
			
		||||
when there is no any traffic to make sure that load balancers do not kill an
 | 
			
		||||
idle connection.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/brocoli.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/grafana_critical_logs.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 94 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/grafana_zlib.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 80 KiB  | 
							
								
								
									
										2
									
								
								doc/redis_conf_2019/makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,2 @@
 | 
			
		||||
all:
 | 
			
		||||
	(cd .. ; make docker && make docker_push)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/mz_engine.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 74 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/neo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 118 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/neo_map.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 113 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/neo_session.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 168 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/redisconf_10_years.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 673 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/redisconf_first_slide.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 MiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/redisconf_last_slide.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 MiB  | 
							
								
								
									
										18
									
								
								doc/redis_conf_2019/remark-latest.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/sentry.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 90 KiB  | 
							
								
								
									
										1164
									
								
								doc/redis_conf_2019/slides.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								doc/redis_conf_2019/tableau.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										21
									
								
								docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
version: "3"
 | 
			
		||||
services:
 | 
			
		||||
  ws:
 | 
			
		||||
    stdin_open: true
 | 
			
		||||
    tty: true
 | 
			
		||||
    image: bsergean/ws:build
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8765:8765"
 | 
			
		||||
    entrypoint: bash
 | 
			
		||||
    networks:
 | 
			
		||||
      - ws-net
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - redis1
 | 
			
		||||
 | 
			
		||||
  redis1:
 | 
			
		||||
    image: redis:alpine
 | 
			
		||||
    networks:
 | 
			
		||||
      - ws-net
 | 
			
		||||
 | 
			
		||||
networks:
 | 
			
		||||
  ws-net:
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
FROM debian:stretch
 | 
			
		||||
 | 
			
		||||
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
RUN apt-get -y install g++ 
 | 
			
		||||
RUN apt-get -y install libssl-dev
 | 
			
		||||
RUN apt-get -y install gdb
 | 
			
		||||
RUN apt-get -y install screen
 | 
			
		||||
RUN apt-get -y install procps
 | 
			
		||||
RUN apt-get -y install lsof
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
WORKDIR examples/ws_connect
 | 
			
		||||
RUN ["sh", "build_linux.sh"]
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
FROM alpine:3.8
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
WORKDIR examples/ws_connect
 | 
			
		||||
RUN ["sh", "build_linux.sh"]
 | 
			
		||||
 | 
			
		||||
EXPOSE 8765
 | 
			
		||||
CMD ["ws_connect"]
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
FROM alpine:3.8
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache g++ musl-dev make cmake openssl-dev
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
WORKDIR examples/ws_connect
 | 
			
		||||
RUN ["sh", "build_linux.sh"]
 | 
			
		||||
 | 
			
		||||
EXPOSE 8765
 | 
			
		||||
CMD ["ws_connect"]
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
FROM debian:stretch
 | 
			
		||||
 | 
			
		||||
ENV DEBIAN_FRONTEND noninteractive
 | 
			
		||||
RUN apt-get update 
 | 
			
		||||
RUN apt-get -y install g++ 
 | 
			
		||||
RUN apt-get -y install libssl-dev
 | 
			
		||||
RUN apt-get -y install gdb
 | 
			
		||||
RUN apt-get -y install screen
 | 
			
		||||
RUN apt-get -y install procps
 | 
			
		||||
RUN apt-get -y install lsof
 | 
			
		||||
RUN apt-get -y install libz-dev
 | 
			
		||||
RUN apt-get -y install vim
 | 
			
		||||
RUN apt-get -y install make
 | 
			
		||||
RUN apt-get -y install cmake
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
WORKDIR test
 | 
			
		||||
RUN ["sh", "build_linux.sh"]
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
FROM gcc:8
 | 
			
		||||
 | 
			
		||||
# RUN yum install -y gcc-c++ make cmake openssl-devel gdb
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
WORKDIR examples/ws_connect
 | 
			
		||||
RUN ["sh", "build_linux.sh"]
 | 
			
		||||
							
								
								
									
										9
									
								
								examples/broadcast_server/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,9 +0,0 @@
 | 
			
		||||
CMakeCache.txt
 | 
			
		||||
package-lock.json
 | 
			
		||||
CMakeFiles		
 | 
			
		||||
ixwebsocket_unittest	
 | 
			
		||||
cmake_install.cmake	
 | 
			
		||||
node_modules
 | 
			
		||||
ixwebsocket
 | 
			
		||||
Makefile
 | 
			
		||||
build
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (broadcast_server)
 | 
			
		||||
 | 
			
		||||
# There's -Weverything too for clang
 | 
			
		||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
 | 
			
		||||
 | 
			
		||||
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
			
		||||
 | 
			
		||||
include_directories(broadcast_server .)
 | 
			
		||||
 | 
			
		||||
add_executable(broadcast_server 
 | 
			
		||||
  broadcast_server.cpp)
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(broadcast_server "-framework foundation" "-framework security")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(broadcast_server ixwebsocket)
 | 
			
		||||
install(TARGETS broadcast_server DESTINATION bin)
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  broadcast_server.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <ixwebsocket/IXWebSocketServer.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char** argv)
 | 
			
		||||
{
 | 
			
		||||
    int port = 8080;
 | 
			
		||||
    if (argc == 2)
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << argv[1];
 | 
			
		||||
        ss >> port;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ix::WebSocketServer server(port);
 | 
			
		||||
 | 
			
		||||
    server.setOnConnectionCallback(
 | 
			
		||||
        [&server](std::shared_ptr<ix::WebSocket> webSocket)
 | 
			
		||||
        {
 | 
			
		||||
            webSocket->setOnMessageCallback(
 | 
			
		||||
                [webSocket, &server](ix::WebSocketMessageType messageType,
 | 
			
		||||
                   const std::string& str,
 | 
			
		||||
                   size_t wireSize,
 | 
			
		||||
                   const ix::WebSocketErrorInfo& error,
 | 
			
		||||
                   const ix::WebSocketOpenInfo& openInfo,
 | 
			
		||||
                   const ix::WebSocketCloseInfo& closeInfo)
 | 
			
		||||
                {
 | 
			
		||||
                    if (messageType == ix::WebSocket_MessageType_Open)
 | 
			
		||||
                    {
 | 
			
		||||
                        std::cerr << "New connection" << std::endl;
 | 
			
		||||
                        std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
			
		||||
                        std::cerr << "Headers:" << std::endl;
 | 
			
		||||
                        for (auto it : openInfo.headers)
 | 
			
		||||
                        {
 | 
			
		||||
                            std::cerr << it.first << ": " << it.second << std::endl;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (messageType == ix::WebSocket_MessageType_Close)
 | 
			
		||||
                    {
 | 
			
		||||
                        std::cerr << "Closed connection" << std::endl;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (messageType == ix::WebSocket_MessageType_Message)
 | 
			
		||||
                    {
 | 
			
		||||
                        for (auto&& client : server.getClients())
 | 
			
		||||
                        {
 | 
			
		||||
                            if (client != webSocket)
 | 
			
		||||
                            {
 | 
			
		||||
                                client->send(str);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    auto res = server.listen();
 | 
			
		||||
    if (!res.first)
 | 
			
		||||
    {
 | 
			
		||||
        std::cerr << res.second << std::endl;
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    server.start();
 | 
			
		||||
    server.wait();
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# cmd_websocket_chat.cpp
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (cmd_websocket_chat)
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
			
		||||
 | 
			
		||||
add_executable(cmd_websocket_chat cmd_websocket_chat.cpp)
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(cmd_websocket_chat "-framework foundation" "-framework security")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(cmd_websocket_chat ixwebsocket)
 | 
			
		||||
install(TARGETS cmd_websocket_chat DESTINATION bin)
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
# Building
 | 
			
		||||
 | 
			
		||||
1. cmake -G .
 | 
			
		||||
2. make
 | 
			
		||||
 | 
			
		||||
## Disable TLS
 | 
			
		||||
 | 
			
		||||
chat$ cmake -DUSE_TLS=OFF .
 | 
			
		||||
-- Configuring done
 | 
			
		||||
-- Generating done
 | 
			
		||||
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
 | 
			
		||||
chat$ make
 | 
			
		||||
Scanning dependencies of target ixwebsocket
 | 
			
		||||
[ 16%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
 | 
			
		||||
[ 33%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
 | 
			
		||||
[ 50%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
 | 
			
		||||
[ 66%] Linking CXX static library libixwebsocket.a
 | 
			
		||||
[ 66%] Built target ixwebsocket
 | 
			
		||||
[ 83%] Linking CXX executable cmd_websocket_chat
 | 
			
		||||
[100%] Built target cmd_websocket_chat
 | 
			
		||||
 | 
			
		||||
## Enable TLS (default)
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
chat$ cmake -DUSE_TLS=ON .
 | 
			
		||||
-- Configuring done
 | 
			
		||||
-- Generating done
 | 
			
		||||
-- Build files have been written to: /Users/bsergeant/src/foss/ixwebsocket/examples/chat
 | 
			
		||||
(venv) chat$ make
 | 
			
		||||
Scanning dependencies of target ixwebsocket
 | 
			
		||||
[ 14%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocket.cpp.o
 | 
			
		||||
[ 28%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocket.cpp.o
 | 
			
		||||
[ 42%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXWebSocketTransport.cpp.o
 | 
			
		||||
[ 57%] Building CXX object ixwebsocket/CMakeFiles/ixwebsocket.dir/ixwebsocket/IXSocketAppleSSL.cpp.o
 | 
			
		||||
[ 71%] Linking CXX static library libixwebsocket.a
 | 
			
		||||
[ 71%] Built target ixwebsocket
 | 
			
		||||
[ 85%] Linking CXX executable cmd_websocket_chat
 | 
			
		||||
[100%] Built target cmd_websocket_chat
 | 
			
		||||
```
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# 'manual' way of building. You can also use cmake.
 | 
			
		||||
 | 
			
		||||
g++ --std=c++11 \
 | 
			
		||||
    ../../ixwebsocket/IXSocket.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketTransport.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXWebSocket.cpp \
 | 
			
		||||
    -I ../.. \
 | 
			
		||||
    cmd_websocket_chat.cpp \
 | 
			
		||||
    -o cmd_websocket_chat
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# 'manual' way of building. You can also use cmake.
 | 
			
		||||
 | 
			
		||||
clang++ --std=c++11 --stdlib=libc++ \
 | 
			
		||||
    ../../ixwebsocket/IXSocket.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketTransport.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXSocketAppleSSL.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXWebSocket.cpp \
 | 
			
		||||
    cmd_websocket_chat.cpp \
 | 
			
		||||
    -o cmd_websocket_chat \
 | 
			
		||||
    -framework Security \
 | 
			
		||||
    -framework Foundation
 | 
			
		||||
							
								
								
									
										31
									
								
								examples/chat/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -1,31 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "async-limiter": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
 | 
			
		||||
    },
 | 
			
		||||
    "safe-buffer": {
 | 
			
		||||
      "version": "5.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
 | 
			
		||||
    },
 | 
			
		||||
    "ultron": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
 | 
			
		||||
    },
 | 
			
		||||
    "ws": {
 | 
			
		||||
      "version": "3.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
 | 
			
		||||
      "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "async-limiter": "1.0.0",
 | 
			
		||||
        "safe-buffer": "5.1.2",
 | 
			
		||||
        "ultron": "1.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "msgpack-js": "^0.3.0",
 | 
			
		||||
    "ws": "^3.3.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,30 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (echo_server)
 | 
			
		||||
 | 
			
		||||
# There's -Weverything too for clang
 | 
			
		||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
 | 
			
		||||
 | 
			
		||||
set (OPENSSL_PREFIX /usr/local/opt/openssl) # Homebrew openssl
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
			
		||||
 | 
			
		||||
include_directories(echo_server .)
 | 
			
		||||
 | 
			
		||||
add_executable(echo_server 
 | 
			
		||||
  echo_server.cpp)
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(echo_server "-framework foundation" "-framework security")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(echo_server ixwebsocket)
 | 
			
		||||
install(TARGETS echo_server DESTINATION bin)
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  echo_server.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <ixwebsocket/IXWebSocketServer.h>
 | 
			
		||||
 | 
			
		||||
int main(int argc, char** argv)
 | 
			
		||||
{
 | 
			
		||||
    int port = 8080;
 | 
			
		||||
    if (argc == 2)
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << argv[1];
 | 
			
		||||
        ss >> port;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ix::WebSocketServer server(port);
 | 
			
		||||
 | 
			
		||||
    server.setOnConnectionCallback(
 | 
			
		||||
        [&server](std::shared_ptr<ix::WebSocket> webSocket)
 | 
			
		||||
        {
 | 
			
		||||
            webSocket->setOnMessageCallback(
 | 
			
		||||
                [webSocket, &server](ix::WebSocketMessageType messageType,
 | 
			
		||||
                   const std::string& str,
 | 
			
		||||
                   size_t wireSize,
 | 
			
		||||
                   const ix::WebSocketErrorInfo& error,
 | 
			
		||||
                   const ix::WebSocketOpenInfo& openInfo,
 | 
			
		||||
                   const ix::WebSocketCloseInfo& closeInfo)
 | 
			
		||||
                {
 | 
			
		||||
                    if (messageType == ix::WebSocket_MessageType_Open)
 | 
			
		||||
                    {
 | 
			
		||||
                        std::cerr << "New connection" << std::endl;
 | 
			
		||||
                        std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
			
		||||
                        std::cerr << "Headers:" << std::endl;
 | 
			
		||||
                        for (auto it : openInfo.headers)
 | 
			
		||||
                        {
 | 
			
		||||
                            std::cerr << it.first << ": " << it.second << std::endl;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (messageType == ix::WebSocket_MessageType_Close)
 | 
			
		||||
                    {
 | 
			
		||||
                        std::cerr << "Closed connection" << std::endl;
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (messageType == ix::WebSocket_MessageType_Message)
 | 
			
		||||
                    {
 | 
			
		||||
                        webSocket->send(str);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    auto res = server.listen();
 | 
			
		||||
    if (!res.first)
 | 
			
		||||
    {
 | 
			
		||||
        std::cerr << res.second << std::endl;
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    server.start();
 | 
			
		||||
    server.wait();
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (ping_pong)
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
			
		||||
 | 
			
		||||
add_executable(ping_pong ping_pong.cpp)
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(ping_pong "-framework foundation" "-framework security")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (WIN32)
 | 
			
		||||
    target_link_libraries(ping_pong wsock32 ws2_32)
 | 
			
		||||
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(ping_pong ixwebsocket)
 | 
			
		||||
install(TARGETS ping_pong DESTINATION bin)
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# 'manual' way of building. You can also use cmake.
 | 
			
		||||
 | 
			
		||||
g++ --std=c++11 \
 | 
			
		||||
    ../../ixwebsocket/IXSocket.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketTransport.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXWebSocket.cpp \
 | 
			
		||||
    -I ../.. \
 | 
			
		||||
    cmd_websocket_chat.cpp \
 | 
			
		||||
    -o cmd_websocket_chat
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
import websockets
 | 
			
		||||
 | 
			
		||||
async def hello(uri):
 | 
			
		||||
    async with websockets.connect(uri) as websocket:
 | 
			
		||||
        await websocket.send("Hello world!")
 | 
			
		||||
        response = await websocket.recv()
 | 
			
		||||
        print(response)
 | 
			
		||||
 | 
			
		||||
        pong_waiter = await websocket.ping('coucou')
 | 
			
		||||
        ret = await pong_waiter   # only if you want to wait for the pong
 | 
			
		||||
        print(ret)
 | 
			
		||||
 | 
			
		||||
asyncio.get_event_loop().run_until_complete(
 | 
			
		||||
    hello('ws://localhost:5678'))
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import asyncio
 | 
			
		||||
import websockets
 | 
			
		||||
 | 
			
		||||
async def echo(websocket, path):
 | 
			
		||||
    async for message in websocket:
 | 
			
		||||
        print(message)
 | 
			
		||||
        await websocket.send(message)
 | 
			
		||||
 | 
			
		||||
        if os.getenv('TEST_CLOSE'):
 | 
			
		||||
            print('Closing')
 | 
			
		||||
            # breakpoint()
 | 
			
		||||
            await websocket.close(1001, 'close message')
 | 
			
		||||
            # await websocket.close()
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
asyncio.get_event_loop().run_until_complete(
 | 
			
		||||
    websockets.serve(echo, 'localhost', 5678))
 | 
			
		||||
asyncio.get_event_loop().run_forever()
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
test -d build || {
 | 
			
		||||
    mkdir -p build
 | 
			
		||||
    cd build
 | 
			
		||||
    cmake ..
 | 
			
		||||
}
 | 
			
		||||
(cd build ; make)
 | 
			
		||||
./build/ping_pong ws://localhost:5678
 | 
			
		||||
							
								
								
									
										3
									
								
								examples/ws_connect/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +0,0 @@
 | 
			
		||||
build
 | 
			
		||||
venv
 | 
			
		||||
node_modules
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
cmake_minimum_required (VERSION 3.4.1)
 | 
			
		||||
project (ws_connect)
 | 
			
		||||
 | 
			
		||||
set (CMAKE_CXX_STANDARD 14)
 | 
			
		||||
 | 
			
		||||
option(USE_TLS "Add TLS support" ON)
 | 
			
		||||
 | 
			
		||||
add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
			
		||||
 | 
			
		||||
add_executable(ws_connect ws_connect.cpp)
 | 
			
		||||
 | 
			
		||||
if (APPLE AND USE_TLS)
 | 
			
		||||
    target_link_libraries(ws_connect "-framework foundation" "-framework security")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
target_link_libraries(ws_connect ixwebsocket)
 | 
			
		||||
install(TARGETS ws_connect DESTINATION bin)
 | 
			
		||||
@@ -1,11 +0,0 @@
 | 
			
		||||
# Building
 | 
			
		||||
 | 
			
		||||
1. mkdir build
 | 
			
		||||
2. cd build
 | 
			
		||||
3. cmake ..
 | 
			
		||||
4. make
 | 
			
		||||
 | 
			
		||||
## Disable TLS
 | 
			
		||||
 | 
			
		||||
* Enable: `cmake -DUSE_TLS=OFF ..`
 | 
			
		||||
* Disable: `cmake -DUSE_TLS=ON ..`
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
#
 | 
			
		||||
# Author: Benjamin Sergeant
 | 
			
		||||
# Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# 'manual' way of building. You can also use cmake.
 | 
			
		||||
 | 
			
		||||
g++ --std=c++11 \
 | 
			
		||||
    -DIXWEBSOCKET_USE_TLS \
 | 
			
		||||
    -g \
 | 
			
		||||
    ../../ixwebsocket/IXEventFd.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXSocket.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXSetThreadName.cpp	\
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketTransport.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXWebSocket.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXDNSLookup.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXSocketConnect.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXSocketOpenSSL.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketPerMessageDeflate.cpp \
 | 
			
		||||
    ../../ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp \
 | 
			
		||||
    -I ../.. \
 | 
			
		||||
    ws_connect.cpp \
 | 
			
		||||
    -o ws_connect \
 | 
			
		||||
    -lcrypto -lssl -lz -lpthread
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    CancellationRequest makeCancellationRequestWithTimeout(int secs,
 | 
			
		||||
                                                           std::atomic<bool>& requestInitCancellation)
 | 
			
		||||
@@ -20,7 +20,7 @@ namespace ix
 | 
			
		||||
        {
 | 
			
		||||
            // Was an explicit cancellation requested ?
 | 
			
		||||
            if (requestInitCancellation) return true;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            auto now = std::chrono::system_clock::now();
 | 
			
		||||
            if ((now - start) > timeout) return true;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    using CancellationRequest = std::function<bool()>;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								ixwebsocket/IXConnectionState.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXConnectionState.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXConnectionState.h"
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    std::atomic<uint64_t> ConnectionState::_globalId(0);
 | 
			
		||||
 | 
			
		||||
    ConnectionState::ConnectionState()
 | 
			
		||||
    {
 | 
			
		||||
        computeId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ConnectionState::computeId()
 | 
			
		||||
    {
 | 
			
		||||
        _id = std::to_string(_globalId++);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const std::string& ConnectionState::getId() const
 | 
			
		||||
    {
 | 
			
		||||
        return _id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<ConnectionState> ConnectionState::createConnectionState()
 | 
			
		||||
    {
 | 
			
		||||
        return std::make_shared<ConnectionState>();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								ixwebsocket/IXConnectionState.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXConnectionState.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class ConnectionState {
 | 
			
		||||
    public:
 | 
			
		||||
        ConnectionState();
 | 
			
		||||
        virtual ~ConnectionState() = default;
 | 
			
		||||
 | 
			
		||||
        virtual void computeId();
 | 
			
		||||
        virtual const std::string& getId() const;
 | 
			
		||||
 | 
			
		||||
        static std::shared_ptr<ConnectionState> createConnectionState();
 | 
			
		||||
 | 
			
		||||
    protected:
 | 
			
		||||
        std::string _id;
 | 
			
		||||
 | 
			
		||||
        static std::atomic<uint64_t> _globalId;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
#include <string.h>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    const int64_t DNSLookup::kDefaultWait = 10; // ms
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +26,7 @@ namespace ix
 | 
			
		||||
        _done(false),
 | 
			
		||||
        _id(_nextId++)
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DNSLookup::~DNSLookup()
 | 
			
		||||
@@ -36,7 +36,7 @@ namespace ix
 | 
			
		||||
        _activeJobs.erase(_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname, 
 | 
			
		||||
    struct addrinfo* DNSLookup::getAddrInfo(const std::string& hostname,
 | 
			
		||||
                                            int port,
 | 
			
		||||
                                            std::string& errMsg)
 | 
			
		||||
    {
 | 
			
		||||
@@ -49,7 +49,7 @@ namespace ix
 | 
			
		||||
        std::string sport = std::to_string(port);
 | 
			
		||||
 | 
			
		||||
        struct addrinfo* res;
 | 
			
		||||
        int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(), 
 | 
			
		||||
        int getaddrinfo_result = getaddrinfo(hostname.c_str(), sport.c_str(),
 | 
			
		||||
                                             &hints, &res);
 | 
			
		||||
        if (getaddrinfo_result)
 | 
			
		||||
        {
 | 
			
		||||
@@ -73,7 +73,7 @@ namespace ix
 | 
			
		||||
        errMsg = "no error";
 | 
			
		||||
 | 
			
		||||
        // Maybe a cancellation request got in before the background thread terminated ?
 | 
			
		||||
        if (isCancellationRequested())
 | 
			
		||||
        if (isCancellationRequested && isCancellationRequested())
 | 
			
		||||
        {
 | 
			
		||||
            errMsg = "cancellation requested";
 | 
			
		||||
            return nullptr;
 | 
			
		||||
@@ -101,7 +101,7 @@ namespace ix
 | 
			
		||||
            _activeJobs.insert(_id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        // Good resource on thread forced termination
 | 
			
		||||
        // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
 | 
			
		||||
        //
 | 
			
		||||
@@ -121,7 +121,7 @@ namespace ix
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Were we cancelled ?
 | 
			
		||||
            if (isCancellationRequested())
 | 
			
		||||
            if (isCancellationRequested && isCancellationRequested())
 | 
			
		||||
            {
 | 
			
		||||
                errMsg = "cancellation requested";
 | 
			
		||||
                return nullptr;
 | 
			
		||||
@@ -129,7 +129,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Maybe a cancellation request got in before the bg terminated ?
 | 
			
		||||
        if (isCancellationRequested())
 | 
			
		||||
        if (isCancellationRequested && isCancellationRequested())
 | 
			
		||||
        {
 | 
			
		||||
            errMsg = "cancellation requested";
 | 
			
		||||
            return nullptr;
 | 
			
		||||
@@ -141,7 +141,7 @@ namespace ix
 | 
			
		||||
    void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
 | 
			
		||||
    {
 | 
			
		||||
        // We don't want to read or write into members variables of an object that could be
 | 
			
		||||
        // gone, so we use temporary variables (res) or we pass in by copy everything that 
 | 
			
		||||
        // gone, so we use temporary variables (res) or we pass in by copy everything that
 | 
			
		||||
        // getAddrInfo needs to work.
 | 
			
		||||
        std::string errMsg;
 | 
			
		||||
        struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 *  Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo 
 | 
			
		||||
 *  Resolve a hostname+port to a struct addrinfo obtained with getaddrinfo
 | 
			
		||||
 *  Does this in a background thread so that it can be cancelled, since
 | 
			
		||||
 *  getaddrinfo is a blocking call, and we don't want to block the main thread on Mobile.
 | 
			
		||||
 */
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 | 
			
		||||
struct addrinfo;
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class DNSLookup {
 | 
			
		||||
    public:
 | 
			
		||||
@@ -39,7 +39,7 @@ namespace ix
 | 
			
		||||
        struct addrinfo* resolveBlocking(std::string& errMsg,
 | 
			
		||||
                                         const CancellationRequest& isCancellationRequested);
 | 
			
		||||
 | 
			
		||||
        static struct addrinfo* getAddrInfo(const std::string& hostname, 
 | 
			
		||||
        static struct addrinfo* getAddrInfo(const std::string& hostname,
 | 
			
		||||
                                            int port,
 | 
			
		||||
                                            std::string& errMsg);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXEventFd.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Linux/Android has a special type of virtual files. select(2) will react
 | 
			
		||||
// when reading/writing to those files, unlike closing sockets.
 | 
			
		||||
//
 | 
			
		||||
// https://linux.die.net/man/2/eventfd
 | 
			
		||||
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
 | 
			
		||||
//
 | 
			
		||||
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
 | 
			
		||||
// is on Kernel 3.x
 | 
			
		||||
//
 | 
			
		||||
// cf Android/Kernel table here 
 | 
			
		||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "IXEventFd.h"
 | 
			
		||||
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
# include <sys/eventfd.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
#include <unistd.h> // for write
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
{
 | 
			
		||||
    EventFd::EventFd() : 
 | 
			
		||||
        _eventfd(-1)
 | 
			
		||||
    {
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        _eventfd = eventfd(0, 0);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    EventFd::~EventFd()
 | 
			
		||||
    {
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        ::close(_eventfd);
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool EventFd::notify()
 | 
			
		||||
    {
 | 
			
		||||
#if defined(__linux__)
 | 
			
		||||
        if (_eventfd == -1) return false;
 | 
			
		||||
 | 
			
		||||
        // select will wake up when a non-zero value is written to our eventfd
 | 
			
		||||
        uint64_t value = 1;
 | 
			
		||||
 | 
			
		||||
        // we should write 8 bytes for an uint64_t
 | 
			
		||||
        return write(_eventfd, &value, sizeof(value)) == 8;
 | 
			
		||||
#else
 | 
			
		||||
        return true;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool EventFd::clear()
 | 
			
		||||
    {
 | 
			
		||||
#if defined(__linux__)
 | 
			
		||||
        if (_eventfd == -1) return false;
 | 
			
		||||
 | 
			
		||||
        // 0 is a special value ; select will not wake up 
 | 
			
		||||
        uint64_t value = 0;
 | 
			
		||||
 | 
			
		||||
        // we should write 8 bytes for an uint64_t
 | 
			
		||||
        return write(_eventfd, &value, sizeof(value)) == 8;
 | 
			
		||||
#else
 | 
			
		||||
        return true;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int EventFd::getFd()
 | 
			
		||||
    {
 | 
			
		||||
        return _eventfd;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXEventFd.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
{
 | 
			
		||||
    class EventFd {
 | 
			
		||||
    public:
 | 
			
		||||
        EventFd();
 | 
			
		||||
        virtual ~EventFd();
 | 
			
		||||
 | 
			
		||||
        bool notify();
 | 
			
		||||
        bool clear();
 | 
			
		||||
        int getFd();
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        int _eventfd;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										467
									
								
								ixwebsocket/IXHttpClient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,467 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXHttpClient.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXHttpClient.h"
 | 
			
		||||
#include "IXUrlParser.h"
 | 
			
		||||
#include "IXWebSocketHttpHeaders.h"
 | 
			
		||||
#include "IXSocketFactory.h"
 | 
			
		||||
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <iomanip>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include <zlib.h>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    const std::string HttpClient::kPost = "POST";
 | 
			
		||||
    const std::string HttpClient::kGet = "GET";
 | 
			
		||||
    const std::string HttpClient::kHead = "HEAD";
 | 
			
		||||
 | 
			
		||||
    HttpClient::HttpClient()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpClient::~HttpClient()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpResponse HttpClient::request(
 | 
			
		||||
        const std::string& url,
 | 
			
		||||
        const std::string& verb,
 | 
			
		||||
        const std::string& body,
 | 
			
		||||
        const HttpRequestArgs& args,
 | 
			
		||||
        int redirects)
 | 
			
		||||
    {
 | 
			
		||||
        uint64_t uploadSize = 0;
 | 
			
		||||
        uint64_t downloadSize = 0;
 | 
			
		||||
        int code = 0;
 | 
			
		||||
        WebSocketHttpHeaders headers;
 | 
			
		||||
        std::string payload;
 | 
			
		||||
 | 
			
		||||
        std::string protocol, host, path, query;
 | 
			
		||||
        int port;
 | 
			
		||||
        bool websocket = false;
 | 
			
		||||
 | 
			
		||||
        if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "Cannot parse url: " << url;
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_UrlMalformed,
 | 
			
		||||
                                   headers, payload, ss.str(),
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool tls = protocol == "https";
 | 
			
		||||
        std::string errorMsg;
 | 
			
		||||
        _socket = createSocket(tls, errorMsg);
 | 
			
		||||
 | 
			
		||||
        if (!_socket)
 | 
			
		||||
        {
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_CannotCreateSocket,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Build request string
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << verb << " " << path << " HTTP/1.1\r\n";
 | 
			
		||||
        ss << "Host: " << host << "\r\n";
 | 
			
		||||
        ss << "User-Agent: ixwebsocket/1.0.0" << "\r\n";
 | 
			
		||||
        ss << "Accept: */*" << "\r\n";
 | 
			
		||||
 | 
			
		||||
        if (args.compress)
 | 
			
		||||
        {
 | 
			
		||||
            ss << "Accept-Encoding: gzip" << "\r\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Append extra headers
 | 
			
		||||
        for (auto&& it : args.extraHeaders)
 | 
			
		||||
        {
 | 
			
		||||
            ss << it.first << ": " << it.second << "\r\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (verb == kPost)
 | 
			
		||||
        {
 | 
			
		||||
            ss << "Content-Length: " << body.size() << "\r\n";
 | 
			
		||||
 | 
			
		||||
            // Set default Content-Type if unspecified
 | 
			
		||||
            if (args.extraHeaders.find("Content-Type") == args.extraHeaders.end())
 | 
			
		||||
            {
 | 
			
		||||
                ss << "Content-Type: application/x-www-form-urlencoded" << "\r\n";
 | 
			
		||||
            }
 | 
			
		||||
            ss << "\r\n";
 | 
			
		||||
            ss << body;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            ss << "\r\n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string req(ss.str());
 | 
			
		||||
        std::string errMsg;
 | 
			
		||||
        std::atomic<bool> requestInitCancellation(false);
 | 
			
		||||
 | 
			
		||||
        // Make a cancellation object dealing with connection timeout
 | 
			
		||||
        auto isCancellationRequested =
 | 
			
		||||
            makeCancellationRequestWithTimeout(args.connectTimeout, requestInitCancellation);
 | 
			
		||||
 | 
			
		||||
        bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
 | 
			
		||||
        if (!success)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "Cannot connect to url: " << url;
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_CannotConnect,
 | 
			
		||||
                                   headers, payload, ss.str(),
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Make a new cancellation object dealing with transfer timeout
 | 
			
		||||
        isCancellationRequested =
 | 
			
		||||
            makeCancellationRequestWithTimeout(args.transferTimeout, requestInitCancellation);
 | 
			
		||||
 | 
			
		||||
        if (args.verbose)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "Sending " << verb << " request "
 | 
			
		||||
               << "to " << host << ":" << port << std::endl
 | 
			
		||||
               << "request size: " << req.size() << " bytes" << std::endl
 | 
			
		||||
               << "=============" << std::endl
 | 
			
		||||
               << req
 | 
			
		||||
               << "=============" << std::endl
 | 
			
		||||
               << std::endl;
 | 
			
		||||
 | 
			
		||||
            log(ss.str(), args);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!_socket->writeBytes(req, isCancellationRequested))
 | 
			
		||||
        {
 | 
			
		||||
            std::string errorMsg("Cannot send request");
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_SendError,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        uploadSize = req.size();
 | 
			
		||||
 | 
			
		||||
        auto lineResult = _socket->readLine(isCancellationRequested);
 | 
			
		||||
        auto lineValid = lineResult.first;
 | 
			
		||||
        auto line = lineResult.second;
 | 
			
		||||
 | 
			
		||||
        if (!lineValid)
 | 
			
		||||
        {
 | 
			
		||||
            std::string errorMsg("Cannot retrieve status line");
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_CannotReadStatusLine,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (args.verbose)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "Status line " << line;
 | 
			
		||||
            log(ss.str(), args);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (sscanf(line.c_str(), "HTTP/1.1 %d", &code) != 1)
 | 
			
		||||
        {
 | 
			
		||||
            std::string errorMsg("Cannot parse response code from status line");
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_MissingStatus,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto result = parseHttpHeaders(_socket, isCancellationRequested);
 | 
			
		||||
        auto headersValid = result.first;
 | 
			
		||||
        headers = result.second;
 | 
			
		||||
 | 
			
		||||
        if (!headersValid)
 | 
			
		||||
        {
 | 
			
		||||
            std::string errorMsg("Cannot parse http headers");
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_HeaderParsingError,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Redirect ?
 | 
			
		||||
        if ((code >= 301 && code <= 308) && args.followRedirects)
 | 
			
		||||
        {
 | 
			
		||||
            if (headers.find("Location") == headers.end())
 | 
			
		||||
            {
 | 
			
		||||
                std::string errorMsg("Missing location header for redirect");
 | 
			
		||||
                return std::make_tuple(code, HttpErrorCode_MissingLocation,
 | 
			
		||||
                                       headers, payload, errorMsg,
 | 
			
		||||
                                       uploadSize, downloadSize);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (redirects >= args.maxRedirects)
 | 
			
		||||
            {
 | 
			
		||||
                std::stringstream ss;
 | 
			
		||||
                ss << "Too many redirects: " << redirects;
 | 
			
		||||
                return std::make_tuple(code, HttpErrorCode_TooManyRedirects,
 | 
			
		||||
                                       headers, payload, ss.str(),
 | 
			
		||||
                                       uploadSize, downloadSize);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Recurse
 | 
			
		||||
            std::string location = headers["Location"];
 | 
			
		||||
            return request(location, verb, body, args, redirects+1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (verb == "HEAD")
 | 
			
		||||
        {
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_Ok,
 | 
			
		||||
                                   headers, payload, std::string(),
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Parse response:
 | 
			
		||||
        if (headers.find("Content-Length") != headers.end())
 | 
			
		||||
        {
 | 
			
		||||
            ssize_t contentLength = -1;
 | 
			
		||||
            ss.str("");
 | 
			
		||||
            ss << headers["Content-Length"];
 | 
			
		||||
            ss >> contentLength;
 | 
			
		||||
 | 
			
		||||
            payload.reserve(contentLength);
 | 
			
		||||
 | 
			
		||||
            auto chunkResult = _socket->readBytes(contentLength,
 | 
			
		||||
                                                  args.onProgressCallback,
 | 
			
		||||
                                                  isCancellationRequested);
 | 
			
		||||
            if (!chunkResult.first)
 | 
			
		||||
            {
 | 
			
		||||
                errorMsg = "Cannot read chunk";
 | 
			
		||||
                return std::make_tuple(code, HttpErrorCode_ChunkReadError,
 | 
			
		||||
                                       headers, payload, errorMsg,
 | 
			
		||||
                                       uploadSize, downloadSize);
 | 
			
		||||
            }
 | 
			
		||||
            payload += chunkResult.second;
 | 
			
		||||
        }
 | 
			
		||||
        else if (headers.find("Transfer-Encoding") != headers.end() &&
 | 
			
		||||
                 headers["Transfer-Encoding"] == "chunked")
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
 | 
			
		||||
            while (true)
 | 
			
		||||
            {
 | 
			
		||||
                lineResult = _socket->readLine(isCancellationRequested);
 | 
			
		||||
                line = lineResult.second;
 | 
			
		||||
 | 
			
		||||
                if (!lineResult.first)
 | 
			
		||||
                {
 | 
			
		||||
                    return std::make_tuple(code, HttpErrorCode_ChunkReadError,
 | 
			
		||||
                                           headers, payload, errorMsg,
 | 
			
		||||
                                           uploadSize, downloadSize);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                uint64_t chunkSize;
 | 
			
		||||
                ss.str("");
 | 
			
		||||
                ss << std::hex << line;
 | 
			
		||||
                ss >> chunkSize;
 | 
			
		||||
 | 
			
		||||
                if (args.verbose)
 | 
			
		||||
                {
 | 
			
		||||
                    std::stringstream oss;
 | 
			
		||||
                    oss << "Reading " << chunkSize << " bytes"
 | 
			
		||||
                        << std::endl;
 | 
			
		||||
                    log(oss.str(), args);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                payload.reserve(payload.size() + chunkSize);
 | 
			
		||||
 | 
			
		||||
                // Read a chunk
 | 
			
		||||
                auto chunkResult = _socket->readBytes(chunkSize,
 | 
			
		||||
                                                      args.onProgressCallback,
 | 
			
		||||
                                                      isCancellationRequested);
 | 
			
		||||
                if (!chunkResult.first)
 | 
			
		||||
                {
 | 
			
		||||
                    errorMsg = "Cannot read chunk";
 | 
			
		||||
                    return std::make_tuple(code, HttpErrorCode_ChunkReadError,
 | 
			
		||||
                                           headers, payload, errorMsg,
 | 
			
		||||
                                           uploadSize, downloadSize);
 | 
			
		||||
                }
 | 
			
		||||
                payload += chunkResult.second;
 | 
			
		||||
 | 
			
		||||
                // Read the line that terminates the chunk (\r\n)
 | 
			
		||||
                lineResult = _socket->readLine(isCancellationRequested);
 | 
			
		||||
 | 
			
		||||
                if (!lineResult.first)
 | 
			
		||||
                {
 | 
			
		||||
                    return std::make_tuple(code, HttpErrorCode_ChunkReadError,
 | 
			
		||||
                                           headers, payload, errorMsg,
 | 
			
		||||
                                           uploadSize, downloadSize);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (chunkSize == 0) break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (code == 204)
 | 
			
		||||
        {
 | 
			
		||||
            ; // 204 is NoContent response code
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            std::string errorMsg("Cannot read http body");
 | 
			
		||||
            return std::make_tuple(code, HttpErrorCode_CannotReadBody,
 | 
			
		||||
                                   headers, payload, errorMsg,
 | 
			
		||||
                                   uploadSize, downloadSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        downloadSize = payload.size();
 | 
			
		||||
 | 
			
		||||
        // If the content was compressed with gzip, decode it
 | 
			
		||||
        if (headers["Content-Encoding"] == "gzip")
 | 
			
		||||
        {
 | 
			
		||||
            std::string decompressedPayload;
 | 
			
		||||
            if (!gzipInflate(payload, decompressedPayload))
 | 
			
		||||
            {
 | 
			
		||||
                std::string errorMsg("Error decompressing payload");
 | 
			
		||||
                return std::make_tuple(code, HttpErrorCode_Gzip,
 | 
			
		||||
                                       headers, payload, errorMsg,
 | 
			
		||||
                                       uploadSize, downloadSize);
 | 
			
		||||
            }
 | 
			
		||||
            payload = decompressedPayload;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::make_tuple(code, HttpErrorCode_Ok,
 | 
			
		||||
                               headers, payload, std::string(),
 | 
			
		||||
                               uploadSize, downloadSize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpResponse HttpClient::get(const std::string& url,
 | 
			
		||||
                                 const HttpRequestArgs& args)
 | 
			
		||||
    {
 | 
			
		||||
        return request(url, kGet, std::string(), args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpResponse HttpClient::head(const std::string& url,
 | 
			
		||||
                                  const HttpRequestArgs& args)
 | 
			
		||||
    {
 | 
			
		||||
        return request(url, kHead, std::string(), args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpResponse HttpClient::post(const std::string& url,
 | 
			
		||||
                                  const HttpParameters& httpParameters,
 | 
			
		||||
                                  const HttpRequestArgs& args)
 | 
			
		||||
    {
 | 
			
		||||
        return request(url, kPost, serializeHttpParameters(httpParameters), args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HttpResponse HttpClient::post(const std::string& url,
 | 
			
		||||
                                  const std::string& body,
 | 
			
		||||
                                  const HttpRequestArgs& args)
 | 
			
		||||
    {
 | 
			
		||||
        return request(url, kPost, body, args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string HttpClient::urlEncode(const std::string& value)
 | 
			
		||||
    {
 | 
			
		||||
        std::ostringstream escaped;
 | 
			
		||||
        escaped.fill('0');
 | 
			
		||||
        escaped << std::hex;
 | 
			
		||||
 | 
			
		||||
        for (std::string::const_iterator i = value.begin(), n = value.end();
 | 
			
		||||
             i != n; ++i)
 | 
			
		||||
        {
 | 
			
		||||
            std::string::value_type c = (*i);
 | 
			
		||||
 | 
			
		||||
            // Keep alphanumeric and other accepted characters intact
 | 
			
		||||
            if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
 | 
			
		||||
            {
 | 
			
		||||
                escaped << c;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Any other characters are percent-encoded
 | 
			
		||||
            escaped << std::uppercase;
 | 
			
		||||
            escaped << '%' << std::setw(2) << int((unsigned char) c);
 | 
			
		||||
            escaped << std::nouppercase;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return escaped.str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string HttpClient::serializeHttpParameters(const HttpParameters& httpParameters)
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        size_t count = httpParameters.size();
 | 
			
		||||
        size_t i = 0;
 | 
			
		||||
 | 
			
		||||
        for (auto&& it : httpParameters)
 | 
			
		||||
        {
 | 
			
		||||
            ss << urlEncode(it.first)
 | 
			
		||||
               << "="
 | 
			
		||||
               << urlEncode(it.second);
 | 
			
		||||
 | 
			
		||||
            if (i++ < (count-1))
 | 
			
		||||
            {
 | 
			
		||||
               ss << "&";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return ss.str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool HttpClient::gzipInflate(
 | 
			
		||||
        const std::string& in,
 | 
			
		||||
        std::string& out)
 | 
			
		||||
    {
 | 
			
		||||
        z_stream inflateState;
 | 
			
		||||
        std::memset(&inflateState, 0, sizeof(inflateState));
 | 
			
		||||
 | 
			
		||||
        inflateState.zalloc = Z_NULL;
 | 
			
		||||
        inflateState.zfree = Z_NULL;
 | 
			
		||||
        inflateState.opaque = Z_NULL;
 | 
			
		||||
        inflateState.avail_in = 0;
 | 
			
		||||
        inflateState.next_in = Z_NULL;
 | 
			
		||||
 | 
			
		||||
        if (inflateInit2(&inflateState, 16+MAX_WBITS) != Z_OK)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        inflateState.avail_in = (uInt) in.size();
 | 
			
		||||
        inflateState.next_in = (unsigned char *)(const_cast<char *>(in.data()));
 | 
			
		||||
 | 
			
		||||
        const int kBufferSize = 1 << 14;
 | 
			
		||||
 | 
			
		||||
        std::unique_ptr<unsigned char[]> compressBuffer =
 | 
			
		||||
            std::make_unique<unsigned char[]>(kBufferSize);
 | 
			
		||||
 | 
			
		||||
        do
 | 
			
		||||
        {
 | 
			
		||||
            inflateState.avail_out = (uInt) kBufferSize;
 | 
			
		||||
            inflateState.next_out = compressBuffer.get();
 | 
			
		||||
 | 
			
		||||
            int ret = inflate(&inflateState, Z_SYNC_FLUSH);
 | 
			
		||||
 | 
			
		||||
            if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
 | 
			
		||||
            {
 | 
			
		||||
                inflateEnd(&inflateState);
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            out.append(
 | 
			
		||||
                reinterpret_cast<char *>(compressBuffer.get()),
 | 
			
		||||
                kBufferSize - inflateState.avail_out
 | 
			
		||||
            );
 | 
			
		||||
        } while (inflateState.avail_out == 0);
 | 
			
		||||
 | 
			
		||||
        inflateEnd(&inflateState);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void HttpClient::log(const std::string& msg,
 | 
			
		||||
                         const HttpRequestArgs& args)
 | 
			
		||||
    {
 | 
			
		||||
        if (args.logger)
 | 
			
		||||
        {
 | 
			
		||||
            args.logger(msg);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								ixwebsocket/IXHttpClient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,107 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXHttpClient.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <map>
 | 
			
		||||
 | 
			
		||||
#include "IXSocket.h"
 | 
			
		||||
#include "IXWebSocketHttpHeaders.h"
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    enum HttpErrorCode
 | 
			
		||||
    {
 | 
			
		||||
        HttpErrorCode_Ok = 0,
 | 
			
		||||
        HttpErrorCode_CannotConnect = 1,
 | 
			
		||||
        HttpErrorCode_Timeout = 2,
 | 
			
		||||
        HttpErrorCode_Gzip = 3,
 | 
			
		||||
        HttpErrorCode_UrlMalformed = 4,
 | 
			
		||||
        HttpErrorCode_CannotCreateSocket = 5,
 | 
			
		||||
        HttpErrorCode_SendError = 6,
 | 
			
		||||
        HttpErrorCode_ReadError = 7,
 | 
			
		||||
        HttpErrorCode_CannotReadStatusLine = 8,
 | 
			
		||||
        HttpErrorCode_MissingStatus = 9,
 | 
			
		||||
        HttpErrorCode_HeaderParsingError = 10,
 | 
			
		||||
        HttpErrorCode_MissingLocation = 11,
 | 
			
		||||
        HttpErrorCode_TooManyRedirects = 12,
 | 
			
		||||
        HttpErrorCode_ChunkReadError = 13,
 | 
			
		||||
        HttpErrorCode_CannotReadBody = 14
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using HttpResponse = std::tuple<int, // status
 | 
			
		||||
                                    HttpErrorCode, // error code
 | 
			
		||||
                                    WebSocketHttpHeaders,
 | 
			
		||||
                                    std::string, // payload
 | 
			
		||||
                                    std::string, // error msg
 | 
			
		||||
                                    uint64_t,    // upload size
 | 
			
		||||
                                    uint64_t>;   // download size
 | 
			
		||||
 | 
			
		||||
    using HttpParameters = std::map<std::string, std::string>;
 | 
			
		||||
    using Logger = std::function<void(const std::string&)>;
 | 
			
		||||
 | 
			
		||||
    struct HttpRequestArgs
 | 
			
		||||
    {
 | 
			
		||||
        std::string url;
 | 
			
		||||
        WebSocketHttpHeaders extraHeaders;
 | 
			
		||||
        std::string body;
 | 
			
		||||
        int connectTimeout;
 | 
			
		||||
        int transferTimeout;
 | 
			
		||||
        bool followRedirects;
 | 
			
		||||
        int maxRedirects;
 | 
			
		||||
        bool verbose;
 | 
			
		||||
        bool compress;
 | 
			
		||||
        Logger logger;
 | 
			
		||||
        OnProgressCallback onProgressCallback;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class HttpClient {
 | 
			
		||||
    public:
 | 
			
		||||
        HttpClient();
 | 
			
		||||
        ~HttpClient();
 | 
			
		||||
 | 
			
		||||
        HttpResponse get(const std::string& url,
 | 
			
		||||
                         const HttpRequestArgs& args);
 | 
			
		||||
        HttpResponse head(const std::string& url,
 | 
			
		||||
                          const HttpRequestArgs& args);
 | 
			
		||||
 | 
			
		||||
        HttpResponse post(const std::string& url,
 | 
			
		||||
                          const HttpParameters& httpParameters,
 | 
			
		||||
                          const HttpRequestArgs& args);
 | 
			
		||||
        HttpResponse post(const std::string& url,
 | 
			
		||||
                          const std::string& body,
 | 
			
		||||
                          const HttpRequestArgs& args);
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        HttpResponse request(const std::string& url,
 | 
			
		||||
                             const std::string& verb,
 | 
			
		||||
                             const std::string& body,
 | 
			
		||||
                             const HttpRequestArgs& args,
 | 
			
		||||
                             int redirects = 0);
 | 
			
		||||
 | 
			
		||||
        std::string serializeHttpParameters(const HttpParameters& httpParameters);
 | 
			
		||||
 | 
			
		||||
        std::string urlEncode(const std::string& value);
 | 
			
		||||
 | 
			
		||||
        void log(const std::string& msg, const HttpRequestArgs& args);
 | 
			
		||||
 | 
			
		||||
        bool gzipInflate(
 | 
			
		||||
            const std::string& in,
 | 
			
		||||
            std::string& out);
 | 
			
		||||
 | 
			
		||||
        std::shared_ptr<Socket> _socket;
 | 
			
		||||
 | 
			
		||||
        const static std::string kPost;
 | 
			
		||||
        const static std::string kGet;
 | 
			
		||||
        const static std::string kHead;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								ixwebsocket/IXProgressCallback.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,14 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXProgressCallback.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    using OnProgressCallback = std::function<bool(int current, int total)>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								ixwebsocket/IXSelectInterrupt.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,46 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterrupt.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterrupt.h"
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    SelectInterrupt::SelectInterrupt()
 | 
			
		||||
    {
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SelectInterrupt::~SelectInterrupt()
 | 
			
		||||
    {
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterrupt::init(std::string& /*errorMsg*/)
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterrupt::notify(uint64_t /*value*/)
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint64_t SelectInterrupt::read()
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterrupt::clear()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int SelectInterrupt::getFd() const
 | 
			
		||||
    {
 | 
			
		||||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								ixwebsocket/IXSelectInterrupt.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterrupt.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterrupt {
 | 
			
		||||
    public:
 | 
			
		||||
        SelectInterrupt();
 | 
			
		||||
        virtual ~SelectInterrupt();
 | 
			
		||||
 | 
			
		||||
        virtual bool init(std::string& errorMsg);
 | 
			
		||||
 | 
			
		||||
        virtual bool notify(uint64_t value);
 | 
			
		||||
        virtual bool clear();
 | 
			
		||||
        virtual uint64_t read();
 | 
			
		||||
        virtual int getFd() const;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										116
									
								
								ixwebsocket/IXSelectInterruptEventFd.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,116 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptEventFd.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// On Linux we use eventd to wake up select.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Linux/Android has a special type of virtual files. select(2) will react
 | 
			
		||||
// when reading/writing to those files, unlike closing sockets.
 | 
			
		||||
//
 | 
			
		||||
// https://linux.die.net/man/2/eventfd
 | 
			
		||||
// http://www.sourcexr.com/articles/2013/10/26/lightweight-inter-process-signaling-with-eventfd
 | 
			
		||||
//
 | 
			
		||||
// eventfd was added in Linux kernel 2.x, and our oldest Android (Kitkat 4.4)
 | 
			
		||||
// is on Kernel 3.x
 | 
			
		||||
//
 | 
			
		||||
// cf Android/Kernel table here
 | 
			
		||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
 | 
			
		||||
//
 | 
			
		||||
// On macOS we use UNIX pipes to wake up select.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterruptEventFd.h"
 | 
			
		||||
 | 
			
		||||
#include <sys/eventfd.h>
 | 
			
		||||
 | 
			
		||||
#include <unistd.h> // for write
 | 
			
		||||
#include <string.h> // for strerror
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    SelectInterruptEventFd::SelectInterruptEventFd()
 | 
			
		||||
    {
 | 
			
		||||
        _eventfd = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SelectInterruptEventFd::~SelectInterruptEventFd()
 | 
			
		||||
    {
 | 
			
		||||
        ::close(_eventfd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptEventFd::init(std::string& errorMsg)
 | 
			
		||||
    {
 | 
			
		||||
        // calling init twice is a programming error
 | 
			
		||||
        assert(_eventfd == -1);
 | 
			
		||||
 | 
			
		||||
        _eventfd = eventfd(0, 0);
 | 
			
		||||
        if (_eventfd < 0)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptEventFd::init() failed in eventfd()"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _eventfd = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fcntl(_eventfd, F_SETFL, O_NONBLOCK) == -1)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptEventFd::init() failed in fcntl() call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _eventfd = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptEventFd::notify(uint64_t value)
 | 
			
		||||
    {
 | 
			
		||||
        int fd = _eventfd;
 | 
			
		||||
 | 
			
		||||
        if (fd == -1) return false;
 | 
			
		||||
 | 
			
		||||
        // we should write 8 bytes for an uint64_t
 | 
			
		||||
        return write(fd, &value, sizeof(value)) == 8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: return max uint64_t for errors ?
 | 
			
		||||
    uint64_t SelectInterruptEventFd::read()
 | 
			
		||||
    {
 | 
			
		||||
        int fd = _eventfd;
 | 
			
		||||
 | 
			
		||||
        uint64_t value = 0;
 | 
			
		||||
        ::read(fd, &value, sizeof(value));
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptEventFd::clear()
 | 
			
		||||
    {
 | 
			
		||||
        if (_eventfd == -1) return false;
 | 
			
		||||
 | 
			
		||||
        // 0 is a special value ; select will not wake up
 | 
			
		||||
        uint64_t value = 0;
 | 
			
		||||
 | 
			
		||||
        // we should write 8 bytes for an uint64_t
 | 
			
		||||
        return write(_eventfd, &value, sizeof(value)) == 8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int SelectInterruptEventFd::getFd() const
 | 
			
		||||
    {
 | 
			
		||||
        return _eventfd;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								ixwebsocket/IXSelectInterruptEventFd.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,32 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptEventFd.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterrupt.h"
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterruptEventFd : public SelectInterrupt {
 | 
			
		||||
    public:
 | 
			
		||||
        SelectInterruptEventFd();
 | 
			
		||||
        virtual ~SelectInterruptEventFd();
 | 
			
		||||
 | 
			
		||||
        bool init(std::string& errorMsg) final;
 | 
			
		||||
 | 
			
		||||
        bool notify(uint64_t value) final;
 | 
			
		||||
        bool clear() final;
 | 
			
		||||
        uint64_t read() final;
 | 
			
		||||
        int getFd() const final;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        int _eventfd;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								ixwebsocket/IXSelectInterruptFactory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptFactory.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterruptFactory.h"
 | 
			
		||||
 | 
			
		||||
#if defined(__linux__) || defined(__APPLE__)
 | 
			
		||||
# include <ixwebsocket/IXSelectInterruptPipe.h>
 | 
			
		||||
#else
 | 
			
		||||
# include <ixwebsocket/IXSelectInterrupt.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    std::shared_ptr<SelectInterrupt> createSelectInterrupt()
 | 
			
		||||
    {
 | 
			
		||||
#if defined(__linux__) || defined(__APPLE__)
 | 
			
		||||
        return std::make_shared<SelectInterruptPipe>();
 | 
			
		||||
#else
 | 
			
		||||
        return std::make_shared<SelectInterrupt>();
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								ixwebsocket/IXSelectInterruptFactory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptFactory.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterrupt;
 | 
			
		||||
    std::shared_ptr<SelectInterrupt> createSelectInterrupt();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										138
									
								
								ixwebsocket/IXSelectInterruptPipe.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,138 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptPipe.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// On macOS we use UNIX pipes to wake up select.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterruptPipe.h"
 | 
			
		||||
 | 
			
		||||
#include <unistd.h> // for write
 | 
			
		||||
#include <string.h> // for strerror
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <assert.h>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    // File descriptor at index 0 in _fildes is the read end of the pipe
 | 
			
		||||
    // File descriptor at index 1 in _fildes is the write end of the pipe
 | 
			
		||||
    const int SelectInterruptPipe::kPipeReadIndex = 0;
 | 
			
		||||
    const int SelectInterruptPipe::kPipeWriteIndex = 1;
 | 
			
		||||
 | 
			
		||||
    SelectInterruptPipe::SelectInterruptPipe()
 | 
			
		||||
    {
 | 
			
		||||
        _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
        _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SelectInterruptPipe::~SelectInterruptPipe()
 | 
			
		||||
    {
 | 
			
		||||
        ::close(_fildes[kPipeReadIndex]);
 | 
			
		||||
        ::close(_fildes[kPipeWriteIndex]);
 | 
			
		||||
        _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
        _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptPipe::init(std::string& errorMsg)
 | 
			
		||||
    {
 | 
			
		||||
        // calling init twice is a programming error
 | 
			
		||||
        assert(_fildes[kPipeReadIndex] == -1);
 | 
			
		||||
        assert(_fildes[kPipeWriteIndex] == -1);
 | 
			
		||||
 | 
			
		||||
        if (pipe(_fildes) < 0)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptPipe::init() failed in pipe() call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fcntl(_fildes[kPipeReadIndex], F_SETFL, O_NONBLOCK) == -1)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
            _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fcntl(_fildes[kPipeWriteIndex], F_SETFL, O_NONBLOCK) == -1)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptPipe::init() failed in fcntl(..., O_NONBLOCK) call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
            _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#ifdef F_SETNOSIGPIPE
 | 
			
		||||
        if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptPipe::init() failed in fcntl(.... F_SETNOSIGPIPE) call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
            _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (fcntl(_fildes[kPipeWriteIndex], F_SETNOSIGPIPE, 1) == -1)
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << "SelectInterruptPipe::init() failed in fcntl(..., F_SETNOSIGPIPE) call"
 | 
			
		||||
               << " : " << strerror(errno);
 | 
			
		||||
            errorMsg = ss.str();
 | 
			
		||||
 | 
			
		||||
            _fildes[kPipeReadIndex] = -1;
 | 
			
		||||
            _fildes[kPipeWriteIndex] = -1;
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptPipe::notify(uint64_t value)
 | 
			
		||||
    {
 | 
			
		||||
        int fd = _fildes[kPipeWriteIndex];
 | 
			
		||||
        if (fd == -1) return false;
 | 
			
		||||
 | 
			
		||||
        // we should write 8 bytes for an uint64_t
 | 
			
		||||
        return write(fd, &value, sizeof(value)) == 8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: return max uint64_t for errors ?
 | 
			
		||||
    uint64_t SelectInterruptPipe::read()
 | 
			
		||||
    {
 | 
			
		||||
        int fd = _fildes[kPipeReadIndex];
 | 
			
		||||
 | 
			
		||||
        uint64_t value = 0;
 | 
			
		||||
        ::read(fd, &value, sizeof(value));
 | 
			
		||||
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SelectInterruptPipe::clear()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int SelectInterruptPipe::getFd() const
 | 
			
		||||
    {
 | 
			
		||||
        return _fildes[kPipeReadIndex];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								ixwebsocket/IXSelectInterruptPipe.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,39 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSelectInterruptPipe.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "IXSelectInterrupt.h"
 | 
			
		||||
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SelectInterruptPipe : public SelectInterrupt {
 | 
			
		||||
    public:
 | 
			
		||||
        SelectInterruptPipe();
 | 
			
		||||
        virtual ~SelectInterruptPipe();
 | 
			
		||||
 | 
			
		||||
        bool init(std::string& errorMsg) final;
 | 
			
		||||
 | 
			
		||||
        bool notify(uint64_t value) final;
 | 
			
		||||
        bool clear() final;
 | 
			
		||||
        uint64_t read() final;
 | 
			
		||||
        int getFd() const final;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        // Store file descriptors used by the communication pipe. Communication
 | 
			
		||||
        // happens between a control thread and a background thread, which is
 | 
			
		||||
        // blocked on select.
 | 
			
		||||
        int _fildes[2];
 | 
			
		||||
 | 
			
		||||
        // Used to identify the read/write idx
 | 
			
		||||
        static const int kPipeReadIndex;
 | 
			
		||||
        static const int kPipeWriteIndex;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
#include "IXSocket.h"
 | 
			
		||||
#include "IXSocketConnect.h"
 | 
			
		||||
#include "IXNetSystem.h"
 | 
			
		||||
#include "IXSelectInterrupt.h"
 | 
			
		||||
#include "IXSelectInterruptFactory.h"
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
@@ -15,20 +17,23 @@
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <sys/types.h>
 | 
			
		||||
#include <poll.h>
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
 | 
			
		||||
    const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
 | 
			
		||||
    const uint64_t Socket::kSendRequest = 1;
 | 
			
		||||
    const uint64_t Socket::kCloseRequest = 2;
 | 
			
		||||
    constexpr size_t Socket::kChunkSize;
 | 
			
		||||
 | 
			
		||||
    Socket::Socket(int fd) : 
 | 
			
		||||
        _sockfd(fd)
 | 
			
		||||
    Socket::Socket(int fd) :
 | 
			
		||||
        _sockfd(fd),
 | 
			
		||||
        _selectInterrupt(createSelectInterrupt())
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Socket::~Socket()
 | 
			
		||||
@@ -40,44 +45,93 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        if (_sockfd == -1)
 | 
			
		||||
        {
 | 
			
		||||
            onPollCallback(PollResultType_Error);
 | 
			
		||||
            if (onPollCallback) onPollCallback(PollResultType::Error);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        constexpr int nfds = 2;
 | 
			
		||||
#else
 | 
			
		||||
        constexpr int nfds = 1;
 | 
			
		||||
#endif
 | 
			
		||||
        PollResultType pollResult = isReadyToRead(1000 * timeoutSecs);
 | 
			
		||||
 | 
			
		||||
        struct pollfd fds[nfds];
 | 
			
		||||
        fds[0].fd = _sockfd;
 | 
			
		||||
        fds[0].events = POLLIN;
 | 
			
		||||
        if (onPollCallback) onPollCallback(pollResult);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#ifdef __linux__
 | 
			
		||||
        fds[1].fd = _eventfd.getFd();
 | 
			
		||||
        fds[1].events = POLLIN;
 | 
			
		||||
#endif
 | 
			
		||||
        int ret = ::poll(fds, nfds, timeoutSecs * 1000);
 | 
			
		||||
    PollResultType Socket::select(bool readyToRead, int timeoutMs)
 | 
			
		||||
    {
 | 
			
		||||
        fd_set rfds;
 | 
			
		||||
        fd_set wfds;
 | 
			
		||||
        FD_ZERO(&rfds);
 | 
			
		||||
        FD_ZERO(&wfds);
 | 
			
		||||
 | 
			
		||||
        PollResultType pollResult = PollResultType_ReadyForRead;
 | 
			
		||||
        fd_set* fds = (readyToRead) ? &rfds : & wfds;
 | 
			
		||||
        FD_SET(_sockfd, fds);
 | 
			
		||||
 | 
			
		||||
        // File descriptor used to interrupt select when needed
 | 
			
		||||
        int interruptFd = _selectInterrupt->getFd();
 | 
			
		||||
        if (interruptFd != -1)
 | 
			
		||||
        {
 | 
			
		||||
            FD_SET(interruptFd, fds);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        struct timeval timeout;
 | 
			
		||||
        timeout.tv_sec = timeoutMs / 1000;
 | 
			
		||||
        timeout.tv_usec = (timeoutMs < 1000) ? 0 : 1000 * (timeoutMs % 1000);
 | 
			
		||||
 | 
			
		||||
        // Compute the highest fd.
 | 
			
		||||
        int sockfd = _sockfd;
 | 
			
		||||
        int nfds = (std::max)(sockfd, interruptFd);
 | 
			
		||||
 | 
			
		||||
        int ret = ::select(nfds + 1, &rfds, &wfds, nullptr,
 | 
			
		||||
                           (timeoutMs < 0) ? nullptr : &timeout);
 | 
			
		||||
 | 
			
		||||
        PollResultType pollResult = PollResultType::ReadyForRead;
 | 
			
		||||
        if (ret < 0)
 | 
			
		||||
        {
 | 
			
		||||
            pollResult = PollResultType_Error;
 | 
			
		||||
            pollResult = PollResultType::Error;
 | 
			
		||||
        }
 | 
			
		||||
        else if (ret == 0)
 | 
			
		||||
        {
 | 
			
		||||
            pollResult = PollResultType_Timeout;
 | 
			
		||||
            pollResult = PollResultType::Timeout;
 | 
			
		||||
        }
 | 
			
		||||
        else if (interruptFd != -1 && FD_ISSET(interruptFd, &rfds))
 | 
			
		||||
        {
 | 
			
		||||
            uint64_t value = _selectInterrupt->read();
 | 
			
		||||
 | 
			
		||||
            if (value == kSendRequest)
 | 
			
		||||
            {
 | 
			
		||||
                pollResult = PollResultType::SendRequest;
 | 
			
		||||
            }
 | 
			
		||||
            else if (value == kCloseRequest)
 | 
			
		||||
            {
 | 
			
		||||
                pollResult = PollResultType::CloseRequest;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (sockfd != -1 && readyToRead && FD_ISSET(sockfd, &rfds))
 | 
			
		||||
        {
 | 
			
		||||
            pollResult = PollResultType::ReadyForRead;
 | 
			
		||||
        }
 | 
			
		||||
        else if (sockfd != -1 && !readyToRead && FD_ISSET(sockfd, &wfds))
 | 
			
		||||
        {
 | 
			
		||||
            pollResult = PollResultType::ReadyForWrite;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onPollCallback(pollResult);
 | 
			
		||||
 | 
			
		||||
        return pollResult;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Socket::wakeUpFromPoll()
 | 
			
		||||
    PollResultType Socket::isReadyToRead(int timeoutMs)
 | 
			
		||||
    {
 | 
			
		||||
        // this will wake up the thread blocked on select, only needed on Linux
 | 
			
		||||
        _eventfd.notify();
 | 
			
		||||
        bool readyToRead = true;
 | 
			
		||||
        return select(readyToRead, timeoutMs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PollResultType Socket::isReadyToWrite(int timeoutMs)
 | 
			
		||||
    {
 | 
			
		||||
        bool readyToRead = false;
 | 
			
		||||
        return select(readyToRead, timeoutMs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Wake up from poll/select by writing to the pipe which is watched by select
 | 
			
		||||
    bool Socket::wakeUpFromPoll(uint8_t wakeUpCode)
 | 
			
		||||
    {
 | 
			
		||||
        return _selectInterrupt->notify(wakeUpCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Socket::connect(const std::string& host,
 | 
			
		||||
@@ -87,7 +141,7 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_socketMutex);
 | 
			
		||||
 | 
			
		||||
        if (!_eventfd.clear()) return false;
 | 
			
		||||
        if (!_selectInterrupt->clear()) return false;
 | 
			
		||||
 | 
			
		||||
        _sockfd = SocketConnect::connect(host, port, errMsg, isCancellationRequested);
 | 
			
		||||
        return _sockfd != -1;
 | 
			
		||||
@@ -146,69 +200,9 @@ namespace ix
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Socket::init()
 | 
			
		||||
    bool Socket::init(std::string& errorMsg)
 | 
			
		||||
    {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
        INT rc;
 | 
			
		||||
        WSADATA wsaData;
 | 
			
		||||
        
 | 
			
		||||
        rc = WSAStartup(MAKEWORD(2, 2), &wsaData);
 | 
			
		||||
        return rc != 0;
 | 
			
		||||
#else
 | 
			
		||||
        return true;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Socket::cleanup()
 | 
			
		||||
    {
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
        WSACleanup();
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Socket::readByte(void* buffer,
 | 
			
		||||
                          const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (isCancellationRequested()) return false;
 | 
			
		||||
 | 
			
		||||
            ssize_t ret;
 | 
			
		||||
            ret = recv(buffer, 1);
 | 
			
		||||
 | 
			
		||||
            // We read one byte, as needed, all good.
 | 
			
		||||
            if (ret == 1)
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            // There is possibly something to be read, try again
 | 
			
		||||
            else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
 | 
			
		||||
                                 getErrno() == EAGAIN))
 | 
			
		||||
            {
 | 
			
		||||
                // Wait with a timeout until something is written.
 | 
			
		||||
                // This way we are not busy looping
 | 
			
		||||
                fd_set rfds;
 | 
			
		||||
                struct timeval timeout;
 | 
			
		||||
                timeout.tv_sec = 0;
 | 
			
		||||
                timeout.tv_usec = 1 * 1000; // 1ms timeout
 | 
			
		||||
 | 
			
		||||
                FD_ZERO(&rfds);
 | 
			
		||||
                FD_SET(_sockfd, &rfds);
 | 
			
		||||
 | 
			
		||||
                if (select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
 | 
			
		||||
                    (errno == EBADF || errno == EINVAL))
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // There was an error during the read, abort
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return _selectInterrupt->init(errorMsg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Socket::writeBytes(const std::string& str,
 | 
			
		||||
@@ -216,7 +210,7 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (isCancellationRequested()) return false;
 | 
			
		||||
            if (isCancellationRequested && isCancellationRequested()) return false;
 | 
			
		||||
 | 
			
		||||
            char* buffer = const_cast<char*>(str.c_str());
 | 
			
		||||
            int len = (int) str.size();
 | 
			
		||||
@@ -228,7 +222,7 @@ namespace ix
 | 
			
		||||
            {
 | 
			
		||||
                return ret == len;
 | 
			
		||||
            }
 | 
			
		||||
            // There is possibly something to be write, try again
 | 
			
		||||
            // There is possibly something to be writen, try again
 | 
			
		||||
            else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
 | 
			
		||||
                                 getErrno() == EAGAIN))
 | 
			
		||||
            {
 | 
			
		||||
@@ -242,7 +236,42 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::pair<bool, std::string> Socket::readLine(const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    bool Socket::readByte(void* buffer,
 | 
			
		||||
                          const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (isCancellationRequested && isCancellationRequested()) return false;
 | 
			
		||||
 | 
			
		||||
            ssize_t ret;
 | 
			
		||||
            ret = recv(buffer, 1);
 | 
			
		||||
 | 
			
		||||
            // We read one byte, as needed, all good.
 | 
			
		||||
            if (ret == 1)
 | 
			
		||||
            {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            // There is possibly something to be read, try again
 | 
			
		||||
            else if (ret < 0 && (getErrno() == EWOULDBLOCK ||
 | 
			
		||||
                                 getErrno() == EAGAIN))
 | 
			
		||||
            {
 | 
			
		||||
                // Wait with a 1ms timeout until the socket is ready to read.
 | 
			
		||||
                // This way we are not busy looping
 | 
			
		||||
                if (isReadyToRead(1) == PollResultType::Error)
 | 
			
		||||
                {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // There was an error during the read, abort
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::pair<bool, std::string> Socket::readLine(
 | 
			
		||||
        const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        char c;
 | 
			
		||||
        std::string line;
 | 
			
		||||
@@ -252,7 +281,8 @@ namespace ix
 | 
			
		||||
        {
 | 
			
		||||
            if (!readByte(&c, isCancellationRequested))
 | 
			
		||||
            {
 | 
			
		||||
                return std::make_pair(false, std::string());
 | 
			
		||||
                // Return what we were able to read
 | 
			
		||||
                return std::make_pair(false, line);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            line += c;
 | 
			
		||||
@@ -260,4 +290,52 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        return std::make_pair(true, line);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::pair<bool, std::string> Socket::readBytes(
 | 
			
		||||
        size_t length,
 | 
			
		||||
        const OnProgressCallback& onProgressCallback,
 | 
			
		||||
        const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        if (_readBuffer.empty())
 | 
			
		||||
        {
 | 
			
		||||
            _readBuffer.resize(kChunkSize);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::vector<uint8_t> output;
 | 
			
		||||
        while (output.size() != length)
 | 
			
		||||
        {
 | 
			
		||||
            if (isCancellationRequested && isCancellationRequested())
 | 
			
		||||
            {
 | 
			
		||||
                return std::make_pair(false, std::string());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            size_t size = std::min(kChunkSize, length - output.size());
 | 
			
		||||
            ssize_t ret = recv((char*)&_readBuffer[0], size);
 | 
			
		||||
 | 
			
		||||
            if (ret <= 0 && (getErrno() != EWOULDBLOCK &&
 | 
			
		||||
                             getErrno() != EAGAIN))
 | 
			
		||||
            {
 | 
			
		||||
                // Error
 | 
			
		||||
                return std::make_pair(false, std::string());
 | 
			
		||||
            }
 | 
			
		||||
            else if (ret > 0)
 | 
			
		||||
            {
 | 
			
		||||
                output.insert(output.end(),
 | 
			
		||||
                              _readBuffer.begin(),
 | 
			
		||||
                              _readBuffer.begin() + ret);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (onProgressCallback) onProgressCallback((int) output.size(), (int) length);
 | 
			
		||||
 | 
			
		||||
            // Wait with a 1ms timeout until the socket is ready to read.
 | 
			
		||||
            // This way we are not busy looping
 | 
			
		||||
            if (isReadyToRead(1) == PollResultType::Error)
 | 
			
		||||
            {
 | 
			
		||||
                return std::make_pair(false, std::string());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::make_pair(true, std::string(output.begin(),
 | 
			
		||||
                                                output.end()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,22 +10,29 @@
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <mutex>
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#include <BaseTsd.h>
 | 
			
		||||
typedef SSIZE_T ssize_t;
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "IXEventFd.h"
 | 
			
		||||
#include "IXCancellationRequest.h"
 | 
			
		||||
#include "IXProgressCallback.h"
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    enum PollResultType
 | 
			
		||||
    class SelectInterrupt;
 | 
			
		||||
 | 
			
		||||
    enum class PollResultType
 | 
			
		||||
    {
 | 
			
		||||
        PollResultType_ReadyForRead = 0,
 | 
			
		||||
        PollResultType_Timeout = 1,
 | 
			
		||||
        PollResultType_Error = 2
 | 
			
		||||
        ReadyForRead = 0,
 | 
			
		||||
        ReadyForWrite = 1,
 | 
			
		||||
        Timeout = 2,
 | 
			
		||||
        Error = 3,
 | 
			
		||||
        SendRequest = 4,
 | 
			
		||||
        CloseRequest = 5
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class Socket {
 | 
			
		||||
@@ -34,15 +41,20 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        Socket(int fd = -1);
 | 
			
		||||
        virtual ~Socket();
 | 
			
		||||
        bool init(std::string& errorMsg);
 | 
			
		||||
 | 
			
		||||
        void configure();
 | 
			
		||||
 | 
			
		||||
        virtual void poll(const OnPollCallback& onPollCallback,
 | 
			
		||||
                          int timeoutSecs = kDefaultPollTimeout);
 | 
			
		||||
        virtual void wakeUpFromPoll();
 | 
			
		||||
        // Functions to check whether there is activity on the socket
 | 
			
		||||
        void poll(const OnPollCallback& onPollCallback,
 | 
			
		||||
                  int timeoutSecs = kDefaultPollTimeout);
 | 
			
		||||
        bool wakeUpFromPoll(uint8_t wakeUpCode);
 | 
			
		||||
 | 
			
		||||
        PollResultType isReadyToWrite(int timeoutMs);
 | 
			
		||||
        PollResultType isReadyToRead(int timeoutMs);
 | 
			
		||||
 | 
			
		||||
        // Virtual methods
 | 
			
		||||
        virtual bool connect(const std::string& url, 
 | 
			
		||||
        virtual bool connect(const std::string& url,
 | 
			
		||||
                             int port,
 | 
			
		||||
                             std::string& errMsg,
 | 
			
		||||
                             const CancellationRequest& isCancellationRequested);
 | 
			
		||||
@@ -58,21 +70,36 @@ namespace ix
 | 
			
		||||
                      const CancellationRequest& isCancellationRequested);
 | 
			
		||||
        bool writeBytes(const std::string& str,
 | 
			
		||||
                        const CancellationRequest& isCancellationRequested);
 | 
			
		||||
        std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
 | 
			
		||||
 | 
			
		||||
        std::pair<bool, std::string> readLine(
 | 
			
		||||
            const CancellationRequest& isCancellationRequested);
 | 
			
		||||
        std::pair<bool, std::string> readBytes(
 | 
			
		||||
            size_t length,
 | 
			
		||||
            const OnProgressCallback& onProgressCallback,
 | 
			
		||||
            const CancellationRequest& isCancellationRequested);
 | 
			
		||||
 | 
			
		||||
        static int getErrno();
 | 
			
		||||
        static bool init(); // Required on Windows to initialize WinSocket
 | 
			
		||||
        static void cleanup(); // Required on Windows to cleanup WinSocket
 | 
			
		||||
 | 
			
		||||
        // Used as special codes for pipe communication
 | 
			
		||||
        static const uint64_t kSendRequest;
 | 
			
		||||
        static const uint64_t kCloseRequest;
 | 
			
		||||
 | 
			
		||||
    protected:
 | 
			
		||||
        void closeSocket(int fd);
 | 
			
		||||
 | 
			
		||||
        std::atomic<int> _sockfd;
 | 
			
		||||
        std::mutex _socketMutex;
 | 
			
		||||
        EventFd _eventfd;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        PollResultType select(bool readyToRead, int timeoutMs);
 | 
			
		||||
 | 
			
		||||
        static const int kDefaultPollTimeout;
 | 
			
		||||
        static const int kDefaultPollNoTimeout;
 | 
			
		||||
 | 
			
		||||
        // Buffer for reading from our socket. That buffer is never resized.
 | 
			
		||||
        std::vector<uint8_t> _readBuffer;
 | 
			
		||||
        static constexpr size_t kChunkSize = 1 << 15;
 | 
			
		||||
 | 
			
		||||
        std::shared_ptr<SelectInterrupt> _selectInterrupt;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ OSStatus read_from_socket(SSLConnectionRef connection, void *data, size_t *len)
 | 
			
		||||
        else
 | 
			
		||||
            return noErr;
 | 
			
		||||
    }
 | 
			
		||||
    else if (0 == status) 
 | 
			
		||||
    else if (0 == status)
 | 
			
		||||
    {
 | 
			
		||||
        *len = 0;
 | 
			
		||||
        return errSSLClosedGraceful;
 | 
			
		||||
@@ -102,7 +102,7 @@ OSStatus write_to_socket(SSLConnectionRef connection, const void *data, size_t *
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        *len = 0;
 | 
			
		||||
        if (EAGAIN == errno) 
 | 
			
		||||
        if (EAGAIN == errno)
 | 
			
		||||
        {
 | 
			
		||||
            return errSSLWouldBlock;
 | 
			
		||||
        }
 | 
			
		||||
@@ -141,7 +141,7 @@ std::string getSSLErrorDescription(OSStatus status)
 | 
			
		||||
 | 
			
		||||
} // anonymous namespace
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    SocketAppleSSL::SocketAppleSSL(int fd) : Socket(fd),
 | 
			
		||||
        _sslContext(nullptr)
 | 
			
		||||
@@ -176,11 +176,11 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
            do {
 | 
			
		||||
                status = SSLHandshake(_sslContext);
 | 
			
		||||
            } while (errSSLWouldBlock == status || 
 | 
			
		||||
            } while (errSSLWouldBlock == status ||
 | 
			
		||||
                     errSSLServerAuthCompleted == status);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (noErr != status) 
 | 
			
		||||
        if (noErr != status)
 | 
			
		||||
        {
 | 
			
		||||
            errMsg = getSSLErrorDescription(status);
 | 
			
		||||
            close();
 | 
			
		||||
@@ -230,7 +230,7 @@ namespace ix
 | 
			
		||||
    ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
 | 
			
		||||
    {
 | 
			
		||||
        OSStatus status = errSSLWouldBlock;
 | 
			
		||||
        while (errSSLWouldBlock == status) 
 | 
			
		||||
        while (errSSLWouldBlock == status)
 | 
			
		||||
        {
 | 
			
		||||
            size_t processed = 0;
 | 
			
		||||
            std::lock_guard<std::mutex> lock(_mutex);
 | 
			
		||||
@@ -239,7 +239,7 @@ namespace ix
 | 
			
		||||
            if (processed > 0)
 | 
			
		||||
                return (ssize_t) processed;
 | 
			
		||||
 | 
			
		||||
            // The connection was reset, inform the caller that this 
 | 
			
		||||
            // The connection was reset, inform the caller that this
 | 
			
		||||
            // Socket should close
 | 
			
		||||
            if (status == errSSLClosedGraceful ||
 | 
			
		||||
                status == errSSLClosedNoNotify ||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,15 +14,15 @@
 | 
			
		||||
 | 
			
		||||
#include <mutex>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketAppleSSL : public Socket 
 | 
			
		||||
    class SocketAppleSSL : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketAppleSSL(int fd = -1);
 | 
			
		||||
        ~SocketAppleSSL();
 | 
			
		||||
 | 
			
		||||
        virtual bool connect(const std::string& host, 
 | 
			
		||||
        virtual bool connect(const std::string& host,
 | 
			
		||||
                             int port,
 | 
			
		||||
                             std::string& errMsg,
 | 
			
		||||
                             const CancellationRequest& isCancellationRequested) final;
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ namespace
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    //
 | 
			
		||||
    // This function can be cancelled every 50 ms
 | 
			
		||||
@@ -42,7 +42,7 @@ namespace ix
 | 
			
		||||
                                        const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        errMsg = "no error";
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        int fd = socket(address->ai_family,
 | 
			
		||||
                        address->ai_socktype,
 | 
			
		||||
                        address->ai_protocol);
 | 
			
		||||
@@ -66,13 +66,13 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            if (isCancellationRequested()) // Must handle timeout as well
 | 
			
		||||
            if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well
 | 
			
		||||
            {
 | 
			
		||||
                closeSocket(fd);
 | 
			
		||||
                errMsg = "Cancelled";
 | 
			
		||||
                return -1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            // Use select to check the status of the new connection
 | 
			
		||||
            struct timeval timeout;
 | 
			
		||||
            timeout.tv_sec = 0;
 | 
			
		||||
@@ -179,7 +179,7 @@ namespace ix
 | 
			
		||||
        // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect
 | 
			
		||||
#ifdef SO_NOSIGPIPE
 | 
			
		||||
        int value = 1;
 | 
			
		||||
        setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, 
 | 
			
		||||
        setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
 | 
			
		||||
                   (void *)&value, sizeof(value));
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
 | 
			
		||||
struct addrinfo;
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketConnect {
 | 
			
		||||
    public:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								ixwebsocket/IXSocketFactory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,64 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSocketFactory.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXSocketFactory.h"
 | 
			
		||||
 | 
			
		||||
#if defined(__APPLE__) or defined(__linux__)
 | 
			
		||||
# ifdef __APPLE__
 | 
			
		||||
#  include <ixwebsocket/IXSocketAppleSSL.h>
 | 
			
		||||
# else
 | 
			
		||||
#  include <ixwebsocket/IXSocketOpenSSL.h>
 | 
			
		||||
# endif
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    std::shared_ptr<Socket> createSocket(bool tls,
 | 
			
		||||
                                         std::string& errorMsg)
 | 
			
		||||
    {
 | 
			
		||||
        errorMsg.clear();
 | 
			
		||||
        std::shared_ptr<Socket> socket;
 | 
			
		||||
 | 
			
		||||
        if (!tls)
 | 
			
		||||
        {
 | 
			
		||||
            socket = std::make_shared<Socket>();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
#ifdef IXWEBSOCKET_USE_TLS
 | 
			
		||||
# ifdef __APPLE__
 | 
			
		||||
            socket = std::make_shared<SocketAppleSSL>();
 | 
			
		||||
# else
 | 
			
		||||
            socket = std::make_shared<SocketOpenSSL>();
 | 
			
		||||
# endif
 | 
			
		||||
#else
 | 
			
		||||
            errorMsg = "TLS support is not enabled on this platform.";
 | 
			
		||||
            return nullptr;
 | 
			
		||||
#endif
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!socket->init(errorMsg))
 | 
			
		||||
        {
 | 
			
		||||
            socket.reset();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return socket;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<Socket> createSocket(int fd,
 | 
			
		||||
                                         std::string& errorMsg)
 | 
			
		||||
    {
 | 
			
		||||
        errorMsg.clear();
 | 
			
		||||
 | 
			
		||||
        std::shared_ptr<Socket> socket = std::make_shared<Socket>(fd);
 | 
			
		||||
        if (!socket->init(errorMsg))
 | 
			
		||||
        {
 | 
			
		||||
            socket.reset();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return socket;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								ixwebsocket/IXSocketFactory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,20 @@
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *  IXSocketFactory.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class Socket;
 | 
			
		||||
    std::shared_ptr<Socket> createSocket(bool tls,
 | 
			
		||||
                                         std::string& errorMsg);
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<Socket> createSocket(int fd,
 | 
			
		||||
                                         std::string& errorMsg);
 | 
			
		||||
}
 | 
			
		||||
@@ -18,12 +18,13 @@
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#define socketerrno errno
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
 | 
			
		||||
    std::once_flag SocketOpenSSL::_openSSLInitFlag;
 | 
			
		||||
 | 
			
		||||
    SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
 | 
			
		||||
        _ssl_connection(nullptr), 
 | 
			
		||||
        _ssl_connection(nullptr),
 | 
			
		||||
        _ssl_context(nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
 | 
			
		||||
@@ -80,7 +81,7 @@ namespace ix
 | 
			
		||||
                return "OpenSSL failed - underlying BIO reported an I/O error";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (err == SSL_ERROR_SSL) 
 | 
			
		||||
        else if (err == SSL_ERROR_SSL)
 | 
			
		||||
        {
 | 
			
		||||
            e = ERR_get_error();
 | 
			
		||||
            std::string errMsg("OpenSSL failed - ");
 | 
			
		||||
@@ -149,7 +150,7 @@ namespace ix
 | 
			
		||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
 | 
			
		||||
        // Check server name
 | 
			
		||||
        bool hostname_verifies_ok = false;
 | 
			
		||||
        STACK_OF(GENERAL_NAME) *san_names = 
 | 
			
		||||
        STACK_OF(GENERAL_NAME) *san_names =
 | 
			
		||||
            (STACK_OF(GENERAL_NAME)*) X509_get_ext_d2i((X509 *)server_cert,
 | 
			
		||||
                                                       NID_subject_alt_name, NULL, NULL);
 | 
			
		||||
        if (san_names)
 | 
			
		||||
@@ -160,8 +161,8 @@ namespace ix
 | 
			
		||||
                if (sk_name->type == GEN_DNS)
 | 
			
		||||
                {
 | 
			
		||||
                    char *name = (char *)ASN1_STRING_data(sk_name->d.dNSName);
 | 
			
		||||
                    if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) && 
 | 
			
		||||
                        checkHost(hostname, name)) 
 | 
			
		||||
                    if ((size_t)ASN1_STRING_length(sk_name->d.dNSName) == strlen(name) &&
 | 
			
		||||
                        checkHost(hostname, name))
 | 
			
		||||
                    {
 | 
			
		||||
                        hostname_verifies_ok = true;
 | 
			
		||||
                        break;
 | 
			
		||||
@@ -185,8 +186,8 @@ namespace ix
 | 
			
		||||
                    ASN1_STRING *cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
 | 
			
		||||
                    char *cn = (char *)ASN1_STRING_data(cn_asn1);
 | 
			
		||||
 | 
			
		||||
                    if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) && 
 | 
			
		||||
                       checkHost(hostname, cn)) 
 | 
			
		||||
                    if ((size_t)ASN1_STRING_length(cn_asn1) == strlen(cn) &&
 | 
			
		||||
                       checkHost(hostname, cn))
 | 
			
		||||
                    {
 | 
			
		||||
                        hostname_verifies_ok = true;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -205,7 +206,7 @@ namespace ix
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg) 
 | 
			
		||||
    bool SocketOpenSSL::openSSLHandshake(const std::string& host, std::string& errMsg)
 | 
			
		||||
    {
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,15 +17,15 @@
 | 
			
		||||
 | 
			
		||||
#include <mutex>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketOpenSSL : public Socket 
 | 
			
		||||
    class SocketOpenSSL : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketOpenSSL(int fd = -1);
 | 
			
		||||
        ~SocketOpenSSL();
 | 
			
		||||
 | 
			
		||||
        virtual bool connect(const std::string& host, 
 | 
			
		||||
        virtual bool connect(const std::string& host,
 | 
			
		||||
                             int port,
 | 
			
		||||
                             std::string& errMsg,
 | 
			
		||||
                             const CancellationRequest& isCancellationRequested) final;
 | 
			
		||||
@@ -50,7 +50,7 @@ namespace ix
 | 
			
		||||
        const SSL_METHOD* _ssl_method;
 | 
			
		||||
        mutable std::mutex _mutex;  // OpenSSL routines are not thread-safe
 | 
			
		||||
 | 
			
		||||
        std::once_flag _openSSLInitFlag;
 | 
			
		||||
        static std::once_flag _openSSLInitFlag;
 | 
			
		||||
        static std::atomic<bool> _openSSLInitializationSuccessful;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@
 | 
			
		||||
// link with ntdsapi.lib for DsMakeSpn function
 | 
			
		||||
#pragma comment(lib, "ntdsapi.lib")
 | 
			
		||||
 | 
			
		||||
// The following function assumes that Winsock 
 | 
			
		||||
// The following function assumes that Winsock
 | 
			
		||||
// has already been initialized
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +59,7 @@
 | 
			
		||||
# error("This file should only be built on Windows")
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    SocketSChannel::SocketSChannel()
 | 
			
		||||
    {
 | 
			
		||||
@@ -68,7 +68,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    SocketSChannel::~SocketSChannel()
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool SocketSChannel::connect(const std::string& host,
 | 
			
		||||
@@ -78,7 +78,7 @@ namespace ix
 | 
			
		||||
        return Socket::connect(host, port, errMsg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    void SocketSChannel::secureSocket()
 | 
			
		||||
    {
 | 
			
		||||
        // there will be a lot to do here ...
 | 
			
		||||
 
 | 
			
		||||
@@ -8,15 +8,15 @@
 | 
			
		||||
 | 
			
		||||
#include "IXSocket.h"
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketSChannel : public Socket 
 | 
			
		||||
    class SocketSChannel : public Socket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        SocketSChannel();
 | 
			
		||||
        ~SocketSChannel();
 | 
			
		||||
 | 
			
		||||
        virtual bool connect(const std::string& host, 
 | 
			
		||||
        virtual bool connect(const std::string& host,
 | 
			
		||||
                             int port,
 | 
			
		||||
                             std::string& errMsg) final;
 | 
			
		||||
        virtual void close() final;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
#include <future>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    const int SocketServer::kDefaultPort(8080);
 | 
			
		||||
    const std::string SocketServer::kDefaultHost("127.0.0.1");
 | 
			
		||||
@@ -29,7 +29,8 @@ namespace ix
 | 
			
		||||
        _host(host),
 | 
			
		||||
        _backlog(backlog),
 | 
			
		||||
        _maxConnections(maxConnections),
 | 
			
		||||
        _stop(false)
 | 
			
		||||
        _stop(false),
 | 
			
		||||
        _connectionStateFactory(&ConnectionState::createConnectionState)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
@@ -83,7 +84,7 @@ namespace ix
 | 
			
		||||
        server.sin_family = AF_INET;
 | 
			
		||||
        server.sin_port   = htons(_port);
 | 
			
		||||
 | 
			
		||||
        // Using INADDR_ANY trigger a pop-up box as binding to any address is detected 
 | 
			
		||||
        // Using INADDR_ANY trigger a pop-up box as binding to any address is detected
 | 
			
		||||
        // by the osx firewall. We need to codesign the binary with a self-signed cert
 | 
			
		||||
        // to allow that, but this is a bit of a pain. (this is what node or python would do).
 | 
			
		||||
        //
 | 
			
		||||
@@ -145,6 +146,12 @@ namespace ix
 | 
			
		||||
        ::close(_serverFd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SocketServer::setConnectionStateFactory(
 | 
			
		||||
        const ConnectionStateFactory& connectionStateFactory)
 | 
			
		||||
    {
 | 
			
		||||
        _connectionStateFactory = connectionStateFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SocketServer::run()
 | 
			
		||||
    {
 | 
			
		||||
        // Set the socket to non blocking mode, so that accept calls are not blocking
 | 
			
		||||
@@ -214,14 +221,21 @@ namespace ix
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::shared_ptr<ConnectionState> connectionState;
 | 
			
		||||
            if (_connectionStateFactory)
 | 
			
		||||
            {
 | 
			
		||||
                connectionState = _connectionStateFactory();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Launch the handleConnection work asynchronously in its own thread.
 | 
			
		||||
            //
 | 
			
		||||
            // the destructor of a future returned by std::async blocks, 
 | 
			
		||||
            // the destructor of a future returned by std::async blocks,
 | 
			
		||||
            // so we need to declare it outside of this loop
 | 
			
		||||
            f = std::async(std::launch::async,
 | 
			
		||||
                           &SocketServer::handleConnection,
 | 
			
		||||
                           this,
 | 
			
		||||
                           clientFd);
 | 
			
		||||
                           clientFd,
 | 
			
		||||
                           connectionState);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "IXConnectionState.h"
 | 
			
		||||
 | 
			
		||||
#include <utility> // pair
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <set>
 | 
			
		||||
@@ -16,10 +18,12 @@
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <condition_variable>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class SocketServer {
 | 
			
		||||
    public:
 | 
			
		||||
        using ConnectionStateFactory = std::function<std::shared_ptr<ConnectionState>()>;
 | 
			
		||||
 | 
			
		||||
        SocketServer(int port = SocketServer::kDefaultPort,
 | 
			
		||||
                     const std::string& host = SocketServer::kDefaultHost,
 | 
			
		||||
                     int backlog = SocketServer::kDefaultTcpBacklog,
 | 
			
		||||
@@ -27,6 +31,8 @@ namespace ix
 | 
			
		||||
        virtual ~SocketServer();
 | 
			
		||||
        virtual void stop();
 | 
			
		||||
 | 
			
		||||
        void setConnectionStateFactory(const ConnectionStateFactory& connectionStateFactory);
 | 
			
		||||
 | 
			
		||||
        const static int kDefaultPort;
 | 
			
		||||
        const static std::string kDefaultHost;
 | 
			
		||||
        const static int kDefaultTcpBacklog;
 | 
			
		||||
@@ -60,9 +66,13 @@ namespace ix
 | 
			
		||||
        std::condition_variable _conditionVariable;
 | 
			
		||||
        std::mutex _conditionVariableMutex;
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        ConnectionStateFactory _connectionStateFactory;
 | 
			
		||||
 | 
			
		||||
        // Methods
 | 
			
		||||
        void run();
 | 
			
		||||
        virtual void handleConnection(int fd) = 0;
 | 
			
		||||
        virtual void handleConnection(int fd,
 | 
			
		||||
                                      std::shared_ptr<ConnectionState> connectionState) = 0;
 | 
			
		||||
        virtual size_t getConnectedClientsCount() = 0;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								ixwebsocket/IXUrlParser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,104 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXUrlParser.cpp
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXUrlParser.h"
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    //
 | 
			
		||||
    // The only difference between those 2 regex is the protocol
 | 
			
		||||
    //
 | 
			
		||||
    std::regex UrlParser::_httpRegex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
 | 
			
		||||
    std::regex UrlParser::_webSocketRegex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
 | 
			
		||||
 | 
			
		||||
    bool UrlParser::parse(const std::string& url,
 | 
			
		||||
                          std::string& protocol,
 | 
			
		||||
                          std::string& host,
 | 
			
		||||
                          std::string& path,
 | 
			
		||||
                          std::string& query,
 | 
			
		||||
                          int& port,
 | 
			
		||||
                          bool websocket)
 | 
			
		||||
    {
 | 
			
		||||
        std::cmatch what;
 | 
			
		||||
        if (!regex_match(url.c_str(), what,
 | 
			
		||||
                         websocket ? _webSocketRegex : _httpRegex))
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string portStr;
 | 
			
		||||
 | 
			
		||||
        protocol = std::string(what[1].first, what[1].second);
 | 
			
		||||
        host     = std::string(what[2].first, what[2].second);
 | 
			
		||||
        portStr  = std::string(what[3].first, what[3].second);
 | 
			
		||||
        path     = std::string(what[4].first, what[4].second);
 | 
			
		||||
        query    = std::string(what[5].first, what[5].second);
 | 
			
		||||
 | 
			
		||||
        if (portStr.empty())
 | 
			
		||||
        {
 | 
			
		||||
            if (protocol == "ws" || protocol == "http")
 | 
			
		||||
            {
 | 
			
		||||
                port = 80;
 | 
			
		||||
            }
 | 
			
		||||
            else if (protocol == "wss" || protocol == "https")
 | 
			
		||||
            {
 | 
			
		||||
                port = 443;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // Invalid protocol. Should be caught by regex check
 | 
			
		||||
                // but this missing branch trigger cpplint linter.
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << portStr;
 | 
			
		||||
            ss >> port;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (path.empty())
 | 
			
		||||
        {
 | 
			
		||||
            path = "/";
 | 
			
		||||
        }
 | 
			
		||||
        else if (path[0] != '/')
 | 
			
		||||
        {
 | 
			
		||||
            path = '/' + path;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!query.empty())
 | 
			
		||||
        {
 | 
			
		||||
            path += "?";
 | 
			
		||||
            path += query;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void UrlParser::printUrl(const std::string& url, bool websocket)
 | 
			
		||||
    {
 | 
			
		||||
        std::string protocol, host, path, query;
 | 
			
		||||
        int port {0};
 | 
			
		||||
 | 
			
		||||
        if (!parse(url, protocol, host, path, query, port, websocket))
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::cout << "[" << url << "]" << std::endl;
 | 
			
		||||
        std::cout << protocol << std::endl;
 | 
			
		||||
        std::cout << host << std::endl;
 | 
			
		||||
        std::cout << port << std::endl;
 | 
			
		||||
        std::cout << path << std::endl;
 | 
			
		||||
        std::cout << query << std::endl;
 | 
			
		||||
        std::cout << "-------------------------------" << std::endl;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								ixwebsocket/IXUrlParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXUrlParser.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <regex>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class UrlParser
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        static bool parse(const std::string& url,
 | 
			
		||||
                          std::string& protocol,
 | 
			
		||||
                          std::string& host,
 | 
			
		||||
                          std::string& path,
 | 
			
		||||
                          std::string& query,
 | 
			
		||||
                          int& port,
 | 
			
		||||
                          bool websocket);
 | 
			
		||||
 | 
			
		||||
        static void printUrl(const std::string& url, bool websocket);
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        static std::regex _httpRegex;
 | 
			
		||||
        static std::regex _webSocketRegex;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -50,7 +50,7 @@ namespace ix
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocket::~WebSocket() 
 | 
			
		||||
    WebSocket::~WebSocket()
 | 
			
		||||
    {
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
@@ -79,10 +79,10 @@ namespace ix
 | 
			
		||||
        return _perMessageDeflateOptions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
 | 
			
		||||
    void WebSocket::setHeartBeatPeriod(int heartBeatPeriod)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_configMutex);
 | 
			
		||||
        _heartBeatPeriod = hearBeatPeriod;
 | 
			
		||||
        _heartBeatPeriod = heartBeatPeriod;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int WebSocket::getHeartBeatPeriod() const
 | 
			
		||||
@@ -135,7 +135,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _onMessageCallback(WebSocket_MessageType_Open, "", 0,
 | 
			
		||||
                           WebSocketErrorInfo(), 
 | 
			
		||||
                           WebSocketErrorInfo(),
 | 
			
		||||
                           WebSocketOpenInfo(status.uri, status.headers),
 | 
			
		||||
                           WebSocketCloseInfo());
 | 
			
		||||
        return status;
 | 
			
		||||
@@ -155,7 +155,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _onMessageCallback(WebSocket_MessageType_Open, "", 0,
 | 
			
		||||
                           WebSocketErrorInfo(), 
 | 
			
		||||
                           WebSocketErrorInfo(),
 | 
			
		||||
                           WebSocketOpenInfo(status.uri, status.headers),
 | 
			
		||||
                           WebSocketCloseInfo());
 | 
			
		||||
        return status;
 | 
			
		||||
@@ -184,7 +184,7 @@ namespace ix
 | 
			
		||||
        using millis = std::chrono::duration<double, std::milli>;
 | 
			
		||||
        millis duration;
 | 
			
		||||
 | 
			
		||||
        while (true) 
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (isConnected() || isClosing() || _stop || !_automaticReconnection)
 | 
			
		||||
            {
 | 
			
		||||
@@ -214,7 +214,7 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        setThreadName(_url);
 | 
			
		||||
 | 
			
		||||
        while (true) 
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
 | 
			
		||||
@@ -223,7 +223,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
 | 
			
		||||
            // 2. Poll to see if there's any new data available 
 | 
			
		||||
            // 2. Poll to see if there's any new data available
 | 
			
		||||
            _ws.poll();
 | 
			
		||||
 | 
			
		||||
            if (_stop) return;
 | 
			
		||||
@@ -252,6 +252,11 @@ namespace ix
 | 
			
		||||
                        {
 | 
			
		||||
                            webSocketMessageType = WebSocket_MessageType_Pong;
 | 
			
		||||
                        } break;
 | 
			
		||||
 | 
			
		||||
                        case WebSocketTransport::FRAGMENT:
 | 
			
		||||
                        {
 | 
			
		||||
                            webSocketMessageType = WebSocket_MessageType_Fragment;
 | 
			
		||||
                        } break;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    WebSocketErrorInfo webSocketErrorInfo;
 | 
			
		||||
@@ -273,7 +278,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    void WebSocket::setOnMessageCallback(const OnMessageCallback& callback)
 | 
			
		||||
    {
 | 
			
		||||
        _onMessageCallback = callback; 
 | 
			
		||||
        _onMessageCallback = callback;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocket::setTrafficTrackerCallback(const OnTrafficTrackerCallback& callback)
 | 
			
		||||
@@ -294,9 +299,16 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketSendInfo WebSocket::send(const std::string& text)
 | 
			
		||||
    WebSocketSendInfo WebSocket::send(const std::string& text,
 | 
			
		||||
                                      const OnProgressCallback& onProgressCallback)
 | 
			
		||||
    {
 | 
			
		||||
        return sendMessage(text, false);
 | 
			
		||||
        return sendMessage(text, SendMessageKind::Binary, onProgressCallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketSendInfo WebSocket::sendText(const std::string& text,
 | 
			
		||||
                                          const OnProgressCallback& onProgressCallback)
 | 
			
		||||
    {
 | 
			
		||||
        return sendMessage(text, SendMessageKind::Text, onProgressCallback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketSendInfo WebSocket::ping(const std::string& text)
 | 
			
		||||
@@ -305,10 +317,12 @@ namespace ix
 | 
			
		||||
        constexpr size_t pingMaxPayloadSize = 125;
 | 
			
		||||
        if (text.size() > pingMaxPayloadSize) return WebSocketSendInfo(false);
 | 
			
		||||
 | 
			
		||||
        return sendMessage(text, true);
 | 
			
		||||
        return sendMessage(text, SendMessageKind::Ping);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketSendInfo WebSocket::sendMessage(const std::string& text, bool ping)
 | 
			
		||||
    WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
 | 
			
		||||
                                             SendMessageKind sendMessageKind,
 | 
			
		||||
                                             const OnProgressCallback& onProgressCallback)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isConnected()) return WebSocketSendInfo(false);
 | 
			
		||||
 | 
			
		||||
@@ -324,13 +338,22 @@ namespace ix
 | 
			
		||||
        std::lock_guard<std::mutex> lock(_writeMutex);
 | 
			
		||||
        WebSocketSendInfo webSocketSendInfo;
 | 
			
		||||
 | 
			
		||||
        if (ping)
 | 
			
		||||
        switch (sendMessageKind)
 | 
			
		||||
        {
 | 
			
		||||
            webSocketSendInfo = _ws.sendPing(text);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            webSocketSendInfo = _ws.sendBinary(text);
 | 
			
		||||
            case SendMessageKind::Text:
 | 
			
		||||
            {
 | 
			
		||||
                webSocketSendInfo = _ws.sendText(text, onProgressCallback);
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SendMessageKind::Binary:
 | 
			
		||||
            {
 | 
			
		||||
                webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
 | 
			
		||||
            } break;
 | 
			
		||||
 | 
			
		||||
            case SendMessageKind::Ping:
 | 
			
		||||
            {
 | 
			
		||||
                webSocketSendInfo = _ws.sendPing(text);
 | 
			
		||||
            } break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
 | 
			
		||||
@@ -340,7 +363,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    ReadyState WebSocket::getReadyState() const
 | 
			
		||||
    {
 | 
			
		||||
        switch (_ws.getReadyState()) 
 | 
			
		||||
        switch (_ws.getReadyState())
 | 
			
		||||
        {
 | 
			
		||||
            case ix::WebSocketTransport::OPEN: return WebSocket_ReadyState_Open;
 | 
			
		||||
            case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
 | 
			
		||||
@@ -371,4 +394,9 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        _automaticReconnection = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t WebSocket::bufferedAmount() const
 | 
			
		||||
    {
 | 
			
		||||
        return _ws.bufferedAmount();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,11 +19,12 @@
 | 
			
		||||
#include "IXWebSocketSendInfo.h"
 | 
			
		||||
#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
			
		||||
#include "IXWebSocketHttpHeaders.h"
 | 
			
		||||
#include "IXProgressCallback.h"
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants
 | 
			
		||||
    enum ReadyState 
 | 
			
		||||
    enum ReadyState
 | 
			
		||||
    {
 | 
			
		||||
        WebSocket_ReadyState_Connecting = 0,
 | 
			
		||||
        WebSocket_ReadyState_Open = 1,
 | 
			
		||||
@@ -38,7 +39,8 @@ namespace ix
 | 
			
		||||
        WebSocket_MessageType_Close = 2,
 | 
			
		||||
        WebSocket_MessageType_Error = 3,
 | 
			
		||||
        WebSocket_MessageType_Ping = 4,
 | 
			
		||||
        WebSocket_MessageType_Pong = 5
 | 
			
		||||
        WebSocket_MessageType_Pong = 5,
 | 
			
		||||
        WebSocket_MessageType_Fragment = 6
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct WebSocketOpenInfo
 | 
			
		||||
@@ -78,7 +80,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    using OnTrafficTrackerCallback = std::function<void(size_t size, bool incoming)>;
 | 
			
		||||
 | 
			
		||||
    class WebSocket 
 | 
			
		||||
    class WebSocket
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        WebSocket();
 | 
			
		||||
@@ -87,7 +89,7 @@ namespace ix
 | 
			
		||||
        void setUrl(const std::string& url);
 | 
			
		||||
        void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
 | 
			
		||||
        void setHandshakeTimeout(int handshakeTimeoutSecs);
 | 
			
		||||
        void setHeartBeatPeriod(int hearBeatPeriod);
 | 
			
		||||
        void setHeartBeatPeriod(int heartBeatPeriod);
 | 
			
		||||
 | 
			
		||||
        // Run asynchronously, by calling start and stop.
 | 
			
		||||
        void start();
 | 
			
		||||
@@ -97,7 +99,10 @@ namespace ix
 | 
			
		||||
        WebSocketInitResult connect(int timeoutSecs);
 | 
			
		||||
        void run();
 | 
			
		||||
 | 
			
		||||
        WebSocketSendInfo send(const std::string& text);
 | 
			
		||||
        WebSocketSendInfo send(const std::string& text,
 | 
			
		||||
                               const OnProgressCallback& onProgressCallback = nullptr);
 | 
			
		||||
        WebSocketSendInfo sendText(const std::string& text,
 | 
			
		||||
                                   const OnProgressCallback& onProgressCallback = nullptr);
 | 
			
		||||
        WebSocketSendInfo ping(const std::string& text);
 | 
			
		||||
        void close();
 | 
			
		||||
 | 
			
		||||
@@ -109,13 +114,16 @@ namespace ix
 | 
			
		||||
        const std::string& getUrl() const;
 | 
			
		||||
        const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
 | 
			
		||||
        int getHeartBeatPeriod() const;
 | 
			
		||||
        size_t bufferedAmount() const;
 | 
			
		||||
 | 
			
		||||
        void enableAutomaticReconnection();
 | 
			
		||||
        void disableAutomaticReconnection();
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
        WebSocketSendInfo sendMessage(const std::string& text, bool ping);
 | 
			
		||||
        WebSocketSendInfo sendMessage(const std::string& text,
 | 
			
		||||
                                      SendMessageKind sendMessageKind,
 | 
			
		||||
                                      const OnProgressCallback& callback = nullptr);
 | 
			
		||||
 | 
			
		||||
        bool isConnected() const;
 | 
			
		||||
        bool isClosing() const;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    struct WebSocketErrorInfo
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
#include "IXWebSocketHandshake.h"
 | 
			
		||||
#include "IXSocketConnect.h"
 | 
			
		||||
#include "IXUrlParser.h"
 | 
			
		||||
 | 
			
		||||
#include "libwshandshake.hpp"
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +17,7 @@
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    WebSocketHandshake::WebSocketHandshake(std::atomic<bool>& requestInitCancellation,
 | 
			
		||||
                                           std::shared_ptr<Socket> socket,
 | 
			
		||||
@@ -32,90 +33,6 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool WebSocketHandshake::parseUrl(const std::string& url,
 | 
			
		||||
                                      std::string& protocol,
 | 
			
		||||
                                      std::string& host,
 | 
			
		||||
                                      std::string& path,
 | 
			
		||||
                                      std::string& query,
 | 
			
		||||
                                      int& port)
 | 
			
		||||
    {
 | 
			
		||||
        std::regex ex("(ws|wss)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)");
 | 
			
		||||
        std::cmatch what;
 | 
			
		||||
        if (!regex_match(url.c_str(), what, ex))
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string portStr;
 | 
			
		||||
 | 
			
		||||
        protocol = std::string(what[1].first, what[1].second);
 | 
			
		||||
        host     = std::string(what[2].first, what[2].second);
 | 
			
		||||
        portStr  = std::string(what[3].first, what[3].second);
 | 
			
		||||
        path     = std::string(what[4].first, what[4].second);
 | 
			
		||||
        query    = std::string(what[5].first, what[5].second);
 | 
			
		||||
 | 
			
		||||
        if (portStr.empty())
 | 
			
		||||
        {
 | 
			
		||||
            if (protocol == "ws")
 | 
			
		||||
            {
 | 
			
		||||
                port = 80;
 | 
			
		||||
            }
 | 
			
		||||
            else if (protocol == "wss")
 | 
			
		||||
            {
 | 
			
		||||
                port = 443;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // Invalid protocol. Should be caught by regex check
 | 
			
		||||
                // but this missing branch trigger cpplint linter.
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            std::stringstream ss;
 | 
			
		||||
            ss << portStr;
 | 
			
		||||
            ss >> port;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (path.empty())
 | 
			
		||||
        {
 | 
			
		||||
            path = "/";
 | 
			
		||||
        }
 | 
			
		||||
        else if (path[0] != '/')
 | 
			
		||||
        {
 | 
			
		||||
            path = '/' + path;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!query.empty())
 | 
			
		||||
        {
 | 
			
		||||
            path += "?";
 | 
			
		||||
            path += query;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void WebSocketHandshake::printUrl(const std::string& url)
 | 
			
		||||
    {
 | 
			
		||||
        std::string protocol, host, path, query;
 | 
			
		||||
        int port {0};
 | 
			
		||||
 | 
			
		||||
        if (!WebSocketHandshake::parseUrl(url, protocol, host,
 | 
			
		||||
                                          path, query, port))
 | 
			
		||||
        {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::cout << "[" << url << "]" << std::endl;
 | 
			
		||||
        std::cout << protocol << std::endl;
 | 
			
		||||
        std::cout << host << std::endl;
 | 
			
		||||
        std::cout << port << std::endl;
 | 
			
		||||
        std::cout << path << std::endl;
 | 
			
		||||
        std::cout << query << std::endl;
 | 
			
		||||
        std::cout << "-------------------------------" << std::endl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string WebSocketHandshake::trim(const std::string& str)
 | 
			
		||||
    {
 | 
			
		||||
        std::string out(str);
 | 
			
		||||
@@ -171,7 +88,7 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    std::string WebSocketHandshake::genRandomString(const int len)
 | 
			
		||||
    {
 | 
			
		||||
        std::string alphanum = 
 | 
			
		||||
        std::string alphanum =
 | 
			
		||||
            "0123456789"
 | 
			
		||||
            "ABCDEFGH"
 | 
			
		||||
            "abcdefgh";
 | 
			
		||||
@@ -192,67 +109,12 @@ namespace ix
 | 
			
		||||
        return s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    std::pair<bool, WebSocketHttpHeaders> WebSocketHandshake::parseHttpHeaders(
 | 
			
		||||
        const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        WebSocketHttpHeaders headers;
 | 
			
		||||
 | 
			
		||||
        char line[256];
 | 
			
		||||
        int i;
 | 
			
		||||
 | 
			
		||||
        while (true) 
 | 
			
		||||
        {
 | 
			
		||||
            int colon = 0;
 | 
			
		||||
 | 
			
		||||
            for (i = 0;
 | 
			
		||||
                 i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n');
 | 
			
		||||
                 ++i)
 | 
			
		||||
            {
 | 
			
		||||
                if (!_socket->readByte(line+i, isCancellationRequested))
 | 
			
		||||
                {
 | 
			
		||||
                    return std::make_pair(false, headers);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (line[i] == ':' && colon == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    colon = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (line[0] == '\r' && line[1] == '\n')
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // line is a single header entry. split by ':', and add it to our
 | 
			
		||||
            // header map. ignore lines with no colon.
 | 
			
		||||
            if (colon > 0)
 | 
			
		||||
            {
 | 
			
		||||
                line[i] = '\0';
 | 
			
		||||
                std::string lineStr(line);
 | 
			
		||||
                // colon is ':', colon+1 is ' ', colon+2 is the start of the value.
 | 
			
		||||
                // i is end of string (\0), i-colon is length of string minus key;
 | 
			
		||||
                // subtract 1 for '\0', 1 for '\n', 1 for '\r',
 | 
			
		||||
                // 1 for the ' ' after the ':', and total is -4
 | 
			
		||||
                std::string name(lineStr.substr(0, colon));
 | 
			
		||||
                std::string value(lineStr.substr(colon + 2, i - colon - 4));
 | 
			
		||||
 | 
			
		||||
                // Make the name lower case.
 | 
			
		||||
                std::transform(name.begin(), name.end(), name.begin(), ::tolower);
 | 
			
		||||
 | 
			
		||||
                headers[name] = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::make_pair(true, headers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
 | 
			
		||||
    {
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << "HTTP/1.1 ";
 | 
			
		||||
        ss << code;
 | 
			
		||||
        ss << "\r\n";
 | 
			
		||||
        ss << " ";
 | 
			
		||||
        ss << reason;
 | 
			
		||||
        ss << "\r\n";
 | 
			
		||||
 | 
			
		||||
@@ -277,7 +139,7 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        _requestInitCancellation = false;
 | 
			
		||||
 | 
			
		||||
        auto isCancellationRequested = 
 | 
			
		||||
        auto isCancellationRequested =
 | 
			
		||||
            makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
 | 
			
		||||
 | 
			
		||||
        std::string errMsg;
 | 
			
		||||
@@ -355,7 +217,7 @@ namespace ix
 | 
			
		||||
            return WebSocketInitResult(false, status, ss.str());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto result = parseHttpHeaders(isCancellationRequested);
 | 
			
		||||
        auto result = parseHttpHeaders(_socket, isCancellationRequested);
 | 
			
		||||
        auto headersValid = result.first;
 | 
			
		||||
        auto headers = result.second;
 | 
			
		||||
 | 
			
		||||
@@ -372,7 +234,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check the value of the connection field
 | 
			
		||||
        // Some websocket servers (Go/Gorilla?) send lowercase values for the 
 | 
			
		||||
        // Some websocket servers (Go/Gorilla?) send lowercase values for the
 | 
			
		||||
        // connection header, so do a case insensitive comparison
 | 
			
		||||
        if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
 | 
			
		||||
        {
 | 
			
		||||
@@ -418,7 +280,7 @@ namespace ix
 | 
			
		||||
        // Set the socket to non blocking mode + other tweaks
 | 
			
		||||
        SocketConnect::configure(fd);
 | 
			
		||||
 | 
			
		||||
        auto isCancellationRequested = 
 | 
			
		||||
        auto isCancellationRequested =
 | 
			
		||||
            makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
 | 
			
		||||
 | 
			
		||||
        std::string remote = std::string("remote fd ") + std::to_string(fd);
 | 
			
		||||
@@ -432,7 +294,7 @@ namespace ix
 | 
			
		||||
        {
 | 
			
		||||
            return sendErrorResponse(400, "Error reading HTTP request line");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Validate request line (GET /foo HTTP/1.1\r\n)
 | 
			
		||||
        auto requestLine = parseRequestLine(line);
 | 
			
		||||
        auto method      = std::get<0>(requestLine);
 | 
			
		||||
@@ -450,7 +312,7 @@ namespace ix
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Retrieve and validate HTTP headers
 | 
			
		||||
        auto result = parseHttpHeaders(isCancellationRequested);
 | 
			
		||||
        auto result = parseHttpHeaders(_socket, isCancellationRequested);
 | 
			
		||||
        auto headersValid = result.first;
 | 
			
		||||
        auto headers = result.second;
 | 
			
		||||
 | 
			
		||||
@@ -491,7 +353,7 @@ namespace ix
 | 
			
		||||
        WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"].c_str(), output);
 | 
			
		||||
 | 
			
		||||
        std::stringstream ss;
 | 
			
		||||
        ss << "HTTP/1.1 101\r\n";
 | 
			
		||||
        ss << "HTTP/1.1 101 Switching Protocols\r\n";
 | 
			
		||||
        ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
 | 
			
		||||
        ss << "Upgrade: websocket\r\n";
 | 
			
		||||
        ss << "Connection: Upgrade\r\n";
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    struct WebSocketInitResult
 | 
			
		||||
    {
 | 
			
		||||
@@ -59,19 +59,10 @@ namespace ix
 | 
			
		||||
        WebSocketInitResult serverHandshake(int fd,
 | 
			
		||||
                                            int timeoutSecs);
 | 
			
		||||
 | 
			
		||||
        static bool parseUrl(const std::string& url,
 | 
			
		||||
                             std::string& protocol,
 | 
			
		||||
                             std::string& host,
 | 
			
		||||
                             std::string& path,
 | 
			
		||||
                             std::string& query,
 | 
			
		||||
                             int& port);
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        static void printUrl(const std::string& url);
 | 
			
		||||
        std::string genRandomString(const int len);
 | 
			
		||||
 | 
			
		||||
        // Parse HTTP headers
 | 
			
		||||
        std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(const CancellationRequest& isCancellationRequested);
 | 
			
		||||
        WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
 | 
			
		||||
 | 
			
		||||
        std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										66
									
								
								ixwebsocket/IXWebSocketHttpHeaders.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  IXWebSocketHttpHeaders.h
 | 
			
		||||
 *  Author: Benjamin Sergeant
 | 
			
		||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "IXWebSocketHttpHeaders.h"
 | 
			
		||||
#include "IXSocket.h"
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
 | 
			
		||||
        std::shared_ptr<Socket> socket,
 | 
			
		||||
        const CancellationRequest& isCancellationRequested)
 | 
			
		||||
    {
 | 
			
		||||
        WebSocketHttpHeaders headers;
 | 
			
		||||
 | 
			
		||||
        char line[1024];
 | 
			
		||||
        int i;
 | 
			
		||||
 | 
			
		||||
        while (true)
 | 
			
		||||
        {
 | 
			
		||||
            int colon = 0;
 | 
			
		||||
 | 
			
		||||
            for (i = 0;
 | 
			
		||||
                 i < 2 || (i < 1023 && line[i-2] != '\r' && line[i-1] != '\n');
 | 
			
		||||
                 ++i)
 | 
			
		||||
            {
 | 
			
		||||
                if (!socket->readByte(line+i, isCancellationRequested))
 | 
			
		||||
                {
 | 
			
		||||
                    return std::make_pair(false, headers);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (line[i] == ':' && colon == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    colon = i;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (line[0] == '\r' && line[1] == '\n')
 | 
			
		||||
            {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // line is a single header entry. split by ':', and add it to our
 | 
			
		||||
            // header map. ignore lines with no colon.
 | 
			
		||||
            if (colon > 0)
 | 
			
		||||
            {
 | 
			
		||||
                line[i] = '\0';
 | 
			
		||||
                std::string lineStr(line);
 | 
			
		||||
                // colon is ':', colon+1 is ' ', colon+2 is the start of the value.
 | 
			
		||||
                // i is end of string (\0), i-colon is length of string minus key;
 | 
			
		||||
                // subtract 1 for '\0', 1 for '\n', 1 for '\r',
 | 
			
		||||
                // 1 for the ' ' after the ':', and total is -4
 | 
			
		||||
                std::string name(lineStr.substr(0, colon));
 | 
			
		||||
                std::string value(lineStr.substr(colon + 2, i - colon - 4));
 | 
			
		||||
 | 
			
		||||
                headers[name] = value;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return std::make_pair(true, headers);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,10 +6,40 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <unordered_map>
 | 
			
		||||
#include "IXCancellationRequest.h"
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    using WebSocketHttpHeaders = std::unordered_map<std::string, std::string>;
 | 
			
		||||
    class Socket;
 | 
			
		||||
 | 
			
		||||
    struct CaseInsensitiveLess
 | 
			
		||||
    {
 | 
			
		||||
        // Case Insensitive compare_less binary function
 | 
			
		||||
        struct NocaseCompare
 | 
			
		||||
        {
 | 
			
		||||
            bool operator() (const unsigned char& c1, const unsigned char& c2) const
 | 
			
		||||
            {
 | 
			
		||||
                return std::tolower(c1) < std::tolower(c2);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        bool operator() (const std::string & s1, const std::string & s2) const
 | 
			
		||||
        {
 | 
			
		||||
            return std::lexicographical_compare
 | 
			
		||||
                (s1.begin(), s1.end(),   // source range
 | 
			
		||||
                 s2.begin(), s2.end(),   // dest range
 | 
			
		||||
                 NocaseCompare());  // comparison
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using WebSocketHttpHeaders = std::map<std::string, std::string, CaseInsensitiveLess>;
 | 
			
		||||
 | 
			
		||||
    std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(
 | 
			
		||||
        std::shared_ptr<Socket> socket,
 | 
			
		||||
        const CancellationRequest& isCancellationRequested);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
 *  - Reused zlib compression + decompression bits.
 | 
			
		||||
 *  - Refactored to have 2 class for compression and decompression, to allow multi-threading
 | 
			
		||||
 *    and make sure that _compressBuffer is not shared between threads.
 | 
			
		||||
 *  - Original code wasn't working for some reason, I had to add checks 
 | 
			
		||||
 *  - Original code wasn't working for some reason, I had to add checks
 | 
			
		||||
 *    for the presence of the kEmptyUncompressedBlock at the end of buffer so that servers
 | 
			
		||||
 *    would start accepting receiving/decoding compressed messages. Original code was probably
 | 
			
		||||
 *    modifying the passed in buffers before processing in enabled.hpp ?
 | 
			
		||||
@@ -65,13 +65,13 @@ namespace ix
 | 
			
		||||
 | 
			
		||||
    bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
 | 
			
		||||
    {
 | 
			
		||||
        bool clientNoContextTakeover = 
 | 
			
		||||
        bool clientNoContextTakeover =
 | 
			
		||||
            perMessageDeflateOptions.getClientNoContextTakeover();
 | 
			
		||||
 | 
			
		||||
        uint8_t deflateBits = perMessageDeflateOptions.getClientMaxWindowBits();
 | 
			
		||||
        uint8_t inflateBits = perMessageDeflateOptions.getServerMaxWindowBits();
 | 
			
		||||
 | 
			
		||||
        return _compressor->init(deflateBits, clientNoContextTakeover) && 
 | 
			
		||||
        return _compressor->init(deflateBits, clientNoContextTakeover) &&
 | 
			
		||||
               _decompressor->init(inflateBits, clientNoContextTakeover);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class WebSocketPerMessageDeflateOptions;
 | 
			
		||||
    class WebSocketPerMessageDeflateCompressor;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    // The passed in size (4) is important, without it the string litteral
 | 
			
		||||
    // is treated as a char* and the null termination (\x00) makes it 
 | 
			
		||||
    // is treated as a char* and the null termination (\x00) makes it
 | 
			
		||||
    // look like an empty string.
 | 
			
		||||
    const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
 | 
			
		||||
 | 
			
		||||
@@ -76,16 +76,16 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        //
 | 
			
		||||
        // 7.2.1.  Compression
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    An endpoint uses the following algorithm to compress a message.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    1.  Compress all the octets of the payload of the message using
 | 
			
		||||
        //        DEFLATE.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    2.  If the resulting data does not end with an empty DEFLATE block
 | 
			
		||||
        //        with no compression (the "BTYPE" bits are set to 00), append an
 | 
			
		||||
        //        empty DEFLATE block with no compression to the tail end.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    3.  Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
 | 
			
		||||
        //        After this step, the last octet of the compressed data contains
 | 
			
		||||
        //        (possibly part of) the DEFLATE header bits with the "BTYPE" bits
 | 
			
		||||
@@ -168,14 +168,14 @@ namespace ix
 | 
			
		||||
    {
 | 
			
		||||
        //
 | 
			
		||||
        // 7.2.2.  Decompression
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    An endpoint uses the following algorithm to decompress a message.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    1.  Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
 | 
			
		||||
        //        payload of the message.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        //    2.  Decompress the resulting data using DEFLATE.
 | 
			
		||||
        // 
 | 
			
		||||
        //
 | 
			
		||||
        std::string inFixed(in);
 | 
			
		||||
        inFixed += kEmptyUncompressedBlock;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class WebSocketPerMessageDeflateCompressor
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ namespace ix
 | 
			
		||||
        _serverMaxWindowBits = serverMaxWindowBits;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    // Four extension parameters are defined for "permessage-deflate" to
 | 
			
		||||
    // help endpoints manage per-connection resource usage.
 | 
			
		||||
    //
 | 
			
		||||
@@ -88,9 +88,9 @@ namespace ix
 | 
			
		||||
                int x;
 | 
			
		||||
                ss >> x;
 | 
			
		||||
 | 
			
		||||
                // Sanitize values to be in the proper range [8, 15] in 
 | 
			
		||||
                // Sanitize values to be in the proper range [8, 15] in
 | 
			
		||||
                // case a server would give us bogus values
 | 
			
		||||
                _serverMaxWindowBits = 
 | 
			
		||||
                _serverMaxWindowBits =
 | 
			
		||||
                    std::min(maxServerMaxWindowBits,
 | 
			
		||||
                        std::max(x, minServerMaxWindowBits));
 | 
			
		||||
            }
 | 
			
		||||
@@ -103,9 +103,9 @@ namespace ix
 | 
			
		||||
                int x;
 | 
			
		||||
                ss >> x;
 | 
			
		||||
 | 
			
		||||
                // Sanitize values to be in the proper range [8, 15] in 
 | 
			
		||||
                // Sanitize values to be in the proper range [8, 15] in
 | 
			
		||||
                // case a server would give us bogus values
 | 
			
		||||
                _clientMaxWindowBits = 
 | 
			
		||||
                _clientMaxWindowBits =
 | 
			
		||||
                    std::min(maxClientMaxWindowBits,
 | 
			
		||||
                        std::max(x, minClientMaxWindowBits));
 | 
			
		||||
            }
 | 
			
		||||
@@ -162,7 +162,7 @@ namespace ix
 | 
			
		||||
    std::string WebSocketPerMessageDeflateOptions::removeSpaces(const std::string& str)
 | 
			
		||||
    {
 | 
			
		||||
        std::string out(str);
 | 
			
		||||
        out.erase(std::remove_if(out.begin(), 
 | 
			
		||||
        out.erase(std::remove_if(out.begin(),
 | 
			
		||||
                                 out.end(),
 | 
			
		||||
                                 [](unsigned char x){ return std::isspace(x); }),
 | 
			
		||||
                  out.end());
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    class WebSocketPerMessageDeflateOptions
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
namespace ix 
 | 
			
		||||
namespace ix
 | 
			
		||||
{
 | 
			
		||||
    struct WebSocketSendInfo
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||