Compare commits
	
		
			103 Commits
		
	
	
		
			user/bserg
			...
			feature/pi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					a5179cd17f | ||
| 
						 | 
					e158175819 | ||
| 
						 | 
					ec2f229489 | ||
| 
						 | 
					ead9616d04 | ||
| 
						 | 
					922d58eb59 | ||
| 
						 | 
					d1a7b9a985 | ||
| 
						 | 
					11092027cd | ||
| 
						 | 
					4de3ec995e | ||
| 
						 | 
					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 | ||
| 
						 | 
					ee12fbdb5f | ||
| 
						 | 
					316c630830 | ||
| 
						 | 
					1ea5db6110 | ||
| 
						 | 
					986d9a00c0 | ||
| 
						 | 
					7a05a11014 | ||
| 
						 | 
					f09434263c | ||
| 
						 | 
					335f594165 | ||
| 
						 | 
					fa7ef06f4d | ||
| 
						 | 
					3c9ec0aed0 | ||
| 
						 | 
					c665d65cba | ||
| 
						 | 
					5d4e897cc4 | ||
| 
						 | 
					05033714bf | ||
| 
						 | 
					a02bd3f25c | ||
| 
						 | 
					fdbd213fa2 | ||
| 
						 | 
					da64d349c8 | ||
| 
						 | 
					17b01a8c66 | ||
| 
						 | 
					79dd766fab | ||
| 
						 | 
					8375b28747 | ||
| 
						 | 
					e12551f309 | ||
| 
						 | 
					6102f81710 | ||
| 
						 | 
					9f678e5962 | ||
| 
						 | 
					02a704a8c7 | ||
| 
						 | 
					dd2360ed70 | ||
| 
						 | 
					c4ab996470 | ||
| 
						 | 
					6c54b07d92 | ||
| 
						 | 
					7f9bef3b8d | ||
| 
						 | 
					12d1c5d956 | ||
| 
						 | 
					e9a4bd5617 | ||
| 
						 | 
					f34ccbfdb5 | ||
| 
						 | 
					1fa75d7fb2 | ||
| 
						 | 
					39140ef98c | ||
| 
						 | 
					e30ef4a87c | ||
| 
						 | 
					9fc94f0487 | ||
| 
						 | 
					121acdab6f | ||
| 
						 | 
					6deaa03114 | ||
| 
						 | 
					f4f30686c5 | ||
| 
						 | 
					a21aae521f | ||
| 
						 | 
					aed2356fc1 | ||
| 
						 | 
					a478f734f6 | ||
| 
						 | 
					98c579da03 | ||
| 
						 | 
					e80def0cd0 | ||
| 
						 | 
					cc8a9e883e | ||
| 
						 | 
					4d587e35d8 | ||
| 
						 | 
					50f4fd1115 | ||
| 
						 | 
					06d2b68696 | ||
| 
						 | 
					bf6f057777 | ||
| 
						 | 
					b57c1d69f2 | ||
| 
						 | 
					ff265d83f9 | ||
| 
						 | 
					5b1c97b774 | ||
| 
						 | 
					c8c81366f7 | ||
| 
						 | 
					9a37fd56d1 | ||
| 
						 | 
					7ecaff8c5d | ||
| 
						 | 
					e4b0286a25 | ||
| 
						 | 
					7ae6972306 | ||
| 
						 | 
					59cea0372b | ||
| 
						 | 
					78d88a8520 | ||
| 
						 | 
					273af25d57 | ||
| 
						 | 
					46d00360a8 | ||
| 
						 | 
					3f5935a284 | ||
| 
						 | 
					c236ff66e9 | 
							
								
								
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					build
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ping_pong/.gitignore → .gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								examples/ping_pong/.gitignore → .gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1 @@
 | 
				
			|||||||
venv
 | 
					 | 
				
			||||||
build
 | 
					build
 | 
				
			||||||
							
								
								
									
										0
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					language: cpp
 | 
				
			||||||
 | 
					dist: xenial
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					compiler:
 | 
				
			||||||
 | 
					  - gcc
 | 
				
			||||||
 | 
					  - clang
 | 
				
			||||||
 | 
					os:
 | 
				
			||||||
 | 
					  - linux
 | 
				
			||||||
 | 
					  - osx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					matrix:
 | 
				
			||||||
 | 
					  exclude:
 | 
				
			||||||
 | 
					    # GCC fails on recent Travis OSX images.
 | 
				
			||||||
 | 
					    - compiler: gcc
 | 
				
			||||||
 | 
					      os: osx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					script: python test/run.py
 | 
				
			||||||
@@ -6,16 +6,21 @@
 | 
				
			|||||||
cmake_minimum_required(VERSION 3.4.1)
 | 
					cmake_minimum_required(VERSION 3.4.1)
 | 
				
			||||||
project(ixwebsocket C CXX)
 | 
					project(ixwebsocket C CXX)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (CMAKE_CXX_STANDARD 11)
 | 
					set (CMAKE_CXX_STANDARD 14)
 | 
				
			||||||
set (CXX_STANDARD_REQUIRED ON)
 | 
					set (CXX_STANDARD_REQUIRED ON)
 | 
				
			||||||
set (CMAKE_CXX_EXTENSIONS OFF)
 | 
					set (CMAKE_CXX_EXTENSIONS OFF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
 | 
					# -Wshorten-64-to-32 does not work with clang
 | 
				
			||||||
 | 
					if (NOT WIN32)
 | 
				
			||||||
 | 
					  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic")
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set( IXWEBSOCKET_SOURCES
 | 
					set( IXWEBSOCKET_SOURCES
 | 
				
			||||||
    ixwebsocket/IXEventFd.cpp
 | 
					    ixwebsocket/IXEventFd.cpp
 | 
				
			||||||
    ixwebsocket/IXSocket.cpp
 | 
					    ixwebsocket/IXSocket.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXSocketServer.cpp
 | 
				
			||||||
    ixwebsocket/IXSocketConnect.cpp
 | 
					    ixwebsocket/IXSocketConnect.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXSocketFactory.cpp
 | 
				
			||||||
    ixwebsocket/IXDNSLookup.cpp
 | 
					    ixwebsocket/IXDNSLookup.cpp
 | 
				
			||||||
    ixwebsocket/IXCancellationRequest.cpp
 | 
					    ixwebsocket/IXCancellationRequest.cpp
 | 
				
			||||||
    ixwebsocket/IXWebSocket.cpp
 | 
					    ixwebsocket/IXWebSocket.cpp
 | 
				
			||||||
@@ -23,16 +28,23 @@ set( IXWEBSOCKET_SOURCES
 | 
				
			|||||||
    ixwebsocket/IXWebSocketTransport.cpp
 | 
					    ixwebsocket/IXWebSocketTransport.cpp
 | 
				
			||||||
    ixwebsocket/IXWebSocketHandshake.cpp
 | 
					    ixwebsocket/IXWebSocketHandshake.cpp
 | 
				
			||||||
    ixwebsocket/IXWebSocketPerMessageDeflate.cpp
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflate.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
 | 
				
			||||||
    ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflateOptions.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXWebSocketHttpHeaders.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXHttpClient.cpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXUrlParser.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set( IXWEBSOCKET_HEADERS
 | 
					set( IXWEBSOCKET_HEADERS
 | 
				
			||||||
    ixwebsocket/IXEventFd.h
 | 
					    ixwebsocket/IXEventFd.h
 | 
				
			||||||
    ixwebsocket/IXSocket.h
 | 
					    ixwebsocket/IXSocket.h
 | 
				
			||||||
 | 
					    ixwebsocket/IXSocketServer.h
 | 
				
			||||||
    ixwebsocket/IXSocketConnect.h
 | 
					    ixwebsocket/IXSocketConnect.h
 | 
				
			||||||
 | 
					    ixwebsocket/IXSocketFactory.h
 | 
				
			||||||
    ixwebsocket/IXSetThreadName.h
 | 
					    ixwebsocket/IXSetThreadName.h
 | 
				
			||||||
    ixwebsocket/IXDNSLookup.h
 | 
					    ixwebsocket/IXDNSLookup.h
 | 
				
			||||||
    ixwebsocket/IXCancellationRequest.h
 | 
					    ixwebsocket/IXCancellationRequest.h
 | 
				
			||||||
 | 
					    ixwebsocket/IXProgressCallback.h
 | 
				
			||||||
    ixwebsocket/IXWebSocket.h
 | 
					    ixwebsocket/IXWebSocket.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketServer.h
 | 
					    ixwebsocket/IXWebSocketServer.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketTransport.h
 | 
					    ixwebsocket/IXWebSocketTransport.h
 | 
				
			||||||
@@ -40,18 +52,23 @@ set( IXWEBSOCKET_HEADERS
 | 
				
			|||||||
    ixwebsocket/IXWebSocketSendInfo.h
 | 
					    ixwebsocket/IXWebSocketSendInfo.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketErrorInfo.h
 | 
					    ixwebsocket/IXWebSocketErrorInfo.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketPerMessageDeflate.h
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflate.h
 | 
				
			||||||
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
 | 
					    ixwebsocket/IXWebSocketPerMessageDeflateOptions.h
 | 
				
			||||||
    ixwebsocket/IXWebSocketHttpHeaders.h
 | 
					    ixwebsocket/IXWebSocketHttpHeaders.h
 | 
				
			||||||
 | 
					    ixwebsocket/libwshandshake.hpp
 | 
				
			||||||
 | 
					    ixwebsocket/IXHttpClient.h
 | 
				
			||||||
 | 
					    ixwebsocket/IXUrlParser.h
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Platform specific code
 | 
					# Platform specific code
 | 
				
			||||||
if (APPLE)
 | 
					if (APPLE)
 | 
				
			||||||
    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
 | 
					    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/apple/IXSetThreadName_apple.cpp)
 | 
				
			||||||
 | 
					elseif (WIN32)
 | 
				
			||||||
 | 
					    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/windows/IXSetThreadName_windows.cpp)
 | 
				
			||||||
else()
 | 
					else()
 | 
				
			||||||
    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
 | 
					    list( APPEND IXWEBSOCKET_SOURCES ixwebsocket/linux/IXSetThreadName_linux.cpp)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
if (USE_TLS)
 | 
					if (USE_TLS)
 | 
				
			||||||
    add_definitions(-DIXWEBSOCKET_USE_TLS)
 | 
					    add_definitions(-DIXWEBSOCKET_USE_TLS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,9 +89,37 @@ add_library( ixwebsocket STATIC
 | 
				
			|||||||
    ${IXWEBSOCKET_HEADERS}
 | 
					    ${IXWEBSOCKET_HEADERS}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
target_link_libraries(ixwebsocket "z")
 | 
					# gcc/Linux needs -pthread
 | 
				
			||||||
 | 
					find_package(Threads)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if(UNIX AND NOT APPLE)
 | 
				
			||||||
 | 
					  find_package(OpenSSL REQUIRED)
 | 
				
			||||||
 | 
					  add_definitions(${OPENSSL_DEFINITIONS})
 | 
				
			||||||
 | 
					  message(STATUS "OpenSSL: " ${OPENSSL_VERSION})
 | 
				
			||||||
 | 
					  include_directories(${OPENSSL_INCLUDE_DIR})
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (WIN32)
 | 
				
			||||||
 | 
					  get_filename_component(libz_path
 | 
				
			||||||
 | 
					    ${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/release_dynamic/x64/lib/zlib.lib
 | 
				
			||||||
 | 
					    ABSOLUTE)
 | 
				
			||||||
 | 
					  add_library(libz STATIC IMPORTED)
 | 
				
			||||||
 | 
					  set_target_properties(libz PROPERTIES IMPORTED_LOCATION
 | 
				
			||||||
 | 
					    ${libz_path})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  include_directories(${PROJECT_SOURCE_DIR}/third_party/ZLIB-Windows/zlib-1.2.11_deploy_v140/include)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  target_link_libraries(ixwebsocket libz wsock32 ws2_32)
 | 
				
			||||||
 | 
					  add_definitions(-D_CRT_SECURE_NO_WARNINGS)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					else()
 | 
				
			||||||
 | 
					  target_link_libraries(ixwebsocket 
 | 
				
			||||||
 | 
					    z ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set( IXWEBSOCKET_INCLUDE_DIRS
 | 
					set( IXWEBSOCKET_INCLUDE_DIRS
 | 
				
			||||||
    .
 | 
					    .
 | 
				
			||||||
    ../../shared/OpenSSL/include)
 | 
					    ../../shared/OpenSSL/include)
 | 
				
			||||||
target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
 | 
					target_include_directories( ixwebsocket PUBLIC ${IXWEBSOCKET_INCLUDE_DIRS} )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_subdirectory(ws)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +0,0 @@
 | 
				
			|||||||
docker/Dockerfile.debian
 | 
					 | 
				
			||||||
							
								
								
									
										31
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								Dockerfile
									
									
									
									
									
										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"]
 | 
				
			||||||
							
								
								
									
										116
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								README.md
									
									
									
									
									
								
							@@ -1,9 +1,11 @@
 | 
				
			|||||||
# General
 | 
					# General
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Introduction
 | 
					## Introduction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
 | 
					[*WebSocket*](https://en.wikipedia.org/wiki/WebSocket) is a computer communications protocol, providing full-duplex
 | 
				
			||||||
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.
 | 
					communication channels over a single TCP connection. *IXWebSocket* is a C++ library for client and server Websocket communication, and for client HTTP communication. The code is derived from [easywsclient](https://github.com/dhbaird/easywsclient) and from the [Satori C SDK](https://github.com/satori-com/satori-rtm-sdk-c). It has been tested on the following platforms.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* macOS
 | 
					* macOS
 | 
				
			||||||
* iOS
 | 
					* iOS
 | 
				
			||||||
@@ -13,7 +15,7 @@ communication channels over a single TCP connection. *IXWebSocket* is a C++ libr
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Examples
 | 
					## 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.
 | 
					Here is what the client API looks like.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,7 +23,11 @@ Here is what the client API looks like.
 | 
				
			|||||||
ix::WebSocket webSocket;
 | 
					ix::WebSocket webSocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::string url("ws://localhost:8080/");
 | 
					std::string url("ws://localhost:8080/");
 | 
				
			||||||
webSocket.configure(url);
 | 
					webSocket.setUrl(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Optional heart beat, sent every 45 seconds when there is not any traffic
 | 
				
			||||||
 | 
					// to make sure that load balancers do not kill an idle connection.
 | 
				
			||||||
 | 
					webSocket.setHeartBeatPeriod(45);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Setup a callback to be fired when a message or an event (open, close, error) is received
 | 
					// Setup a callback to be fired when a message or an event (open, close, error) is received
 | 
				
			||||||
webSocket.setOnMessageCallback(
 | 
					webSocket.setOnMessageCallback(
 | 
				
			||||||
@@ -71,7 +77,10 @@ server.setOnConnectionCallback(
 | 
				
			|||||||
                if (messageType == ix::WebSocket_MessageType_Open)
 | 
					                if (messageType == ix::WebSocket_MessageType_Open)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    std::cerr << "New connection" << std::endl;
 | 
					                    std::cerr << "New connection" << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // The uri the client did connect to.
 | 
				
			||||||
                    std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
					                    std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    std::cerr << "Headers:" << std::endl;
 | 
					                    std::cerr << "Headers:" << std::endl;
 | 
				
			||||||
                    for (auto it : openInfo.headers)
 | 
					                    for (auto it : openInfo.headers)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
@@ -80,7 +89,7 @@ server.setOnConnectionCallback(
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                else if (messageType == ix::WebSocket_MessageType_Message)
 | 
					                else if (messageType == ix::WebSocket_MessageType_Message)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // For an echo server, we just send back to the client whatever was received by the client
 | 
					                    // For an echo server, we just send back to the client whatever was received by the server
 | 
				
			||||||
                    // All connected clients are available in an std::set. See the broadcast cpp example.
 | 
					                    // All connected clients are available in an std::set. See the broadcast cpp example.
 | 
				
			||||||
                    webSocket->send(str);
 | 
					                    webSocket->send(str);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -104,12 +113,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 = parseHeaders(headersData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
					## 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.
 | 
					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`.
 | 
					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 create --cmake https://github.com/machinezone/IXWebSocket/archive/v1.1.0.tar.gz
 | 
				
			||||||
 | 
					brew install IXWebSocket
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Implementation details
 | 
					## Implementation details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Per Message Deflate compression.
 | 
					### Per Message Deflate compression.
 | 
				
			||||||
@@ -128,25 +206,19 @@ 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.
 | 
					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 500M were sent and received succesfully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Limitations
 | 
					## Limitations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
 | 
					* There is no text support for sending data, only the binary protocol is supported. Sending json or text over the binary protocol works well.
 | 
				
			||||||
* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
 | 
					* Automatic reconnection works at the TCP socket level, and will detect remote end disconnects. However, if the device/computer network become unreachable (by turning off wifi), it is quite hard to reliably and timely detect it at the socket level using `recv` and `send` error codes. [Here](https://stackoverflow.com/questions/14782143/linux-socket-how-to-detect-disconnected-network-in-a-client-program) is a good discussion on the subject. This behavior is consistent with other runtimes such as node.js. One way to detect a disconnected device with low level C code is to do a name resolution with DNS but this can be expensive. Mobile devices have good and reliable API to do that.
 | 
				
			||||||
* The server code is using select to detect incoming data, and creates one OS thread per connection. This isn't as scalable as strategies using epoll or kqueue.
 | 
					* The server code is using select to detect incoming data, and creates one OS thread per connection. This is not as scalable as strategies using epoll or kqueue.
 | 
				
			||||||
 | 
					 | 
				
			||||||
## 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.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## C++ code organization
 | 
					## 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
 | 
					+-----------------------+ --- Public
 | 
				
			||||||
@@ -198,7 +270,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.
 | 
					1. WebSocket_ReadyState_Connecting - The connection is not yet open.
 | 
				
			||||||
2. WebSocket_ReadyState_Open       - The connection is open and ready to communicate.
 | 
					2. WebSocket_ReadyState_Open       - The connection is open and ready to communicate.
 | 
				
			||||||
3. WebSocket_ReadyState_Closing    - The connection is in the process of closing.
 | 
					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
 | 
					### Open and Close notifications
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -304,3 +376,13 @@ 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");
 | 
					websocket.ping("ping data, optional (empty string is ok): limited to 125 bytes long");
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Heartbeat.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can configure an optional heart beat / keep-alive, sent every 45 seconds
 | 
				
			||||||
 | 
					when there is not any traffic to make sure that load balancers do not kill an
 | 
				
			||||||
 | 
					idle connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					webSocket.setHeartBeatPeriod(45);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								appveyor.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								appveyor.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					image:
 | 
				
			||||||
 | 
					- Visual Studio 2017
 | 
				
			||||||
 | 
					- Ubuntu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install:
 | 
				
			||||||
 | 
					- ls -al
 | 
				
			||||||
 | 
					- cmd: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
 | 
				
			||||||
 | 
					- python test/run.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					build: off
 | 
				
			||||||
@@ -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
									
									
								
							
							
						
						
									
										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 11)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/chat/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								examples/chat/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +0,0 @@
 | 
				
			|||||||
build
 | 
					 | 
				
			||||||
venv
 | 
					 | 
				
			||||||
node_modules
 | 
					 | 
				
			||||||
@@ -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 11)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
									
									
									
								
							
							
						
						
									
										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"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								examples/cobra_publisher/ixcrypto/IXHash.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								examples/cobra_publisher/ixcrypto/IXHash.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXHash.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    uint64_t djb2Hash(const std::string& data)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        uint64_t hashAddress = 5381;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (auto& c : data)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            hashAddress = ((hashAddress << 5) + hashAddress) + c;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return hashAddress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								examples/cobra_publisher/ixcrypto/IXHash.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples/cobra_publisher/ixcrypto/IXHash.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXHash.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    uint64_t djb2Hash(const std::string& data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										75
									
								
								examples/cobra_publisher/ixcrypto/IXUuid.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								examples/cobra_publisher/ixcrypto/IXUuid.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXUuid.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate a random uuid similar to the uuid python module
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * >>> import uuid
 | 
				
			||||||
 | 
					 * >>> uuid.uuid4().hex
 | 
				
			||||||
 | 
					 * 'bec08155b37d4050a1f3c3fa0276bf12'
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Code adapted from https://github.com/r-lyeh-archived/sole
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXUuid.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <iomanip>
 | 
				
			||||||
 | 
					#include <random>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    class Uuid
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            Uuid();
 | 
				
			||||||
 | 
					            std::string toString() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            uint64_t _ab;
 | 
				
			||||||
 | 
					            uint64_t _cd;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Uuid::Uuid()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        static std::random_device rd;
 | 
				
			||||||
 | 
					        static std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ab = dist(rd);
 | 
				
			||||||
 | 
					        _cd = dist(rd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _ab = (_ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
 | 
				
			||||||
 | 
					        _cd = (_cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string Uuid::toString() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::stringstream ss;
 | 
				
			||||||
 | 
					        ss << std::hex << std::nouppercase << std::setfill('0');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint32_t a = (_ab >> 32);
 | 
				
			||||||
 | 
					        uint32_t b = (_ab & 0xFFFFFFFF);
 | 
				
			||||||
 | 
					        uint32_t c = (_cd >> 32);
 | 
				
			||||||
 | 
					        uint32_t d = (_cd & 0xFFFFFFFF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ss << std::setw(8) << (a);
 | 
				
			||||||
 | 
					        ss << std::setw(4) << (b >> 16);
 | 
				
			||||||
 | 
					        ss << std::setw(4) << (b & 0xFFFF);
 | 
				
			||||||
 | 
					        ss << std::setw(4) << (c >> 16 );
 | 
				
			||||||
 | 
					        ss << std::setw(4) << (c & 0xFFFF);
 | 
				
			||||||
 | 
					        ss << std::setw(8) << d;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ss.str();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string uuid4()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Uuid id;
 | 
				
			||||||
 | 
					        return id.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								examples/cobra_publisher/ixcrypto/IXUuid.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/cobra_publisher/ixcrypto/IXUuid.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXUuid.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2017 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   /**
 | 
				
			||||||
 | 
					    * Generate a random uuid
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					   std::string uuid4();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,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 11)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 11)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
									
									
								
							
							
						
						
									
										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 11)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_receive/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/ws_receive/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					build
 | 
				
			||||||
							
								
								
									
										30
									
								
								examples/ws_receive/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/ws_receive/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					# Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cmake_minimum_required (VERSION 3.4.1)
 | 
				
			||||||
 | 
					project (ws_receive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# There's -Weverything too for clang
 | 
				
			||||||
 | 
					set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set (CMAKE_CXX_STANDARD 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					option(USE_TLS "Add TLS support" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include_directories(ws_receive .)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable(ws_receive 
 | 
				
			||||||
 | 
					  jsoncpp/jsoncpp.cpp
 | 
				
			||||||
 | 
					  ixcrypto/IXBase64.cpp
 | 
				
			||||||
 | 
					  ixcrypto/IXHash.cpp
 | 
				
			||||||
 | 
					  ws_receive.cpp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (APPLE AND USE_TLS)
 | 
				
			||||||
 | 
					    target_link_libraries(ws_receive "-framework foundation" "-framework security")
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(ws_receive ixwebsocket)
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_receive/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/ws_receive/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					ws_receive is a simple server upload program. It needs to be used in conjonction with ws_send.
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_receive/ixcrypto
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								examples/ws_receive/ixcrypto
									
									
									
									
									
										Symbolic link
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					../cobra_publisher/ixcrypto
 | 
				
			||||||
							
								
								
									
										29
									
								
								examples/ws_receive/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								examples/ws_receive/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "requires": true,
 | 
				
			||||||
 | 
					  "lockfileVersion": 1,
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "async-limiter": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "base-64": {
 | 
				
			||||||
 | 
					      "version": "0.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "djb2": {
 | 
				
			||||||
 | 
					      "version": "0.0.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/djb2/-/djb2-0.0.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-RAs4kao6uBQrVNRpsXe66p6W5O8="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "ws": {
 | 
				
			||||||
 | 
					      "version": "6.1.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "async-limiter": "1.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										153
									
								
								examples/ws_receive/ws_receive.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								examples/ws_receive/ws_receive.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  ws_receive.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <fstream>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXWebSocketServer.h>
 | 
				
			||||||
 | 
					#include <jsoncpp/json/json.h>
 | 
				
			||||||
 | 
					#include <ixcrypto/IXBase64.h>
 | 
				
			||||||
 | 
					#include <ixcrypto/IXHash.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // We should cleanup the file name and full path further to remove .. as well
 | 
				
			||||||
 | 
					    std::string extractFilename(const std::string& path)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string filename("filename.conf");
 | 
				
			||||||
 | 
					        std::string::size_type idx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        idx = path.rfind('/');
 | 
				
			||||||
 | 
					        if (idx != std::string::npos)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::string filename = path.substr(idx+1);
 | 
				
			||||||
 | 
					            return filename;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return std::string();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void errorHandler(const std::string& errMsg,
 | 
				
			||||||
 | 
					                      const std::string& id,
 | 
				
			||||||
 | 
					                      std::shared_ptr<ix::WebSocket> webSocket)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Json::Value pdu;
 | 
				
			||||||
 | 
					        pdu["kind"] = "error";
 | 
				
			||||||
 | 
					        pdu["id"] = id;
 | 
				
			||||||
 | 
					        pdu["message"] = errMsg;
 | 
				
			||||||
 | 
					        webSocket->send(pdu.toStyledString());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void messageHandler(const std::string& str,
 | 
				
			||||||
 | 
					                        std::shared_ptr<ix::WebSocket> webSocket)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cerr << "Received message: " << str.size() << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Json::Value data;
 | 
				
			||||||
 | 
					        Json::Reader reader;
 | 
				
			||||||
 | 
					        if (!reader.parse(str, data))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            errorHandler("Invalid JSON", std::string(), webSocket);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "id: " << data["id"].asString() << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string content = ix::base64_decode(data["content"].asString());
 | 
				
			||||||
 | 
					        std::cout << "Content size: " << content.size() << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Validate checksum
 | 
				
			||||||
 | 
					        uint64_t cksum = ix::djb2Hash(data["content"].asString());
 | 
				
			||||||
 | 
					        uint64_t cksumRef = data["djb2_hash"].asUInt64();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "Computed hash: " << cksum << std::endl;
 | 
				
			||||||
 | 
					        std::cout << "Reference hash: " << cksumRef << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (cksum != cksumRef)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            errorHandler("Hash mismatch.", std::string(), webSocket);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string filename = data["filename"].asString();
 | 
				
			||||||
 | 
					        filename = extractFilename(filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::ofstream out(filename);
 | 
				
			||||||
 | 
					        out << content;
 | 
				
			||||||
 | 
					        out.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Json::Value pdu;
 | 
				
			||||||
 | 
					        pdu["ack"] = true;
 | 
				
			||||||
 | 
					        pdu["id"] = data["id"];
 | 
				
			||||||
 | 
					        pdu["filename"] = data["filename"];
 | 
				
			||||||
 | 
					        webSocket->send(pdu.toStyledString());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char** argv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int port = 8080;
 | 
				
			||||||
 | 
					    if (argc == 2)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::stringstream ss;
 | 
				
			||||||
 | 
					        ss << argv[1];
 | 
				
			||||||
 | 
					        ss >> port;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ix::WebSocketServer server(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    server.setOnConnectionCallback(
 | 
				
			||||||
 | 
					        [&server](std::shared_ptr<ix::WebSocket> webSocket)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            webSocket->setOnMessageCallback(
 | 
				
			||||||
 | 
					                [webSocket, &server](ix::WebSocketMessageType messageType,
 | 
				
			||||||
 | 
					                                     const std::string& str,
 | 
				
			||||||
 | 
					                                     size_t wireSize,
 | 
				
			||||||
 | 
					                                     const ix::WebSocketErrorInfo& error,
 | 
				
			||||||
 | 
					                                     const ix::WebSocketOpenInfo& openInfo,
 | 
				
			||||||
 | 
					                                     const ix::WebSocketCloseInfo& closeInfo)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (messageType == ix::WebSocket_MessageType_Open)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        std::cerr << "New connection" << std::endl;
 | 
				
			||||||
 | 
					                        std::cerr << "Uri: " << openInfo.uri << std::endl;
 | 
				
			||||||
 | 
					                        std::cerr << "Headers:" << std::endl;
 | 
				
			||||||
 | 
					                        for (auto it : openInfo.headers)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            std::cerr << it.first << ": " << it.second << std::endl;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else if (messageType == ix::WebSocket_MessageType_Close)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        std::cerr << "Closed connection" << std::endl;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else if (messageType == ix::WebSocket_MessageType_Message)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        messageHandler(str, webSocket);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto res = server.listen();
 | 
				
			||||||
 | 
					    if (!res.first)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cerr << res.second << std::endl;
 | 
				
			||||||
 | 
					        return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    server.start();
 | 
				
			||||||
 | 
					    server.wait();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								examples/ws_receive/ws_receive.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								examples/ws_receive/ws_receive.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  ws_receive.js
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const WebSocket = require('ws')
 | 
				
			||||||
 | 
					const djb2 = require('djb2')
 | 
				
			||||||
 | 
					const fs = require('fs')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const wss = new WebSocket.Server({ port: 8080,
 | 
				
			||||||
 | 
					                                   perMessageDeflate: false,
 | 
				
			||||||
 | 
					                                   maxPayload: 1024 * 1024 * 1024 * 1024});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					wss.on('connection', function connection(ws) {
 | 
				
			||||||
 | 
					  ws.on('message', function incoming(data) {
 | 
				
			||||||
 | 
					    console.log('Received message')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let str = data.toString()
 | 
				
			||||||
 | 
					    let obj = JSON.parse(str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(obj.id)
 | 
				
			||||||
 | 
					    console.log(obj.djb2_hash)
 | 
				
			||||||
 | 
					    console.log(djb2(obj.content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var content = Buffer.from(obj.content, 'base64')
 | 
				
			||||||
 | 
					    // let bytes = base64.decode(obj.content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let path = obj.filename
 | 
				
			||||||
 | 
					    fs.writeFile(path, content, function(err) {
 | 
				
			||||||
 | 
					      if (err) {
 | 
				
			||||||
 | 
					        throw err
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        console.log('wrote data to disk')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let response = {
 | 
				
			||||||
 | 
					      id: obj.id
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ws.send(JSON.stringify(response))
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_send/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/ws_send/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					build
 | 
				
			||||||
							
								
								
									
										31
									
								
								examples/ws_send/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								examples/ws_send/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					# Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					# Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cmake_minimum_required (VERSION 3.4.1)
 | 
				
			||||||
 | 
					project (ws_send)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# There's -Weverything too for clang
 | 
				
			||||||
 | 
					set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wshorten-64-to-32")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set (CMAKE_CXX_STANDARD 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					option(USE_TLS "Add TLS support" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_subdirectory(${PROJECT_SOURCE_DIR}/../.. ixwebsocket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					include_directories(ws_send .)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable(ws_send 
 | 
				
			||||||
 | 
					  jsoncpp/jsoncpp.cpp
 | 
				
			||||||
 | 
					  ixcrypto/IXBase64.cpp
 | 
				
			||||||
 | 
					  ixcrypto/IXUuid.cpp
 | 
				
			||||||
 | 
					  ixcrypto/IXHash.cpp
 | 
				
			||||||
 | 
					  ws_send.cpp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (APPLE AND USE_TLS)
 | 
				
			||||||
 | 
					    target_link_libraries(ws_send "-framework foundation" "-framework security")
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(ws_send ixwebsocket)
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_send/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/ws_send/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					ws_send is a simple upload program. It needs to be used in conjonction with ws_receive.
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_send/ixcrypto
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								examples/ws_send/ixcrypto
									
									
									
									
									
										Symbolic link
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					../cobra_publisher/ixcrypto
 | 
				
			||||||
							
								
								
									
										1
									
								
								examples/ws_send/jsoncpp
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								examples/ws_send/jsoncpp
									
									
									
									
									
										Symbolic link
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					../cobra_publisher/jsoncpp
 | 
				
			||||||
							
								
								
									
										306
									
								
								examples/ws_send/ws_send.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								examples/ws_send/ws_send.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  ws_send.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <fstream>
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <condition_variable>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXWebSocket.h>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXSocket.h>
 | 
				
			||||||
 | 
					#include <ixcrypto/IXUuid.h>
 | 
				
			||||||
 | 
					#include <ixcrypto/IXBase64.h>
 | 
				
			||||||
 | 
					#include <ixcrypto/IXHash.h>
 | 
				
			||||||
 | 
					#include <jsoncpp/json/json.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void log(const std::string& msg)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cout << msg << std::endl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class WebSocketSender
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            WebSocketSender(const std::string& _url,
 | 
				
			||||||
 | 
					                            bool enablePerMessageDeflate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            void subscribe(const std::string& channel);
 | 
				
			||||||
 | 
					            void start();
 | 
				
			||||||
 | 
					            void stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            void waitForConnection();
 | 
				
			||||||
 | 
					            void waitForAck();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            void sendMessage(const std::string& filename, bool throttle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            std::string _url;
 | 
				
			||||||
 | 
					            std::string _id;
 | 
				
			||||||
 | 
					            ix::WebSocket _webSocket;
 | 
				
			||||||
 | 
					            bool _enablePerMessageDeflate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            std::mutex _conditionVariableMutex;
 | 
				
			||||||
 | 
					            std::condition_variable _condition;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WebSocketSender::WebSocketSender(const std::string& url,
 | 
				
			||||||
 | 
					                                     bool enablePerMessageDeflate) :
 | 
				
			||||||
 | 
					        _url(url),
 | 
				
			||||||
 | 
					        _enablePerMessageDeflate(enablePerMessageDeflate)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketSender::stop()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _webSocket.stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketSender::waitForConnection()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cout << "Connecting..." << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
				
			||||||
 | 
					        _condition.wait(lock);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketSender::waitForAck()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cout << "Waiting for ack..." << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
				
			||||||
 | 
					        _condition.wait(lock);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string load(const std::string& path)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // std::vector<uint8_t> memblock;
 | 
				
			||||||
 | 
					        std::string str;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::ifstream file(path);
 | 
				
			||||||
 | 
					        if (!file.is_open()) return std::string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        file.seekg(0, file.end);
 | 
				
			||||||
 | 
					        std::streamoff size = file.tellg();
 | 
				
			||||||
 | 
					        file.seekg(0, file.beg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        str.resize(size);
 | 
				
			||||||
 | 
					        file.read((char*)&str.front(), static_cast<std::streamsize>(size));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return str;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketSender::start()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _webSocket.setUrl(_url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ix::WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(
 | 
				
			||||||
 | 
					            _enablePerMessageDeflate, false, false, 15, 15);
 | 
				
			||||||
 | 
					        _webSocket.setPerMessageDeflateOptions(webSocketPerMessageDeflateOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::stringstream ss;
 | 
				
			||||||
 | 
					        log(std::string("Connecting to url: ") + _url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _webSocket.setOnMessageCallback(
 | 
				
			||||||
 | 
					            [this](ix::WebSocketMessageType messageType,
 | 
				
			||||||
 | 
					               const std::string& str,
 | 
				
			||||||
 | 
					               size_t wireSize,
 | 
				
			||||||
 | 
					               const ix::WebSocketErrorInfo& error,
 | 
				
			||||||
 | 
					               const ix::WebSocketOpenInfo& openInfo,
 | 
				
			||||||
 | 
					               const ix::WebSocketCloseInfo& closeInfo)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::stringstream ss;
 | 
				
			||||||
 | 
					                if (messageType == ix::WebSocket_MessageType_Open)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _condition.notify_one();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    log("ws_send: connected");
 | 
				
			||||||
 | 
					                    std::cout << "Uri: " << openInfo.uri << std::endl;
 | 
				
			||||||
 | 
					                    std::cout << "Handshake Headers:" << std::endl;
 | 
				
			||||||
 | 
					                    for (auto it : openInfo.headers)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        std::cout << it.first << ": " << it.second << std::endl;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Close)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "ws_send: connection closed:";
 | 
				
			||||||
 | 
					                    ss << " code " << closeInfo.code;
 | 
				
			||||||
 | 
					                    ss << " reason " << closeInfo.reason << std::endl;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Message)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _condition.notify_one();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ss << "ws_send: received message: "
 | 
				
			||||||
 | 
					                       << str;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Json::Value data;
 | 
				
			||||||
 | 
					                    Json::Reader reader;
 | 
				
			||||||
 | 
					                    if (!reader.parse(str, data))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        std::cerr << "Invalid JSON response" << std::endl;
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    std::string id = data["id"].asString();
 | 
				
			||||||
 | 
					                    if (_id != id)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        std::cerr << "Invalid id" << std::endl;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Error)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Connection error: " << error.reason      << std::endl;
 | 
				
			||||||
 | 
					                    ss << "#retries: "         << error.retries     << std::endl;
 | 
				
			||||||
 | 
					                    ss << "Wait time(ms): "    << error.wait_time   << std::endl;
 | 
				
			||||||
 | 
					                    ss << "HTTP Status: "      << error.http_status << std::endl;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Invalid ix::WebSocketMessageType";
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _webSocket.start();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Bench
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            Bench(const std::string& description) :
 | 
				
			||||||
 | 
					                _description(description),
 | 
				
			||||||
 | 
					                _start(std::chrono::system_clock::now()),
 | 
				
			||||||
 | 
					                _reported(false)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ~Bench()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (!_reported)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    report();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            void report()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                auto now = std::chrono::system_clock::now();
 | 
				
			||||||
 | 
					                auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ms = milliseconds.count();
 | 
				
			||||||
 | 
					                std::cout << _description << " completed in "
 | 
				
			||||||
 | 
					                          << _ms << "ms" << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _reported = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            uint64_t getDuration() const
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return _ms;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            std::string _description;
 | 
				
			||||||
 | 
					            std::chrono::time_point<std::chrono::system_clock> _start;
 | 
				
			||||||
 | 
					            uint64_t _ms;
 | 
				
			||||||
 | 
					            bool _reported;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketSender::sendMessage(const std::string& filename,
 | 
				
			||||||
 | 
					                                      bool throttle)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string content;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Bench bench("load file from disk");
 | 
				
			||||||
 | 
					            content = load(filename);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _id = uuid4();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string b64Content;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Bench bench("base 64 encode file");
 | 
				
			||||||
 | 
					            b64Content = base64_encode(content, content.size());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Json::Value pdu;
 | 
				
			||||||
 | 
					        pdu["kind"] = "send";
 | 
				
			||||||
 | 
					        pdu["id"] = _id;
 | 
				
			||||||
 | 
					        pdu["content"] = b64Content;
 | 
				
			||||||
 | 
					        pdu["djb2_hash"] = djb2Hash(b64Content);
 | 
				
			||||||
 | 
					        pdu["filename"] = filename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Bench bench("Sending file through websocket");
 | 
				
			||||||
 | 
					        _webSocket.send(pdu.toStyledString(),
 | 
				
			||||||
 | 
					                        [throttle](int current, int total) -> bool
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::cout << "Step " << current << " out of " << total << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (throttle)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::chrono::duration<double, std::milli> duration(10);
 | 
				
			||||||
 | 
					                std::this_thread::sleep_for(duration);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bench.report();
 | 
				
			||||||
 | 
					        auto duration = bench.getDuration();
 | 
				
			||||||
 | 
					        auto transferRate = 1000 * b64Content.size() / duration;
 | 
				
			||||||
 | 
					        transferRate /= (1024 * 1024);
 | 
				
			||||||
 | 
					        std::cout << "Send transfer rate: " << transferRate << "MB/s" << std::endl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void wsSend(const std::string& url,
 | 
				
			||||||
 | 
					                const std::string& path,
 | 
				
			||||||
 | 
					                bool enablePerMessageDeflate,
 | 
				
			||||||
 | 
					                bool throttle)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        WebSocketSender webSocketSender(url, enablePerMessageDeflate);
 | 
				
			||||||
 | 
					        webSocketSender.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webSocketSender.waitForConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "Sending..." << std::endl;
 | 
				
			||||||
 | 
					        webSocketSender.sendMessage(path, throttle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webSocketSender.waitForAck();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "Done !" << std::endl;
 | 
				
			||||||
 | 
					        webSocketSender.stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char** argv)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (argc != 3)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::cerr << "Usage: ws_send <url> <path>" << std::endl;
 | 
				
			||||||
 | 
					        return 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    std::string url = argv[1];
 | 
				
			||||||
 | 
					    std::string path = argv[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool throttle = false;
 | 
				
			||||||
 | 
					    bool enablePerMessageDeflate = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Socket::init();
 | 
				
			||||||
 | 
					    wsSend(url, path, enablePerMessageDeflate, throttle);
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,10 +5,8 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "IXDNSLookup.h"
 | 
					#include "IXDNSLookup.h"
 | 
				
			||||||
 | 
					#include "IXNetSystem.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <sys/types.h>
 | 
					 | 
				
			||||||
#include <sys/socket.h>
 | 
					 | 
				
			||||||
#include <netdb.h>
 | 
					 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <chrono>
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,7 +105,7 @@ namespace ix
 | 
				
			|||||||
        // Good resource on thread forced termination
 | 
					        // Good resource on thread forced termination
 | 
				
			||||||
        // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
 | 
					        // https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
 | 
				
			||||||
        //
 | 
					        //
 | 
				
			||||||
        _thread = std::thread(&DNSLookup::run, this);
 | 
					        _thread = std::thread(&DNSLookup::run, this, _id, _hostname, _port);
 | 
				
			||||||
        _thread.detach();
 | 
					        _thread.detach();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
					        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
				
			||||||
@@ -140,11 +138,13 @@ namespace ix
 | 
				
			|||||||
        return _res;
 | 
					        return _res;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void DNSLookup::run()
 | 
					    void DNSLookup::run(uint64_t id, const std::string& hostname, int port) // thread runner
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        uint64_t id = _id;
 | 
					        // We don't want to read or write into members variables of an object that could be
 | 
				
			||||||
 | 
					        // gone, so we use temporary variables (res) or we pass in by copy everything that
 | 
				
			||||||
 | 
					        // getAddrInfo needs to work.
 | 
				
			||||||
        std::string errMsg;
 | 
					        std::string errMsg;
 | 
				
			||||||
        _res = getAddrInfo(_hostname, _port, errMsg);
 | 
					        struct addrinfo* res = getAddrInfo(hostname, port, errMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if this isn't an active job, and the control thread is gone
 | 
					        // if this isn't an active job, and the control thread is gone
 | 
				
			||||||
        // there is not thing to do, and we don't want to touch the defunct
 | 
					        // there is not thing to do, and we don't want to touch the defunct
 | 
				
			||||||
@@ -155,9 +155,10 @@ namespace ix
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Copy result into the member variables
 | 
				
			||||||
 | 
					        _res = res;
 | 
				
			||||||
        _errMsg = errMsg;
 | 
					        _errMsg = errMsg;
 | 
				
			||||||
        _condition.notify_one();
 | 
					        _condition.notify_one();
 | 
				
			||||||
        _done = true;
 | 
					        _done = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ namespace ix
 | 
				
			|||||||
                                            int port,
 | 
					                                            int port,
 | 
				
			||||||
                                            std::string& errMsg);
 | 
					                                            std::string& errMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void run(); // thread runner
 | 
					        void run(uint64_t id, const std::string& hostname, int port); // thread runner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::string _hostname;
 | 
					        std::string _hostname;
 | 
				
			||||||
        int _port;
 | 
					        int _port;
 | 
				
			||||||
@@ -56,7 +56,7 @@ namespace ix
 | 
				
			|||||||
        std::condition_variable _condition;
 | 
					        std::condition_variable _condition;
 | 
				
			||||||
        std::mutex _conditionVariableMutex;
 | 
					        std::mutex _conditionVariableMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::atomic<uint64_t> _id;
 | 
					        uint64_t _id;
 | 
				
			||||||
        static std::atomic<uint64_t> _nextId;
 | 
					        static std::atomic<uint64_t> _nextId;
 | 
				
			||||||
        static std::set<uint64_t> _activeJobs;
 | 
					        static std::set<uint64_t> _activeJobs;
 | 
				
			||||||
        static std::mutex _activeJobsMutex;
 | 
					        static std::mutex _activeJobsMutex;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,8 @@
 | 
				
			|||||||
// cf Android/Kernel table here
 | 
					// cf Android/Kernel table here
 | 
				
			||||||
// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
 | 
					// https://android.stackexchange.com/questions/51651/which-android-runs-which-linux-kernel
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					// On macOS we use UNIX pipes to wake up select.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "IXEventFd.h"
 | 
					#include "IXEventFd.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,17 +26,24 @@
 | 
				
			|||||||
# include <sys/eventfd.h>
 | 
					# include <sys/eventfd.h>
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef _WIN32
 | 
					 | 
				
			||||||
#include <unistd.h> // for write
 | 
					#include <unistd.h> // for write
 | 
				
			||||||
#endif
 | 
					#include <fcntl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    EventFd::EventFd() : 
 | 
					    EventFd::EventFd()
 | 
				
			||||||
        _eventfd(-1)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
#ifdef __linux__
 | 
					#ifdef __linux__
 | 
				
			||||||
 | 
					        _eventfd = -1;
 | 
				
			||||||
        _eventfd = eventfd(0, 0);
 | 
					        _eventfd = eventfd(0, 0);
 | 
				
			||||||
 | 
					        fcntl(_eventfd, F_SETFL, O_NONBLOCK);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        _fildes[0] = -1;
 | 
				
			||||||
 | 
					        _fildes[1] = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pipe(_fildes);
 | 
				
			||||||
 | 
					        fcntl(_fildes[0], F_SETFL, O_NONBLOCK);
 | 
				
			||||||
 | 
					        fcntl(_fildes[1], F_SETFL, O_NONBLOCK);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,22 +51,44 @@ namespace ix
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
#ifdef __linux__
 | 
					#ifdef __linux__
 | 
				
			||||||
        ::close(_eventfd);
 | 
					        ::close(_eventfd);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        ::close(_fildes[0]);
 | 
				
			||||||
 | 
					        ::close(_fildes[1]);
 | 
				
			||||||
 | 
					        _fildes[0] = -1;
 | 
				
			||||||
 | 
					        _fildes[1] = -1;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool EventFd::notify()
 | 
					    bool EventFd::notify(uint64_t value)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
#if defined(__linux__)
 | 
					        int fd;
 | 
				
			||||||
        if (_eventfd == -1) return false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // select will wake up when a non-zero value is written to our eventfd
 | 
					#if defined(__linux__)
 | 
				
			||||||
        uint64_t value = 1;
 | 
					        fd = _eventfd;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        // File descriptor at index 1 in _fildes is the write end of the pipe
 | 
				
			||||||
 | 
					        fd = _fildes[1];
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fd == -1) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // we should write 8 bytes for an uint64_t
 | 
					        // we should write 8 bytes for an uint64_t
 | 
				
			||||||
        return write(_eventfd, &value, sizeof(value)) == 8;
 | 
					        return write(fd, &value, sizeof(value)) == 8;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: return max uint64_t for errors ?
 | 
				
			||||||
 | 
					    uint64_t EventFd::read()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int fd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(__linux__)
 | 
				
			||||||
 | 
					        fd = _eventfd;
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        return true;
 | 
					        fd = _fildes[0];
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					        uint64_t value = 0;
 | 
				
			||||||
 | 
					        ::read(fd, &value, sizeof(value));
 | 
				
			||||||
 | 
					        return value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool EventFd::clear()
 | 
					    bool EventFd::clear()
 | 
				
			||||||
@@ -77,6 +108,10 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    int EventFd::getFd()
 | 
					    int EventFd::getFd()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					#if defined(__linux__)
 | 
				
			||||||
        return _eventfd;
 | 
					        return _eventfd;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        return _fildes[0];
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <stdint.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    class EventFd {
 | 
					    class EventFd {
 | 
				
			||||||
@@ -13,11 +15,19 @@ namespace ix
 | 
				
			|||||||
        EventFd();
 | 
					        EventFd();
 | 
				
			||||||
        virtual ~EventFd();
 | 
					        virtual ~EventFd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool notify();
 | 
					        bool notify(uint64_t value);
 | 
				
			||||||
        bool clear();
 | 
					        bool clear();
 | 
				
			||||||
 | 
					        uint64_t read();
 | 
				
			||||||
        int getFd();
 | 
					        int getFd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
 | 
					#if defined(__linux__)
 | 
				
			||||||
        int _eventfd;
 | 
					        int _eventfd;
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					        // 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];
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										467
									
								
								ixwebsocket/IXHttpClient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										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;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								ixwebsocket/IXNetSystem.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								ixwebsocket/IXNetSystem.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXNetSystem.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2019 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
					# include <WS2tcpip.h>
 | 
				
			||||||
 | 
					# include <WinSock2.h>
 | 
				
			||||||
 | 
					# include <basetsd.h>
 | 
				
			||||||
 | 
					# include <io.h>
 | 
				
			||||||
 | 
					# include <ws2def.h>
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					# include <arpa/inet.h>
 | 
				
			||||||
 | 
					# include <errno.h>
 | 
				
			||||||
 | 
					# include <netdb.h>
 | 
				
			||||||
 | 
					# include <netinet/tcp.h>
 | 
				
			||||||
 | 
					# include <sys/select.h>
 | 
				
			||||||
 | 
					# include <sys/socket.h>
 | 
				
			||||||
 | 
					# include <sys/stat.h>
 | 
				
			||||||
 | 
					# include <sys/time.h>
 | 
				
			||||||
 | 
					# include <unistd.h>
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										14
									
								
								ixwebsocket/IXProgressCallback.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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)>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 *  IXSetThreadName.cpp
 | 
					 | 
				
			||||||
 *  Author: Benjamin Sergeant
 | 
					 | 
				
			||||||
 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
#include "IXSetThreadName.h"
 | 
					 | 
				
			||||||
#include <pthread.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace ix
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    void setThreadName(const std::string& name)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
#if defined(__linux__)
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // Linux only reserve 16 bytes for its thread names
 | 
					 | 
				
			||||||
        // See prctl and PR_SET_NAME property in
 | 
					 | 
				
			||||||
        // http://man7.org/linux/man-pages/man2/prctl.2.html
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        pthread_setname_np(pthread_self(),
 | 
					 | 
				
			||||||
                           name.substr(0, 15).c_str());
 | 
					 | 
				
			||||||
#elif defined(__APPLE__)
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // Apple is more generous with 64 chars.
 | 
					 | 
				
			||||||
        // notice how the Apple version does not take a pthread_t argument
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        pthread_setname_np(name.substr(0, 63).c_str());
 | 
					 | 
				
			||||||
#elif
 | 
					 | 
				
			||||||
        #error("Unsupported platform");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -6,23 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXSocket.h"
 | 
					#include "IXSocket.h"
 | 
				
			||||||
#include "IXSocketConnect.h"
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
 | 
					#include "IXNetSystem.h"
 | 
				
			||||||
#ifdef _WIN32
 | 
					 | 
				
			||||||
# include <basetsd.h>
 | 
					 | 
				
			||||||
# include <WinSock2.h>
 | 
					 | 
				
			||||||
# include <ws2def.h>
 | 
					 | 
				
			||||||
# include <WS2tcpip.h>
 | 
					 | 
				
			||||||
# include <io.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
# include <unistd.h>
 | 
					 | 
				
			||||||
# include <errno.h>
 | 
					 | 
				
			||||||
# include <netdb.h>
 | 
					 | 
				
			||||||
# include <netinet/tcp.h>
 | 
					 | 
				
			||||||
# include <sys/socket.h>
 | 
					 | 
				
			||||||
# include <sys/time.h>
 | 
					 | 
				
			||||||
# include <sys/select.h>
 | 
					 | 
				
			||||||
# include <sys/stat.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
@@ -37,10 +21,16 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    const int Socket::kDefaultPollNoTimeout = -1; // No poll timeout by default
 | 
				
			||||||
 | 
					    const int Socket::kDefaultPollTimeout = kDefaultPollNoTimeout;
 | 
				
			||||||
 | 
					    const uint8_t Socket::kSendRequest = 1;
 | 
				
			||||||
 | 
					    const uint8_t Socket::kCloseRequest = 2;
 | 
				
			||||||
 | 
					    constexpr size_t Socket::kChunkSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Socket::Socket(int fd) :
 | 
					    Socket::Socket(int fd) :
 | 
				
			||||||
        _sockfd(fd)
 | 
					        _sockfd(fd)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        ;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Socket::~Socket()
 | 
					    Socket::~Socket()
 | 
				
			||||||
@@ -48,33 +38,73 @@ namespace ix
 | 
				
			|||||||
        close();
 | 
					        close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void Socket::poll(const OnPollCallback& onPollCallback)
 | 
					    void Socket::poll(const OnPollCallback& onPollCallback, int timeoutSecs)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (_sockfd == -1)
 | 
					        if (_sockfd == -1)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            onPollCallback();
 | 
					            if (onPollCallback) onPollCallback(PollResultType_Error);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PollResultType pollResult = select(timeoutSecs, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (onPollCallback) onPollCallback(pollResult);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PollResultType Socket::select(int timeoutSecs, int timeoutMs)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        fd_set rfds;
 | 
					        fd_set rfds;
 | 
				
			||||||
        FD_ZERO(&rfds);
 | 
					        FD_ZERO(&rfds);
 | 
				
			||||||
        FD_SET(_sockfd, &rfds);
 | 
					        FD_SET(_sockfd, &rfds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef __linux__
 | 
					        // File descriptor at index 0 in _fildes is the read end of the pipe
 | 
				
			||||||
        FD_SET(_eventfd.getFd(), &rfds);
 | 
					        int eventfd = _eventfd.getFd();
 | 
				
			||||||
#endif
 | 
					        if (eventfd != -1)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
        int sockfd = _sockfd;
 | 
					            FD_SET(eventfd, &rfds);
 | 
				
			||||||
        int nfds = (std::max)(sockfd, _eventfd.getFd());
 | 
					 | 
				
			||||||
        select(nfds + 1, &rfds, nullptr, nullptr, nullptr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onPollCallback();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void Socket::wakeUpFromPoll()
 | 
					        struct timeval timeout;
 | 
				
			||||||
 | 
					        timeout.tv_sec = timeoutSecs;
 | 
				
			||||||
 | 
					        timeout.tv_usec = 1000 * timeoutMs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Compute the highest fd.
 | 
				
			||||||
 | 
					        int sockfd = _sockfd;
 | 
				
			||||||
 | 
					        int nfds = (std::max)(sockfd, eventfd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int ret = ::select(nfds + 1, &rfds, nullptr, nullptr,
 | 
				
			||||||
 | 
					                           (timeoutSecs < 0) ? nullptr : &timeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        PollResultType pollResult = PollResultType_ReadyForRead;
 | 
				
			||||||
 | 
					        if (ret < 0)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
        // this will wake up the thread blocked on select, only needed on Linux
 | 
					            pollResult = PollResultType_Error;
 | 
				
			||||||
        _eventfd.notify();
 | 
					        }
 | 
				
			||||||
 | 
					        else if (ret == 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            pollResult = PollResultType_Timeout;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (eventfd != -1 && FD_ISSET(eventfd, &rfds))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            uint8_t value = _eventfd.read();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (value == kSendRequest)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                pollResult = PollResultType_SendRequest;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else if (value == kCloseRequest)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                pollResult = PollResultType_CloseRequest;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return pollResult;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Wake up from poll/select by writing to the pipe which is watched by select
 | 
				
			||||||
 | 
					    bool Socket::wakeUpFromPoll(uint8_t wakeUpCode)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return _eventfd.notify(wakeUpCode);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool Socket::connect(const std::string& host,
 | 
					    bool Socket::connect(const std::string& host,
 | 
				
			||||||
@@ -100,32 +130,32 @@ namespace ix
 | 
				
			|||||||
        _sockfd = -1;
 | 
					        _sockfd = -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int Socket::send(char* buffer, size_t length)
 | 
					    ssize_t Socket::send(char* buffer, size_t length)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        int flags = 0;
 | 
					        int flags = 0;
 | 
				
			||||||
#ifdef MSG_NOSIGNAL
 | 
					#ifdef MSG_NOSIGNAL
 | 
				
			||||||
        flags = MSG_NOSIGNAL;
 | 
					        flags = MSG_NOSIGNAL;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (int) ::send(_sockfd, buffer, length, flags);
 | 
					        return ::send(_sockfd, buffer, length, flags);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int Socket::send(const std::string& buffer)
 | 
					    ssize_t Socket::send(const std::string& buffer)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return send((char*)&buffer[0], buffer.size());
 | 
					        return send((char*)&buffer[0], buffer.size());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int Socket::recv(void* buffer, size_t length)
 | 
					    ssize_t Socket::recv(void* buffer, size_t length)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        int flags = 0;
 | 
					        int flags = 0;
 | 
				
			||||||
#ifdef MSG_NOSIGNAL
 | 
					#ifdef MSG_NOSIGNAL
 | 
				
			||||||
        flags = MSG_NOSIGNAL;
 | 
					        flags = MSG_NOSIGNAL;
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (int) ::recv(_sockfd, (char*) buffer, length, flags);
 | 
					        return ::recv(_sockfd, (char*) buffer, length, flags);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int Socket::getErrno() const
 | 
					    int Socket::getErrno()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
#ifdef _WIN32
 | 
					#ifdef _WIN32
 | 
				
			||||||
        return WSAGetLastError();
 | 
					        return WSAGetLastError();
 | 
				
			||||||
@@ -163,46 +193,6 @@ namespace ix
 | 
				
			|||||||
#endif
 | 
					#endif
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool Socket::readByte(void* buffer,
 | 
					 | 
				
			||||||
                          const CancellationRequest& isCancellationRequested)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        while (true)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (isCancellationRequested()) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            int 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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                FD_ZERO(&rfds);
 | 
					 | 
				
			||||||
                FD_SET(_sockfd, &rfds);
 | 
					 | 
				
			||||||
                select(_sockfd + 1, &rfds, nullptr, nullptr, &timeout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            // There was an error during the read, abort
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool Socket::writeBytes(const std::string& str,
 | 
					    bool Socket::writeBytes(const std::string& str,
 | 
				
			||||||
                            const CancellationRequest& isCancellationRequested)
 | 
					                            const CancellationRequest& isCancellationRequested)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -213,7 +203,7 @@ namespace ix
 | 
				
			|||||||
            char* buffer = const_cast<char*>(str.c_str());
 | 
					            char* buffer = const_cast<char*>(str.c_str());
 | 
				
			||||||
            int len = (int) str.size();
 | 
					            int len = (int) str.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            int ret = send(buffer, len);
 | 
					            ssize_t ret = send(buffer, len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // We wrote some bytes, as needed, all good.
 | 
					            // We wrote some bytes, as needed, all good.
 | 
				
			||||||
            if (ret > 0)
 | 
					            if (ret > 0)
 | 
				
			||||||
@@ -234,7 +224,43 @@ namespace ix
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::pair<bool, std::string> Socket::readLine(const CancellationRequest& isCancellationRequested)
 | 
					    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 ready to read.
 | 
				
			||||||
 | 
					                // This way we are not busy looping
 | 
				
			||||||
 | 
					                int res = select(0, 1);
 | 
				
			||||||
 | 
					                if (res < 0 && (errno == EBADF || errno == EINVAL))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // There was an error during the read, abort
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::pair<bool, std::string> Socket::readLine(
 | 
				
			||||||
 | 
					        const CancellationRequest& isCancellationRequested)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        char c;
 | 
					        char c;
 | 
				
			||||||
        std::string line;
 | 
					        std::string line;
 | 
				
			||||||
@@ -244,7 +270,8 @@ namespace ix
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!readByte(&c, isCancellationRequested))
 | 
					            if (!readByte(&c, isCancellationRequested))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return std::make_pair(false, std::string());
 | 
					                // Return what we were able to read
 | 
				
			||||||
 | 
					                return std::make_pair(false, line);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            line += c;
 | 
					            line += c;
 | 
				
			||||||
@@ -252,4 +279,46 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return std::make_pair(true, line);
 | 
					        return std::make_pair(true, line);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::pair<bool, std::string> Socket::readBytes(
 | 
				
			||||||
 | 
					        size_t length,
 | 
				
			||||||
 | 
					        const OnProgressCallback& onProgressCallback,
 | 
				
			||||||
 | 
					        const CancellationRequest& isCancellationRequested)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (_readBuffer.empty())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _readBuffer.resize(kChunkSize);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::vector<uint8_t> output;
 | 
				
			||||||
 | 
					        while (output.size() != length)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (isCancellationRequested()) return std::make_pair(false, std::string());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int 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 timeout until something is ready to read.
 | 
				
			||||||
 | 
					            // This way we are not busy looping
 | 
				
			||||||
 | 
					            select(0, 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return std::make_pair(true, std::string(output.begin(),
 | 
				
			||||||
 | 
					                                                output.end()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,23 +10,41 @@
 | 
				
			|||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
#include <mutex>
 | 
					#include <mutex>
 | 
				
			||||||
#include <atomic>
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
					#include <BaseTsd.h>
 | 
				
			||||||
 | 
					typedef SSIZE_T ssize_t;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "IXEventFd.h"
 | 
					 | 
				
			||||||
#include "IXCancellationRequest.h"
 | 
					#include "IXCancellationRequest.h"
 | 
				
			||||||
 | 
					#include "IXProgressCallback.h"
 | 
				
			||||||
 | 
					#include "IXEventFd.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    enum PollResultType
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        PollResultType_ReadyForRead = 0,
 | 
				
			||||||
 | 
					        PollResultType_Timeout = 1,
 | 
				
			||||||
 | 
					        PollResultType_Error = 2,
 | 
				
			||||||
 | 
					        PollResultType_SendRequest = 3,
 | 
				
			||||||
 | 
					        PollResultType_CloseRequest = 4
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Socket {
 | 
					    class Socket {
 | 
				
			||||||
    public:
 | 
					    public:
 | 
				
			||||||
        using OnPollCallback = std::function<void()>;
 | 
					        using OnPollCallback = std::function<void(PollResultType)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Socket(int fd = -1);
 | 
					        Socket(int fd = -1);
 | 
				
			||||||
        virtual ~Socket();
 | 
					        virtual ~Socket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void configure();
 | 
					        void configure();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        virtual void poll(const OnPollCallback& onPollCallback);
 | 
					        PollResultType select(int timeoutSecs, int timeoutMs);
 | 
				
			||||||
        virtual void wakeUpFromPoll();
 | 
					        virtual void poll(const OnPollCallback& onPollCallback,
 | 
				
			||||||
 | 
					                          int timeoutSecs = kDefaultPollTimeout);
 | 
				
			||||||
 | 
					        virtual bool wakeUpFromPoll(uint8_t wakeUpCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Virtual methods
 | 
					        // Virtual methods
 | 
				
			||||||
        virtual bool connect(const std::string& url,
 | 
					        virtual bool connect(const std::string& url,
 | 
				
			||||||
@@ -35,9 +53,9 @@ namespace ix
 | 
				
			|||||||
                             const CancellationRequest& isCancellationRequested);
 | 
					                             const CancellationRequest& isCancellationRequested);
 | 
				
			||||||
        virtual void close();
 | 
					        virtual void close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        virtual int send(char* buffer, size_t length);
 | 
					        virtual ssize_t send(char* buffer, size_t length);
 | 
				
			||||||
        virtual int send(const std::string& buffer);
 | 
					        virtual ssize_t send(const std::string& buffer);
 | 
				
			||||||
        virtual int recv(void* buffer, size_t length);
 | 
					        virtual ssize_t recv(void* buffer, size_t length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Blocking and cancellable versions, working with socket that can be set
 | 
					        // Blocking and cancellable versions, working with socket that can be set
 | 
				
			||||||
        // to non blocking mode. Used during HTTP upgrade.
 | 
					        // to non blocking mode. Used during HTTP upgrade.
 | 
				
			||||||
@@ -45,17 +63,36 @@ namespace ix
 | 
				
			|||||||
                      const CancellationRequest& isCancellationRequested);
 | 
					                      const CancellationRequest& isCancellationRequested);
 | 
				
			||||||
        bool writeBytes(const std::string& str,
 | 
					        bool writeBytes(const std::string& str,
 | 
				
			||||||
                        const CancellationRequest& isCancellationRequested);
 | 
					                        const CancellationRequest& isCancellationRequested);
 | 
				
			||||||
        std::pair<bool, std::string> readLine(const CancellationRequest& isCancellationRequested);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int getErrno() const;
 | 
					        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 bool init(); // Required on Windows to initialize WinSocket
 | 
				
			||||||
        static void cleanup(); // Required on Windows to cleanup WinSocket
 | 
					        static void cleanup(); // Required on Windows to cleanup WinSocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Used as special codes for pipe communication
 | 
				
			||||||
 | 
					        static const uint8_t kSendRequest;
 | 
				
			||||||
 | 
					        static const uint8_t kCloseRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected:
 | 
					    protected:
 | 
				
			||||||
        void closeSocket(int fd);
 | 
					        void closeSocket(int fd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::atomic<int> _sockfd;
 | 
					        std::atomic<int> _sockfd;
 | 
				
			||||||
        std::mutex _socketMutex;
 | 
					        std::mutex _socketMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        EventFd _eventfd;
 | 
					        EventFd _eventfd;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -203,7 +203,7 @@ namespace ix
 | 
				
			|||||||
        Socket::close();
 | 
					        Socket::close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int SocketAppleSSL::send(char* buf, size_t nbyte)
 | 
					    ssize_t SocketAppleSSL::send(char* buf, size_t nbyte)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ssize_t ret = 0;
 | 
					        ssize_t ret = 0;
 | 
				
			||||||
        OSStatus status;
 | 
					        OSStatus status;
 | 
				
			||||||
@@ -218,16 +218,16 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (ret == 0 && errSSLClosedAbort != status)
 | 
					        if (ret == 0 && errSSLClosedAbort != status)
 | 
				
			||||||
            ret = -1;
 | 
					            ret = -1;
 | 
				
			||||||
        return (int) ret;
 | 
					        return ret;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int SocketAppleSSL::send(const std::string& buffer)
 | 
					    ssize_t SocketAppleSSL::send(const std::string& buffer)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return send((char*)&buffer[0], buffer.size());
 | 
					        return send((char*)&buffer[0], buffer.size());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // No wait support
 | 
					    // No wait support
 | 
				
			||||||
    int SocketAppleSSL::recv(void* buf, size_t nbyte)
 | 
					    ssize_t SocketAppleSSL::recv(void* buf, size_t nbyte)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        OSStatus status = errSSLWouldBlock;
 | 
					        OSStatus status = errSSLWouldBlock;
 | 
				
			||||||
        while (errSSLWouldBlock == status)
 | 
					        while (errSSLWouldBlock == status)
 | 
				
			||||||
@@ -237,7 +237,7 @@ namespace ix
 | 
				
			|||||||
            status = SSLRead(_sslContext, buf, nbyte, &processed);
 | 
					            status = SSLRead(_sslContext, buf, nbyte, &processed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (processed > 0)
 | 
					            if (processed > 0)
 | 
				
			||||||
                return (int) processed;
 | 
					                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
 | 
					            // Socket should close
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,9 +28,9 @@ namespace ix
 | 
				
			|||||||
                             const CancellationRequest& isCancellationRequested) final;
 | 
					                             const CancellationRequest& isCancellationRequested) final;
 | 
				
			||||||
        virtual void close() final;
 | 
					        virtual void close() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        virtual int send(char* buffer, size_t length) final;
 | 
					        virtual ssize_t send(char* buffer, size_t length) final;
 | 
				
			||||||
        virtual int send(const std::string& buffer) final;
 | 
					        virtual ssize_t send(const std::string& buffer) final;
 | 
				
			||||||
        virtual int recv(void* buffer, size_t length) final;
 | 
					        virtual ssize_t recv(void* buffer, size_t length) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        SSLContextRef _sslContext;
 | 
					        SSLContextRef _sslContext;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,23 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXSocketConnect.h"
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
#include "IXDNSLookup.h"
 | 
					#include "IXDNSLookup.h"
 | 
				
			||||||
 | 
					#include "IXNetSystem.h"
 | 
				
			||||||
#ifdef _WIN32
 | 
					 | 
				
			||||||
# include <basetsd.h>
 | 
					 | 
				
			||||||
# include <WinSock2.h>
 | 
					 | 
				
			||||||
# include <ws2def.h>
 | 
					 | 
				
			||||||
# include <WS2tcpip.h>
 | 
					 | 
				
			||||||
# include <io.h>
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
# include <unistd.h>
 | 
					 | 
				
			||||||
# include <errno.h>
 | 
					 | 
				
			||||||
# include <netdb.h>
 | 
					 | 
				
			||||||
# include <netinet/tcp.h>
 | 
					 | 
				
			||||||
# include <sys/socket.h>
 | 
					 | 
				
			||||||
# include <sys/time.h>
 | 
					 | 
				
			||||||
# include <sys/select.h>
 | 
					 | 
				
			||||||
# include <sys/stat.h>
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <fcntl.h>
 | 
					#include <fcntl.h>
 | 
				
			||||||
@@ -86,31 +70,45 @@ namespace ix
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                closeSocket(fd);
 | 
					                closeSocket(fd);
 | 
				
			||||||
                errMsg = "Cancelled";
 | 
					                errMsg = "Cancelled";
 | 
				
			||||||
                return false;
 | 
					                return -1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            fd_set wfds;
 | 
					            // Use select to check the status of the new connection
 | 
				
			||||||
            FD_ZERO(&wfds);
 | 
					 | 
				
			||||||
            FD_SET(fd, &wfds);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 50ms select timeout
 | 
					 | 
				
			||||||
            struct timeval timeout;
 | 
					            struct timeval timeout;
 | 
				
			||||||
            timeout.tv_sec = 0;
 | 
					            timeout.tv_sec = 0;
 | 
				
			||||||
            timeout.tv_usec = 50 * 1000;
 | 
					            timeout.tv_usec = 10 * 1000; // 10ms timeout
 | 
				
			||||||
 | 
					            fd_set wfds;
 | 
				
			||||||
 | 
					            fd_set efds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            select(fd + 1, nullptr, &wfds, nullptr, &timeout);
 | 
					            FD_ZERO(&wfds);
 | 
				
			||||||
 | 
					            FD_SET(fd, &wfds);
 | 
				
			||||||
 | 
					            FD_ZERO(&efds);
 | 
				
			||||||
 | 
					            FD_SET(fd, &efds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (select(fd + 1, nullptr, &wfds, &efds, &timeout) < 0 &&
 | 
				
			||||||
 | 
					                (errno == EBADF || errno == EINVAL))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                closeSocket(fd);
 | 
				
			||||||
 | 
					                errMsg = std::string("Connect error, select error: ") + strerror(errno);
 | 
				
			||||||
 | 
					                return -1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Nothing was written to the socket, wait again.
 | 
					            // Nothing was written to the socket, wait again.
 | 
				
			||||||
            if (!FD_ISSET(fd, &wfds)) continue;
 | 
					            if (!FD_ISSET(fd, &wfds)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Something was written to the socket
 | 
					            // Something was written to the socket. Check for errors.
 | 
				
			||||||
            int optval = -1;
 | 
					            int optval = -1;
 | 
				
			||||||
            socklen_t optlen = sizeof(optval);
 | 
					            socklen_t optlen = sizeof(optval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef _WIN32
 | 
				
			||||||
 | 
					            // On connect error, in async mode, windows will write to the exceptions fds
 | 
				
			||||||
 | 
					            if (FD_ISSET(fd, &efds))
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
            // getsockopt() puts the errno value for connect into optval so 0
 | 
					            // getsockopt() puts the errno value for connect into optval so 0
 | 
				
			||||||
            // means no-error.
 | 
					            // means no-error.
 | 
				
			||||||
            if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
 | 
					            if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1 ||
 | 
				
			||||||
                optval != 0)
 | 
					                optval != 0)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                closeSocket(fd);
 | 
					                closeSocket(fd);
 | 
				
			||||||
                errMsg = strerror(optval);
 | 
					                errMsg = strerror(optval);
 | 
				
			||||||
@@ -173,7 +171,7 @@ namespace ix
 | 
				
			|||||||
        // 2. make socket non blocking
 | 
					        // 2. make socket non blocking
 | 
				
			||||||
#ifdef _WIN32
 | 
					#ifdef _WIN32
 | 
				
			||||||
        unsigned long nonblocking = 1;
 | 
					        unsigned long nonblocking = 1;
 | 
				
			||||||
        ioctlsocket(_sockfd, FIONBIO, &nonblocking);
 | 
					        ioctlsocket(sockfd, FIONBIO, &nonblocking);
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
 | 
					        fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								ixwebsocket/IXSocketFactory.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								ixwebsocket/IXSocketFactory.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!tls)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return std::make_shared<Socket>();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					#ifdef IXWEBSOCKET_USE_TLS
 | 
				
			||||||
 | 
					# ifdef __APPLE__
 | 
				
			||||||
 | 
					            return std::make_shared<SocketAppleSSL>();
 | 
				
			||||||
 | 
					# else
 | 
				
			||||||
 | 
					            return std::make_shared<SocketOpenSSL>();
 | 
				
			||||||
 | 
					# endif
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					            errorMsg = "TLS support is not enabled on this platform.";
 | 
				
			||||||
 | 
					            return nullptr;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								ixwebsocket/IXSocketFactory.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ixwebsocket/IXSocketFactory.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,30 +18,26 @@
 | 
				
			|||||||
#include <errno.h>
 | 
					#include <errno.h>
 | 
				
			||||||
#define socketerrno errno
 | 
					#define socketerrno errno
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace {
 | 
					namespace ix
 | 
				
			||||||
 | 
					 | 
				
			||||||
std::mutex initMutex;
 | 
					 | 
				
			||||||
bool openSSLInitialized = false;
 | 
					 | 
				
			||||||
bool openSSLInitializationSuccessful = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool openSSLInitialize(std::string& errMsg)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    std::lock_guard<std::mutex> lock(initMutex);
 | 
					    std::atomic<bool> SocketOpenSSL::_openSSLInitializationSuccessful(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (openSSLInitialized)
 | 
					    SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
 | 
				
			||||||
 | 
					        _ssl_connection(nullptr),
 | 
				
			||||||
 | 
					        _ssl_context(nullptr)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return openSSLInitializationSuccessful;
 | 
					        std::call_once(_openSSLInitFlag, &SocketOpenSSL::openSSLInitialize, this);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SocketOpenSSL::~SocketOpenSSL()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        SocketOpenSSL::close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketOpenSSL::openSSLInitialize()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
 | 
					#if OPENSSL_VERSION_NUMBER >= 0x10100000L
 | 
				
			||||||
    if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr))
 | 
					        if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, nullptr)) return;
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        errMsg = "OPENSSL_init_ssl failure";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        openSSLInitializationSuccessful = false;
 | 
					 | 
				
			||||||
        openSSLInitialized = true;
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        (void) OPENSSL_config(nullptr);
 | 
					        (void) OPENSSL_config(nullptr);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
@@ -49,41 +45,7 @@ bool openSSLInitialize(std::string& errMsg)
 | 
				
			|||||||
        (void) OpenSSL_add_ssl_algorithms();
 | 
					        (void) OpenSSL_add_ssl_algorithms();
 | 
				
			||||||
        (void) SSL_load_error_strings();
 | 
					        (void) SSL_load_error_strings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    openSSLInitializationSuccessful = true;
 | 
					        _openSSLInitializationSuccessful = true;
 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int openssl_verify_callback(int preverify, X509_STORE_CTX *x509_ctx)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return preverify;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* create new SSL connection state object */
 | 
					 | 
				
			||||||
SSL *openssl_create_connection(SSL_CTX *ctx, int socket)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    assert(ctx != nullptr);
 | 
					 | 
				
			||||||
    assert(socket > 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SSL *ssl = SSL_new(ctx);
 | 
					 | 
				
			||||||
    if (ssl)
 | 
					 | 
				
			||||||
        SSL_set_fd(ssl, socket);
 | 
					 | 
				
			||||||
    return ssl;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} // anonymous namespace
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace ix 
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    SocketOpenSSL::SocketOpenSSL(int fd) : Socket(fd),
 | 
					 | 
				
			||||||
        _ssl_connection(nullptr), 
 | 
					 | 
				
			||||||
        _ssl_context(nullptr)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SocketOpenSSL::~SocketOpenSSL()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        SocketOpenSSL::close();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::string SocketOpenSSL::getSSLError(int ret)
 | 
					    std::string SocketOpenSSL::getSSLError(int ret)
 | 
				
			||||||
@@ -153,7 +115,12 @@ namespace ix
 | 
				
			|||||||
        if (ctx)
 | 
					        if (ctx)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // To skip verification, pass in SSL_VERIFY_NONE
 | 
					            // To skip verification, pass in SSL_VERIFY_NONE
 | 
				
			||||||
            SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, openssl_verify_callback);
 | 
					            SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER,
 | 
				
			||||||
 | 
					                               [](int preverify, X509_STORE_CTX*) -> int
 | 
				
			||||||
 | 
					                               {
 | 
				
			||||||
 | 
					                                   return preverify;
 | 
				
			||||||
 | 
					                               });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            SSL_CTX_set_verify_depth(ctx, 4);
 | 
					            SSL_CTX_set_verify_depth(ctx, 4);
 | 
				
			||||||
            SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 | 
					            SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -283,8 +250,9 @@ namespace ix
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            std::lock_guard<std::mutex> lock(_mutex);
 | 
					            std::lock_guard<std::mutex> lock(_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!openSSLInitialize(errMsg))
 | 
					            if (!_openSSLInitializationSuccessful)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                errMsg = "OPENSSL_init_ssl failure";
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -306,14 +274,15 @@ namespace ix
 | 
				
			|||||||
                errMsg += ERR_error_string(ssl_err, nullptr);
 | 
					                errMsg += ERR_error_string(ssl_err, nullptr);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _ssl_connection = openssl_create_connection(_ssl_context, _sockfd);
 | 
					            _ssl_connection = SSL_new(_ssl_context);
 | 
				
			||||||
            if (nullptr == _ssl_connection)
 | 
					            if (_ssl_connection == nullptr)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                errMsg = "OpenSSL failed to connect";
 | 
					                errMsg = "OpenSSL failed to connect";
 | 
				
			||||||
                SSL_CTX_free(_ssl_context);
 | 
					                SSL_CTX_free(_ssl_context);
 | 
				
			||||||
                _ssl_context = nullptr;
 | 
					                _ssl_context = nullptr;
 | 
				
			||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            SSL_set_fd(_ssl_connection, _sockfd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // SNI support
 | 
					            // SNI support
 | 
				
			||||||
            SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
 | 
					            SSL_set_tlsext_host_name(_ssl_connection, host.c_str());
 | 
				
			||||||
@@ -357,7 +326,7 @@ namespace ix
 | 
				
			|||||||
        Socket::close();
 | 
					        Socket::close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int SocketOpenSSL::send(char* buf, size_t nbyte)
 | 
					    ssize_t SocketOpenSSL::send(char* buf, size_t nbyte)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        ssize_t sent = 0;
 | 
					        ssize_t sent = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -371,7 +340,7 @@ namespace ix
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ERR_clear_error();
 | 
					            ERR_clear_error();
 | 
				
			||||||
            int write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
 | 
					            ssize_t write_result = SSL_write(_ssl_connection, buf + sent, (int) nbyte);
 | 
				
			||||||
            int reason = SSL_get_error(_ssl_connection, write_result);
 | 
					            int reason = SSL_get_error(_ssl_connection, write_result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (reason == SSL_ERROR_NONE) {
 | 
					            if (reason == SSL_ERROR_NONE) {
 | 
				
			||||||
@@ -384,16 +353,16 @@ namespace ix
 | 
				
			|||||||
                return -1;
 | 
					                return -1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return (int) sent;
 | 
					        return sent;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int SocketOpenSSL::send(const std::string& buffer)
 | 
					    ssize_t SocketOpenSSL::send(const std::string& buffer)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return send((char*)&buffer[0], buffer.size());
 | 
					        return send((char*)&buffer[0], buffer.size());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // No wait support
 | 
					    // No wait support
 | 
				
			||||||
    int SocketOpenSSL::recv(void* buf, size_t nbyte)
 | 
					    ssize_t SocketOpenSSL::recv(void* buf, size_t nbyte)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        while (true)
 | 
					        while (true)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -405,7 +374,7 @@ namespace ix
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ERR_clear_error();
 | 
					            ERR_clear_error();
 | 
				
			||||||
            int read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
 | 
					            ssize_t read_result = SSL_read(_ssl_connection, buf, (int) nbyte);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (read_result > 0)
 | 
					            if (read_result > 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,11 +31,12 @@ namespace ix
 | 
				
			|||||||
                             const CancellationRequest& isCancellationRequested) final;
 | 
					                             const CancellationRequest& isCancellationRequested) final;
 | 
				
			||||||
        virtual void close() final;
 | 
					        virtual void close() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        virtual int send(char* buffer, size_t length) final;
 | 
					        virtual ssize_t send(char* buffer, size_t length) final;
 | 
				
			||||||
        virtual int send(const std::string& buffer) final;
 | 
					        virtual ssize_t send(const std::string& buffer) final;
 | 
				
			||||||
        virtual int recv(void* buffer, size_t length) final;
 | 
					        virtual ssize_t recv(void* buffer, size_t length) final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
 | 
					        void openSSLInitialize();
 | 
				
			||||||
        std::string getSSLError(int ret);
 | 
					        std::string getSSLError(int ret);
 | 
				
			||||||
        SSL_CTX* openSSLCreateContext(std::string& errMsg);
 | 
					        SSL_CTX* openSSLCreateContext(std::string& errMsg);
 | 
				
			||||||
        bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
 | 
					        bool openSSLHandshake(const std::string& hostname, std::string& errMsg);
 | 
				
			||||||
@@ -44,10 +45,13 @@ namespace ix
 | 
				
			|||||||
                                    std::string& errMsg);
 | 
					                                    std::string& errMsg);
 | 
				
			||||||
        bool checkHost(const std::string& host, const char *pattern);
 | 
					        bool checkHost(const std::string& host, const char *pattern);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        SSL_CTX* _ssl_context;
 | 
					 | 
				
			||||||
        SSL* _ssl_connection;
 | 
					        SSL* _ssl_connection;
 | 
				
			||||||
 | 
					        SSL_CTX* _ssl_context;
 | 
				
			||||||
        const SSL_METHOD* _ssl_method;
 | 
					        const SSL_METHOD* _ssl_method;
 | 
				
			||||||
        mutable std::mutex _mutex;  // OpenSSL routines are not thread-safe
 | 
					        mutable std::mutex _mutex;  // OpenSSL routines are not thread-safe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::once_flag _openSSLInitFlag;
 | 
				
			||||||
 | 
					        static std::atomic<bool> _openSSLInitializationSuccessful;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										228
									
								
								ixwebsocket/IXSocketServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								ixwebsocket/IXSocketServer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXSocketServer.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXSocketServer.h"
 | 
				
			||||||
 | 
					#include "IXSocket.h"
 | 
				
			||||||
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
 | 
					#include "IXNetSystem.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <future>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const int SocketServer::kDefaultPort(8080);
 | 
				
			||||||
 | 
					    const std::string SocketServer::kDefaultHost("127.0.0.1");
 | 
				
			||||||
 | 
					    const int SocketServer::kDefaultTcpBacklog(5);
 | 
				
			||||||
 | 
					    const size_t SocketServer::kDefaultMaxConnections(32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SocketServer::SocketServer(int port,
 | 
				
			||||||
 | 
					                               const std::string& host,
 | 
				
			||||||
 | 
					                               int backlog,
 | 
				
			||||||
 | 
					                               size_t maxConnections) :
 | 
				
			||||||
 | 
					        _port(port),
 | 
				
			||||||
 | 
					        _host(host),
 | 
				
			||||||
 | 
					        _backlog(backlog),
 | 
				
			||||||
 | 
					        _maxConnections(maxConnections),
 | 
				
			||||||
 | 
					        _stop(false)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SocketServer::~SocketServer()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::logError(const std::string& str)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_logMutex);
 | 
				
			||||||
 | 
					        std::cerr << str << std::endl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::logInfo(const std::string& str)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_logMutex);
 | 
				
			||||||
 | 
					        std::cout << str << std::endl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::pair<bool, std::string> SocketServer::listen()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        struct sockaddr_in server; // server address information
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get a socket for accepting connections.
 | 
				
			||||||
 | 
					        if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "SocketServer::listen() error creating socket): "
 | 
				
			||||||
 | 
					               << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return std::make_pair(false, ss.str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make that socket reusable. (allow restarting this server at will)
 | 
				
			||||||
 | 
					        int enable = 1;
 | 
				
			||||||
 | 
					        if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR,
 | 
				
			||||||
 | 
					                       (char*) &enable, sizeof(enable)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "SocketServer::listen() error calling setsockopt(SO_REUSEADDR) "
 | 
				
			||||||
 | 
					               << "at address " << _host << ":" << _port
 | 
				
			||||||
 | 
					               << " : " << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::close(_serverFd);
 | 
				
			||||||
 | 
					            return std::make_pair(false, ss.str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bind the socket to the server address.
 | 
				
			||||||
 | 
					        server.sin_family = AF_INET;
 | 
				
			||||||
 | 
					        server.sin_port   = htons(_port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Using INADDR_ANY trigger a pop-up box as binding to any address is detected
 | 
				
			||||||
 | 
					        // by the osx firewall. We need to codesign the binary with a self-signed cert
 | 
				
			||||||
 | 
					        // to allow that, but this is a bit of a pain. (this is what node or python would do).
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // Using INADDR_LOOPBACK also does not work ... while it should.
 | 
				
			||||||
 | 
					        // We default to 127.0.0.1 (localhost)
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        server.sin_addr.s_addr = inet_addr(_host.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "SocketServer::listen() error calling bind "
 | 
				
			||||||
 | 
					               << "at address " << _host << ":" << _port
 | 
				
			||||||
 | 
					               << " : " << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::close(_serverFd);
 | 
				
			||||||
 | 
					            return std::make_pair(false, ss.str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // Listen for connections. Specify the tcp backlog.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        if (::listen(_serverFd, _backlog) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "SocketServer::listen() error calling listen "
 | 
				
			||||||
 | 
					               << "at address " << _host << ":" << _port
 | 
				
			||||||
 | 
					               << " : " << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::close(_serverFd);
 | 
				
			||||||
 | 
					            return std::make_pair(false, ss.str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return std::make_pair(true, "");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::start()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (_thread.joinable()) return; // we've already been started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _thread = std::thread(&SocketServer::run, this);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::wait()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
				
			||||||
 | 
					        _conditionVariable.wait(lock);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::stop()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!_thread.joinable()) return; // nothing to do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _stop = true;
 | 
				
			||||||
 | 
					        _thread.join();
 | 
				
			||||||
 | 
					        _stop = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _conditionVariable.notify_one();
 | 
				
			||||||
 | 
					        ::close(_serverFd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void SocketServer::run()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Set the socket to non blocking mode, so that accept calls are not blocking
 | 
				
			||||||
 | 
					        SocketConnect::configure(_serverFd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return value of std::async, ignored
 | 
				
			||||||
 | 
					        std::future<void> f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (;;)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_stop) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use select to check whether a new connection is in progress
 | 
				
			||||||
 | 
					            fd_set rfds;
 | 
				
			||||||
 | 
					            struct timeval timeout;
 | 
				
			||||||
 | 
					            timeout.tv_sec = 0;
 | 
				
			||||||
 | 
					            timeout.tv_usec = 10 * 1000; // 10ms timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            FD_ZERO(&rfds);
 | 
				
			||||||
 | 
					            FD_SET(_serverFd, &rfds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout) < 0 &&
 | 
				
			||||||
 | 
					                (errno == EBADF || errno == EINVAL))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::stringstream ss;
 | 
				
			||||||
 | 
					                ss << "SocketServer::run() error in select: "
 | 
				
			||||||
 | 
					                   << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					                logError(ss.str());
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!FD_ISSET(_serverFd, &rfds))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // We reached the select timeout, and no new connections are pending
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Accept a connection.
 | 
				
			||||||
 | 
					            struct sockaddr_in client; // client address information
 | 
				
			||||||
 | 
					            int clientFd;              // socket connected to client
 | 
				
			||||||
 | 
					            socklen_t addressLen = sizeof(socklen_t);
 | 
				
			||||||
 | 
					            memset(&client, 0, sizeof(client));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (Socket::getErrno() != EWOULDBLOCK)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // FIXME: that error should be propagated
 | 
				
			||||||
 | 
					                    std::stringstream ss;
 | 
				
			||||||
 | 
					                    ss << "SocketServer::run() error accepting connection: "
 | 
				
			||||||
 | 
					                       << strerror(Socket::getErrno());
 | 
				
			||||||
 | 
					                    logError(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (getConnectedClientsCount() >= _maxConnections)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::stringstream ss;
 | 
				
			||||||
 | 
					                ss << "SocketServer::run() reached max connections = "
 | 
				
			||||||
 | 
					                   << _maxConnections << ". "
 | 
				
			||||||
 | 
					                   << "Not accepting connection";
 | 
				
			||||||
 | 
					                logError(ss.str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ::close(clientFd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Launch the handleConnection work asynchronously in its own thread.
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // 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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										68
									
								
								ixwebsocket/IXSocketServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								ixwebsocket/IXSocketServer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXSocketServer.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <utility> // pair
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <set>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <condition_variable>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    class SocketServer {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        SocketServer(int port = SocketServer::kDefaultPort,
 | 
				
			||||||
 | 
					                     const std::string& host = SocketServer::kDefaultHost,
 | 
				
			||||||
 | 
					                     int backlog = SocketServer::kDefaultTcpBacklog,
 | 
				
			||||||
 | 
					                     size_t maxConnections = SocketServer::kDefaultMaxConnections);
 | 
				
			||||||
 | 
					        virtual ~SocketServer();
 | 
				
			||||||
 | 
					        virtual void stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const static int kDefaultPort;
 | 
				
			||||||
 | 
					        const static std::string kDefaultHost;
 | 
				
			||||||
 | 
					        const static int kDefaultTcpBacklog;
 | 
				
			||||||
 | 
					        const static size_t kDefaultMaxConnections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void start();
 | 
				
			||||||
 | 
					        std::pair<bool, std::string> listen();
 | 
				
			||||||
 | 
					        void wait();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Logging
 | 
				
			||||||
 | 
					        void logError(const std::string& str);
 | 
				
			||||||
 | 
					        void logInfo(const std::string& str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        // Member variables
 | 
				
			||||||
 | 
					        int _port;
 | 
				
			||||||
 | 
					        std::string _host;
 | 
				
			||||||
 | 
					        int _backlog;
 | 
				
			||||||
 | 
					        size_t _maxConnections;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // socket for accepting connections
 | 
				
			||||||
 | 
					        int _serverFd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::mutex _logMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::atomic<bool> _stop;
 | 
				
			||||||
 | 
					        std::thread _thread;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::condition_variable _conditionVariable;
 | 
				
			||||||
 | 
					        std::mutex _conditionVariableMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Methods
 | 
				
			||||||
 | 
					        void run();
 | 
				
			||||||
 | 
					        virtual void handleConnection(int fd) = 0;
 | 
				
			||||||
 | 
					        virtual size_t getConnectedClientsCount() = 0;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										104
									
								
								ixwebsocket/IXUrlParser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										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;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -31,12 +31,14 @@ namespace ix
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
 | 
					    OnTrafficTrackerCallback WebSocket::_onTrafficTrackerCallback = nullptr;
 | 
				
			||||||
    const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
 | 
					    const int WebSocket::kDefaultHandShakeTimeoutSecs(60);
 | 
				
			||||||
 | 
					    const int WebSocket::kDefaultHeartBeatPeriod(-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocket::WebSocket() :
 | 
					    WebSocket::WebSocket() :
 | 
				
			||||||
        _onMessageCallback(OnMessageCallback()),
 | 
					        _onMessageCallback(OnMessageCallback()),
 | 
				
			||||||
        _stop(false),
 | 
					        _stop(false),
 | 
				
			||||||
        _automaticReconnection(true),
 | 
					        _automaticReconnection(true),
 | 
				
			||||||
        _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs)
 | 
					        _handshakeTimeoutSecs(kDefaultHandShakeTimeoutSecs),
 | 
				
			||||||
 | 
					        _heartBeatPeriod(kDefaultHeartBeatPeriod)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _ws.setOnCloseCallback(
 | 
					        _ws.setOnCloseCallback(
 | 
				
			||||||
            [this](uint16_t code, const std::string& reason, size_t wireSize)
 | 
					            [this](uint16_t code, const std::string& reason, size_t wireSize)
 | 
				
			||||||
@@ -77,6 +79,18 @@ namespace ix
 | 
				
			|||||||
        return _perMessageDeflateOptions;
 | 
					        return _perMessageDeflateOptions;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocket::setHeartBeatPeriod(int hearBeatPeriod)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_configMutex);
 | 
				
			||||||
 | 
					        _heartBeatPeriod = hearBeatPeriod;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int WebSocket::getHeartBeatPeriod() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_configMutex);
 | 
				
			||||||
 | 
					        return _heartBeatPeriod;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocket::start()
 | 
					    void WebSocket::start()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (_thread.joinable()) return; // we've already been started
 | 
					        if (_thread.joinable()) return; // we've already been started
 | 
				
			||||||
@@ -110,7 +124,8 @@ namespace ix
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            std::lock_guard<std::mutex> lock(_configMutex);
 | 
					            std::lock_guard<std::mutex> lock(_configMutex);
 | 
				
			||||||
            _ws.configure(_perMessageDeflateOptions);
 | 
					            _ws.configure(_perMessageDeflateOptions,
 | 
				
			||||||
 | 
					                          _heartBeatPeriod);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
 | 
					        WebSocketInitResult status = _ws.connectToUrl(_url, timeoutSecs);
 | 
				
			||||||
@@ -130,7 +145,7 @@ namespace ix
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            std::lock_guard<std::mutex> lock(_configMutex);
 | 
					            std::lock_guard<std::mutex> lock(_configMutex);
 | 
				
			||||||
            _ws.configure(_perMessageDeflateOptions);
 | 
					            _ws.configure(_perMessageDeflateOptions, _heartBeatPeriod);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
 | 
					        WebSocketInitResult status = _ws.connectToSocket(fd, timeoutSecs);
 | 
				
			||||||
@@ -237,6 +252,11 @@ namespace ix
 | 
				
			|||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            webSocketMessageType = WebSocket_MessageType_Pong;
 | 
					                            webSocketMessageType = WebSocket_MessageType_Pong;
 | 
				
			||||||
                        } break;
 | 
					                        } break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        case WebSocketTransport::FRAGMENT:
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            webSocketMessageType = WebSocket_MessageType_Fragment;
 | 
				
			||||||
 | 
					                        } break;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    WebSocketErrorInfo webSocketErrorInfo;
 | 
					                    WebSocketErrorInfo webSocketErrorInfo;
 | 
				
			||||||
@@ -279,9 +299,10 @@ 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, false, onProgressCallback);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketSendInfo WebSocket::ping(const std::string& text)
 | 
					    WebSocketSendInfo WebSocket::ping(const std::string& text)
 | 
				
			||||||
@@ -293,7 +314,9 @@ namespace ix
 | 
				
			|||||||
        return sendMessage(text, true);
 | 
					        return sendMessage(text, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketSendInfo WebSocket::sendMessage(const std::string& text, bool ping)
 | 
					    WebSocketSendInfo WebSocket::sendMessage(const std::string& text,
 | 
				
			||||||
 | 
					                                             bool ping,
 | 
				
			||||||
 | 
					                                             const OnProgressCallback& onProgressCallback)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (!isConnected()) return WebSocketSendInfo(false);
 | 
					        if (!isConnected()) return WebSocketSendInfo(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -315,7 +338,7 @@ namespace ix
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            webSocketSendInfo = _ws.sendBinary(text);
 | 
					            webSocketSendInfo = _ws.sendBinary(text, onProgressCallback);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
 | 
					        WebSocket::invokeTrafficTrackerCallback(webSocketSendInfo.wireSize, false);
 | 
				
			||||||
@@ -331,6 +354,7 @@ namespace ix
 | 
				
			|||||||
            case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
 | 
					            case ix::WebSocketTransport::CONNECTING: return WebSocket_ReadyState_Connecting;
 | 
				
			||||||
            case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
 | 
					            case ix::WebSocketTransport::CLOSING: return WebSocket_ReadyState_Closing;
 | 
				
			||||||
            case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
 | 
					            case ix::WebSocketTransport::CLOSED: return WebSocket_ReadyState_Closed;
 | 
				
			||||||
 | 
					            default: return WebSocket_ReadyState_Closed;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -342,6 +366,7 @@ namespace ix
 | 
				
			|||||||
            case WebSocket_ReadyState_Connecting: return "CONNECTING";
 | 
					            case WebSocket_ReadyState_Connecting: return "CONNECTING";
 | 
				
			||||||
            case WebSocket_ReadyState_Closing: return "CLOSING";
 | 
					            case WebSocket_ReadyState_Closing: return "CLOSING";
 | 
				
			||||||
            case WebSocket_ReadyState_Closed: return "CLOSED";
 | 
					            case WebSocket_ReadyState_Closed: return "CLOSED";
 | 
				
			||||||
 | 
					            default: return "CLOSED";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -354,4 +379,9 @@ namespace ix
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        _automaticReconnection = false;
 | 
					        _automaticReconnection = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t WebSocket::bufferedAmount() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return _ws.bufferedAmount();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@
 | 
				
			|||||||
#include "IXWebSocketSendInfo.h"
 | 
					#include "IXWebSocketSendInfo.h"
 | 
				
			||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
					#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
				
			||||||
#include "IXWebSocketHttpHeaders.h"
 | 
					#include "IXWebSocketHttpHeaders.h"
 | 
				
			||||||
 | 
					#include "IXProgressCallback.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -38,7 +39,8 @@ namespace ix
 | 
				
			|||||||
        WebSocket_MessageType_Close = 2,
 | 
					        WebSocket_MessageType_Close = 2,
 | 
				
			||||||
        WebSocket_MessageType_Error = 3,
 | 
					        WebSocket_MessageType_Error = 3,
 | 
				
			||||||
        WebSocket_MessageType_Ping = 4,
 | 
					        WebSocket_MessageType_Ping = 4,
 | 
				
			||||||
        WebSocket_MessageType_Pong = 5
 | 
					        WebSocket_MessageType_Pong = 5,
 | 
				
			||||||
 | 
					        WebSocket_MessageType_Fragment = 6
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    struct WebSocketOpenInfo
 | 
					    struct WebSocketOpenInfo
 | 
				
			||||||
@@ -60,7 +62,7 @@ namespace ix
 | 
				
			|||||||
        uint16_t code;
 | 
					        uint16_t code;
 | 
				
			||||||
        std::string reason;
 | 
					        std::string reason;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketCloseInfo(uint64_t c = 0,
 | 
					        WebSocketCloseInfo(uint16_t c = 0,
 | 
				
			||||||
                           const std::string& r = std::string())
 | 
					                           const std::string& r = std::string())
 | 
				
			||||||
            : code(c)
 | 
					            : code(c)
 | 
				
			||||||
            , reason(r)
 | 
					            , reason(r)
 | 
				
			||||||
@@ -86,7 +88,8 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        void setUrl(const std::string& url);
 | 
					        void setUrl(const std::string& url);
 | 
				
			||||||
        void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
 | 
					        void setPerMessageDeflateOptions(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
 | 
				
			||||||
        void setHandshakeTimeout(int _handshakeTimeoutSecs);
 | 
					        void setHandshakeTimeout(int handshakeTimeoutSecs);
 | 
				
			||||||
 | 
					        void setHeartBeatPeriod(int hearBeatPeriod);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Run asynchronously, by calling start and stop.
 | 
					        // Run asynchronously, by calling start and stop.
 | 
				
			||||||
        void start();
 | 
					        void start();
 | 
				
			||||||
@@ -96,7 +99,8 @@ namespace ix
 | 
				
			|||||||
        WebSocketInitResult connect(int timeoutSecs);
 | 
					        WebSocketInitResult connect(int timeoutSecs);
 | 
				
			||||||
        void run();
 | 
					        void run();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketSendInfo send(const std::string& text);
 | 
					        WebSocketSendInfo send(const std::string& text,
 | 
				
			||||||
 | 
					                               const OnProgressCallback& onProgressCallback = nullptr);
 | 
				
			||||||
        WebSocketSendInfo ping(const std::string& text);
 | 
					        WebSocketSendInfo ping(const std::string& text);
 | 
				
			||||||
        void close();
 | 
					        void close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,13 +111,17 @@ namespace ix
 | 
				
			|||||||
        ReadyState getReadyState() const;
 | 
					        ReadyState getReadyState() const;
 | 
				
			||||||
        const std::string& getUrl() const;
 | 
					        const std::string& getUrl() const;
 | 
				
			||||||
        const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
 | 
					        const WebSocketPerMessageDeflateOptions& getPerMessageDeflateOptions() const;
 | 
				
			||||||
 | 
					        int getHeartBeatPeriod() const;
 | 
				
			||||||
 | 
					        size_t bufferedAmount() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void enableAutomaticReconnection();
 | 
					        void enableAutomaticReconnection();
 | 
				
			||||||
        void disableAutomaticReconnection();
 | 
					        void disableAutomaticReconnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketSendInfo sendMessage(const std::string& text, bool ping);
 | 
					        WebSocketSendInfo sendMessage(const std::string& text,
 | 
				
			||||||
 | 
					                                      bool ping,
 | 
				
			||||||
 | 
					                                      const OnProgressCallback& callback = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool isConnected() const;
 | 
					        bool isConnected() const;
 | 
				
			||||||
        bool isClosing() const;
 | 
					        bool isClosing() const;
 | 
				
			||||||
@@ -142,6 +150,10 @@ namespace ix
 | 
				
			|||||||
        std::atomic<int> _handshakeTimeoutSecs;
 | 
					        std::atomic<int> _handshakeTimeoutSecs;
 | 
				
			||||||
        static const int kDefaultHandShakeTimeoutSecs;
 | 
					        static const int kDefaultHandShakeTimeoutSecs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Optional Heartbeat
 | 
				
			||||||
 | 
					        int _heartBeatPeriod;
 | 
				
			||||||
 | 
					        static const int kDefaultHeartBeatPeriod;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        friend class WebSocketServer;
 | 
					        friend class WebSocketServer;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXWebSocketHandshake.h"
 | 
					#include "IXWebSocketHandshake.h"
 | 
				
			||||||
#include "IXSocketConnect.h"
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
 | 
					#include "IXUrlParser.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "libwshandshake.hpp"
 | 
					#include "libwshandshake.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,91 +33,7 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool WebSocketHandshake::parseUrl(const std::string& url,
 | 
					    std::string WebSocketHandshake::trim(const std::string& str)
 | 
				
			||||||
                                      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 trim(const std::string& str)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        std::string out(str);
 | 
					        std::string out(str);
 | 
				
			||||||
        out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
 | 
					        out.erase(std::remove(out.begin(), out.end(), ' '), out.end());
 | 
				
			||||||
@@ -125,6 +42,16 @@ namespace ix
 | 
				
			|||||||
        return out;
 | 
					        return out;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return std::equal(a.begin(), a.end(),
 | 
				
			||||||
 | 
					                          b.begin(), b.end(),
 | 
				
			||||||
 | 
					                          [](char a, char b)
 | 
				
			||||||
 | 
					                          {
 | 
				
			||||||
 | 
					                              return tolower(a) == tolower(b);
 | 
				
			||||||
 | 
					                          });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
 | 
					    std::tuple<std::string, std::string, std::string> WebSocketHandshake::parseRequestLine(const std::string& line)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
 | 
					        // Request-Line   = Method SP Request-URI SP HTTP-Version CRLF
 | 
				
			||||||
@@ -182,61 +109,6 @@ namespace ix
 | 
				
			|||||||
        return s;
 | 
					        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)
 | 
					    WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        std::stringstream ss;
 | 
					        std::stringstream ss;
 | 
				
			||||||
@@ -345,7 +217,7 @@ namespace ix
 | 
				
			|||||||
            return WebSocketInitResult(false, status, ss.str());
 | 
					            return WebSocketInitResult(false, status, ss.str());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        auto result = parseHttpHeaders(isCancellationRequested);
 | 
					        auto result = parseHttpHeaders(_socket, isCancellationRequested);
 | 
				
			||||||
        auto headersValid = result.first;
 | 
					        auto headersValid = result.first;
 | 
				
			||||||
        auto headers = result.second;
 | 
					        auto headers = result.second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -354,6 +226,23 @@ namespace ix
 | 
				
			|||||||
            return WebSocketInitResult(false, status, "Error parsing HTTP headers");
 | 
					            return WebSocketInitResult(false, status, "Error parsing HTTP headers");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check the presence of the connection field
 | 
				
			||||||
 | 
					        if (headers.find("connection") == headers.end())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::string errorMsg("Missing connection value");
 | 
				
			||||||
 | 
					            return WebSocketInitResult(false, status, errorMsg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check the value of the connection field
 | 
				
			||||||
 | 
					        // Some websocket servers (Go/Gorilla?) send lowercase values for the
 | 
				
			||||||
 | 
					        // connection header, so do a case insensitive comparison
 | 
				
			||||||
 | 
					        if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "Invalid connection value: " << headers["connection"];
 | 
				
			||||||
 | 
					            return WebSocketInitResult(false, status, ss.str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        char output[29] = {};
 | 
					        char output[29] = {};
 | 
				
			||||||
        WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
 | 
					        WebSocketHandshakeKeyGen::generate(secWebSocketKey.c_str(), output);
 | 
				
			||||||
        if (std::string(output) != headers["sec-websocket-accept"])
 | 
					        if (std::string(output) != headers["sec-websocket-accept"])
 | 
				
			||||||
@@ -423,7 +312,7 @@ namespace ix
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Retrieve and validate HTTP headers
 | 
					        // Retrieve and validate HTTP headers
 | 
				
			||||||
        auto result = parseHttpHeaders(isCancellationRequested);
 | 
					        auto result = parseHttpHeaders(_socket, isCancellationRequested);
 | 
				
			||||||
        auto headersValid = result.first;
 | 
					        auto headersValid = result.first;
 | 
				
			||||||
        auto headers = result.second;
 | 
					        auto headers = result.second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -466,6 +355,8 @@ namespace ix
 | 
				
			|||||||
        std::stringstream ss;
 | 
					        std::stringstream ss;
 | 
				
			||||||
        ss << "HTTP/1.1 101\r\n";
 | 
					        ss << "HTTP/1.1 101\r\n";
 | 
				
			||||||
        ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
 | 
					        ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
 | 
				
			||||||
 | 
					        ss << "Upgrade: websocket\r\n";
 | 
				
			||||||
 | 
					        ss << "Connection: Upgrade\r\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Parse the client headers. Does it support deflate ?
 | 
					        // Parse the client headers. Does it support deflate ?
 | 
				
			||||||
        std::string header = headers["sec-websocket-extensions"];
 | 
					        std::string header = headers["sec-websocket-extensions"];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,22 +59,15 @@ namespace ix
 | 
				
			|||||||
        WebSocketInitResult serverHandshake(int fd,
 | 
					        WebSocketInitResult serverHandshake(int fd,
 | 
				
			||||||
                                            int timeoutSecs);
 | 
					                                            int timeoutSecs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static bool parseUrl(const std::string& url,
 | 
					 | 
				
			||||||
                             std::string& protocol,
 | 
					 | 
				
			||||||
                             std::string& host,
 | 
					 | 
				
			||||||
                             std::string& path,
 | 
					 | 
				
			||||||
                             std::string& query,
 | 
					 | 
				
			||||||
                             int& port);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        static void printUrl(const std::string& url);
 | 
					 | 
				
			||||||
        std::string genRandomString(const int len);
 | 
					        std::string genRandomString(const int len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Parse HTTP headers
 | 
					        // Parse HTTP headers
 | 
				
			||||||
        std::pair<bool, WebSocketHttpHeaders> parseHttpHeaders(const CancellationRequest& isCancellationRequested);
 | 
					 | 
				
			||||||
        WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
 | 
					        WebSocketInitResult sendErrorResponse(int code, const std::string& reason);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
 | 
					        std::tuple<std::string, std::string, std::string> parseRequestLine(const std::string& line);
 | 
				
			||||||
 | 
					        std::string trim(const std::string& str);
 | 
				
			||||||
 | 
					        bool insensitiveStringCompare(const std::string& a, const std::string& b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::atomic<bool>& _requestInitCancellation;
 | 
					        std::atomic<bool>& _requestInitCancellation;
 | 
				
			||||||
        std::shared_ptr<Socket> _socket;
 | 
					        std::shared_ptr<Socket> _socket;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								ixwebsocket/IXWebSocketHttpHeaders.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										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
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXCancellationRequest.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <unordered_map>
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,211 +47,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXWebSocketPerMessageDeflate.h"
 | 
					#include "IXWebSocketPerMessageDeflate.h"
 | 
				
			||||||
#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
					#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
				
			||||||
 | 
					#include "IXWebSocketPerMessageDeflateCodec.h"
 | 
				
			||||||
#include <iostream>
 | 
					 | 
				
			||||||
#include <cassert>
 | 
					 | 
				
			||||||
#include <string.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    // The passed in size (4) is important, without it the string litteral
 | 
					 | 
				
			||||||
    // is treated as a char* and the null termination (\x00) makes it 
 | 
					 | 
				
			||||||
    // look like an empty string.
 | 
					 | 
				
			||||||
    const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const int kBufferSize = 1 << 14;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    //
 | 
					    WebSocketPerMessageDeflate::WebSocketPerMessageDeflate() :
 | 
				
			||||||
    // Compressor
 | 
					        _compressor(std::make_unique<WebSocketPerMessageDeflateCompressor>()),
 | 
				
			||||||
    //
 | 
					        _decompressor(std::make_unique<WebSocketPerMessageDeflateDecompressor>())
 | 
				
			||||||
    WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
 | 
					 | 
				
			||||||
      : _compressBufferSize(kBufferSize)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        memset(&_deflateState, 0, sizeof(_deflateState));
 | 
					        ;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        _deflateState.zalloc = Z_NULL;
 | 
					 | 
				
			||||||
        _deflateState.zfree = Z_NULL;
 | 
					 | 
				
			||||||
        _deflateState.opaque = Z_NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        deflateEnd(&_deflateState);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
 | 
					 | 
				
			||||||
                                                    bool clientNoContextTakeOver)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        int ret = deflateInit2(
 | 
					 | 
				
			||||||
            &_deflateState,
 | 
					 | 
				
			||||||
            Z_DEFAULT_COMPRESSION,
 | 
					 | 
				
			||||||
            Z_DEFLATED,
 | 
					 | 
				
			||||||
            -1*deflateBits,
 | 
					 | 
				
			||||||
            4, // memory level 1-9
 | 
					 | 
				
			||||||
            Z_DEFAULT_STRATEGY
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ret != Z_OK) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _compressBuffer.reset(new unsigned char[_compressBufferSize]);
 | 
					 | 
				
			||||||
        _flush = (clientNoContextTakeOver)
 | 
					 | 
				
			||||||
                 ? Z_FULL_FLUSH
 | 
					 | 
				
			||||||
                 : Z_SYNC_FLUSH;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
 | 
					 | 
				
			||||||
                                                        const std::string& ending)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (ending.size() > value.size()) return false;
 | 
					 | 
				
			||||||
        return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
 | 
					 | 
				
			||||||
                                                        std::string& out)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // 7.2.1.  Compression
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    An endpoint uses the following algorithm to compress a message.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    1.  Compress all the octets of the payload of the message using
 | 
					 | 
				
			||||||
        //        DEFLATE.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    2.  If the resulting data does not end with an empty DEFLATE block
 | 
					 | 
				
			||||||
        //        with no compression (the "BTYPE" bits are set to 00), append an
 | 
					 | 
				
			||||||
        //        empty DEFLATE block with no compression to the tail end.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    3.  Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
 | 
					 | 
				
			||||||
        //        After this step, the last octet of the compressed data contains
 | 
					 | 
				
			||||||
        //        (possibly part of) the DEFLATE header bits with the "BTYPE" bits
 | 
					 | 
				
			||||||
        //        set to 00.
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        size_t output;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (in.empty())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
 | 
					 | 
				
			||||||
            out.append((char *)(buf), 6);
 | 
					 | 
				
			||||||
            return true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _deflateState.avail_in = (uInt) in.size();
 | 
					 | 
				
			||||||
        _deflateState.next_in = (Bytef*) in.data();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        do
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Output to local buffer
 | 
					 | 
				
			||||||
            _deflateState.avail_out = (uInt) _compressBufferSize;
 | 
					 | 
				
			||||||
            _deflateState.next_out = _compressBuffer.get();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            deflate(&_deflateState, _flush);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            output = _compressBufferSize - _deflateState.avail_out;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            out.append((char *)(_compressBuffer.get()),output);
 | 
					 | 
				
			||||||
        } while (_deflateState.avail_out == 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (endsWith(out, kEmptyUncompressedBlock))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            out.resize(out.size() - 4);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
    // Decompressor
 | 
					 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
    WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
 | 
					 | 
				
			||||||
      : _compressBufferSize(kBufferSize)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        memset(&_inflateState, 0, sizeof(_inflateState));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _inflateState.zalloc = Z_NULL;
 | 
					 | 
				
			||||||
        _inflateState.zfree = Z_NULL;
 | 
					 | 
				
			||||||
        _inflateState.opaque = Z_NULL;
 | 
					 | 
				
			||||||
        _inflateState.avail_in = 0;
 | 
					 | 
				
			||||||
        _inflateState.next_in = Z_NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        inflateEnd(&_inflateState);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
 | 
					 | 
				
			||||||
                                                      bool clientNoContextTakeOver)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        int ret = inflateInit2(
 | 
					 | 
				
			||||||
            &_inflateState,
 | 
					 | 
				
			||||||
            -1*inflateBits
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ret != Z_OK) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _compressBuffer.reset(new unsigned char[_compressBufferSize]);
 | 
					 | 
				
			||||||
        _flush = (clientNoContextTakeOver)
 | 
					 | 
				
			||||||
                 ? Z_FULL_FLUSH
 | 
					 | 
				
			||||||
                 : Z_SYNC_FLUSH;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
 | 
					 | 
				
			||||||
                                                            std::string& out)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // 7.2.2.  Decompression
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    An endpoint uses the following algorithm to decompress a message.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    1.  Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
 | 
					 | 
				
			||||||
        //        payload of the message.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        //    2.  Decompress the resulting data using DEFLATE.
 | 
					 | 
				
			||||||
        // 
 | 
					 | 
				
			||||||
        std::string inFixed(in);
 | 
					 | 
				
			||||||
        inFixed += kEmptyUncompressedBlock;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _inflateState.avail_in = (uInt) inFixed.size();
 | 
					 | 
				
			||||||
        _inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        do
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _inflateState.avail_out = (uInt) _compressBufferSize;
 | 
					 | 
				
			||||||
            _inflateState.next_out = _compressBuffer.get();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return false; // zlib error
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            out.append(
 | 
					 | 
				
			||||||
                reinterpret_cast<char *>(_compressBuffer.get()),
 | 
					 | 
				
			||||||
                _compressBufferSize - _inflateState.avail_out
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        } while (_inflateState.avail_out == 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    WebSocketPerMessageDeflate::WebSocketPerMessageDeflate()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _compressor.reset(new WebSocketPerMessageDeflateCompressor());
 | 
					 | 
				
			||||||
        _decompressor.reset(new WebSocketPerMessageDeflateDecompressor());
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
 | 
					    WebSocketPerMessageDeflate::~WebSocketPerMessageDeflate()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _compressor.reset();
 | 
					        ;
 | 
				
			||||||
        _decompressor.reset();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
 | 
					    bool WebSocketPerMessageDeflate::init(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,47 +34,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "zlib.h"
 | 
					 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    class WebSocketPerMessageDeflateOptions;
 | 
					    class WebSocketPerMessageDeflateOptions;
 | 
				
			||||||
 | 
					    class WebSocketPerMessageDeflateCompressor;
 | 
				
			||||||
    class WebSocketPerMessageDeflateCompressor
 | 
					    class WebSocketPerMessageDeflateDecompressor;
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    public:
 | 
					 | 
				
			||||||
        WebSocketPerMessageDeflateCompressor();
 | 
					 | 
				
			||||||
        ~WebSocketPerMessageDeflateCompressor();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
 | 
					 | 
				
			||||||
        bool compress(const std::string& in, std::string& out);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private:
 | 
					 | 
				
			||||||
        static bool endsWith(const std::string& value, const std::string& ending);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int _flush;
 | 
					 | 
				
			||||||
        size_t _compressBufferSize;
 | 
					 | 
				
			||||||
        std::unique_ptr<unsigned char[]> _compressBuffer;
 | 
					 | 
				
			||||||
        z_stream _deflateState;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class WebSocketPerMessageDeflateDecompressor
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    public:
 | 
					 | 
				
			||||||
        WebSocketPerMessageDeflateDecompressor();
 | 
					 | 
				
			||||||
        ~WebSocketPerMessageDeflateDecompressor();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
 | 
					 | 
				
			||||||
        bool decompress(const std::string& in, std::string& out);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private:
 | 
					 | 
				
			||||||
        int _flush;
 | 
					 | 
				
			||||||
        size_t _compressBufferSize;
 | 
					 | 
				
			||||||
        std::unique_ptr<unsigned char[]> _compressBuffer;
 | 
					 | 
				
			||||||
        z_stream _inflateState;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class WebSocketPerMessageDeflate
 | 
					    class WebSocketPerMessageDeflate
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -87,7 +54,7 @@ namespace ix
 | 
				
			|||||||
        bool decompress(const std::string& in, std::string& out);
 | 
					        bool decompress(const std::string& in, std::string& out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        std::shared_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
 | 
					        std::unique_ptr<WebSocketPerMessageDeflateCompressor> _compressor;
 | 
				
			||||||
        std::shared_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
 | 
					        std::unique_ptr<WebSocketPerMessageDeflateDecompressor> _decompressor;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										206
									
								
								ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								ixwebsocket/IXWebSocketPerMessageDeflateCodec.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,206 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXWebSocketPerMessageDeflateCodec.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXWebSocketPerMessageDeflateCodec.h"
 | 
				
			||||||
 | 
					#include "IXWebSocketPerMessageDeflateOptions.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <cassert>
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // The passed in size (4) is important, without it the string litteral
 | 
				
			||||||
 | 
					    // is treated as a char* and the null termination (\x00) makes it
 | 
				
			||||||
 | 
					    // look like an empty string.
 | 
				
			||||||
 | 
					    const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const int kBufferSize = 1 << 14;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // Compressor
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
 | 
				
			||||||
 | 
					      : _compressBufferSize(kBufferSize)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        memset(&_deflateState, 0, sizeof(_deflateState));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _deflateState.zalloc = Z_NULL;
 | 
				
			||||||
 | 
					        _deflateState.zfree = Z_NULL;
 | 
				
			||||||
 | 
					        _deflateState.opaque = Z_NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        deflateEnd(&_deflateState);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
 | 
				
			||||||
 | 
					                                                    bool clientNoContextTakeOver)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int ret = deflateInit2(
 | 
				
			||||||
 | 
					            &_deflateState,
 | 
				
			||||||
 | 
					            Z_DEFAULT_COMPRESSION,
 | 
				
			||||||
 | 
					            Z_DEFLATED,
 | 
				
			||||||
 | 
					            -1*deflateBits,
 | 
				
			||||||
 | 
					            4, // memory level 1-9
 | 
				
			||||||
 | 
					            Z_DEFAULT_STRATEGY
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (ret != Z_OK) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _flush = (clientNoContextTakeOver)
 | 
				
			||||||
 | 
					                 ? Z_FULL_FLUSH
 | 
				
			||||||
 | 
					                 : Z_SYNC_FLUSH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketPerMessageDeflateCompressor::endsWith(const std::string& value,
 | 
				
			||||||
 | 
					                                                        const std::string& ending)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (ending.size() > value.size()) return false;
 | 
				
			||||||
 | 
					        return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
 | 
				
			||||||
 | 
					                                                        std::string& out)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // 7.2.1.  Compression
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    An endpoint uses the following algorithm to compress a message.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    1.  Compress all the octets of the payload of the message using
 | 
				
			||||||
 | 
					        //        DEFLATE.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    2.  If the resulting data does not end with an empty DEFLATE block
 | 
				
			||||||
 | 
					        //        with no compression (the "BTYPE" bits are set to 00), append an
 | 
				
			||||||
 | 
					        //        empty DEFLATE block with no compression to the tail end.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    3.  Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
 | 
				
			||||||
 | 
					        //        After this step, the last octet of the compressed data contains
 | 
				
			||||||
 | 
					        //        (possibly part of) the DEFLATE header bits with the "BTYPE" bits
 | 
				
			||||||
 | 
					        //        set to 00.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        size_t output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (in.empty())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            uint8_t buf[6] = {0x02, 0x00, 0x00, 0x00, 0xff, 0xff};
 | 
				
			||||||
 | 
					            out.append((char *)(buf), 6);
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _deflateState.avail_in = (uInt) in.size();
 | 
				
			||||||
 | 
					        _deflateState.next_in = (Bytef*) in.data();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Output to local buffer
 | 
				
			||||||
 | 
					            _deflateState.avail_out = (uInt) _compressBufferSize;
 | 
				
			||||||
 | 
					            _deflateState.next_out = _compressBuffer.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            deflate(&_deflateState, _flush);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            output = _compressBufferSize - _deflateState.avail_out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            out.append((char *)(_compressBuffer.get()),output);
 | 
				
			||||||
 | 
					        } while (_deflateState.avail_out == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (endsWith(out, kEmptyUncompressedBlock))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            out.resize(out.size() - 4);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    // Decompressor
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
 | 
				
			||||||
 | 
					      : _compressBufferSize(kBufferSize)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        memset(&_inflateState, 0, sizeof(_inflateState));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _inflateState.zalloc = Z_NULL;
 | 
				
			||||||
 | 
					        _inflateState.zfree = Z_NULL;
 | 
				
			||||||
 | 
					        _inflateState.opaque = Z_NULL;
 | 
				
			||||||
 | 
					        _inflateState.avail_in = 0;
 | 
				
			||||||
 | 
					        _inflateState.next_in = Z_NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        inflateEnd(&_inflateState);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
 | 
				
			||||||
 | 
					                                                      bool clientNoContextTakeOver)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int ret = inflateInit2(
 | 
				
			||||||
 | 
					            &_inflateState,
 | 
				
			||||||
 | 
					            -1*inflateBits
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (ret != Z_OK) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _compressBuffer = std::make_unique<unsigned char[]>(_compressBufferSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _flush = (clientNoContextTakeOver)
 | 
				
			||||||
 | 
					                 ? Z_FULL_FLUSH
 | 
				
			||||||
 | 
					                 : Z_SYNC_FLUSH;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in,
 | 
				
			||||||
 | 
					                                                            std::string& out)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // 7.2.2.  Decompression
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    An endpoint uses the following algorithm to decompress a message.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    1.  Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
 | 
				
			||||||
 | 
					        //        payload of the message.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        //    2.  Decompress the resulting data using DEFLATE.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        std::string inFixed(in);
 | 
				
			||||||
 | 
					        inFixed += kEmptyUncompressedBlock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _inflateState.avail_in = (uInt) inFixed.size();
 | 
				
			||||||
 | 
					        _inflateState.next_in = (unsigned char *)(const_cast<char *>(inFixed.data()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        do
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _inflateState.avail_out = (uInt) _compressBufferSize;
 | 
				
			||||||
 | 
					            _inflateState.next_out = _compressBuffer.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int ret = inflate(&_inflateState, Z_SYNC_FLUSH);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false; // zlib error
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            out.append(
 | 
				
			||||||
 | 
					                reinterpret_cast<char *>(_compressBuffer.get()),
 | 
				
			||||||
 | 
					                _compressBufferSize - _inflateState.avail_out
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } while (_inflateState.avail_out == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										50
									
								
								ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								ixwebsocket/IXWebSocketPerMessageDeflateCodec.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXWebSocketPerMessageDeflateCodec.h
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018-2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "zlib.h"
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    class WebSocketPerMessageDeflateCompressor
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        WebSocketPerMessageDeflateCompressor();
 | 
				
			||||||
 | 
					        ~WebSocketPerMessageDeflateCompressor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool init(uint8_t deflateBits, bool clientNoContextTakeOver);
 | 
				
			||||||
 | 
					        bool compress(const std::string& in, std::string& out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        static bool endsWith(const std::string& value, const std::string& ending);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int _flush;
 | 
				
			||||||
 | 
					        size_t _compressBufferSize;
 | 
				
			||||||
 | 
					        std::unique_ptr<unsigned char[]> _compressBuffer;
 | 
				
			||||||
 | 
					        z_stream _deflateState;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class WebSocketPerMessageDeflateDecompressor
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        WebSocketPerMessageDeflateDecompressor();
 | 
				
			||||||
 | 
					        ~WebSocketPerMessageDeflateDecompressor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool init(uint8_t inflateBits, bool clientNoContextTakeOver);
 | 
				
			||||||
 | 
					        bool decompress(const std::string& in, std::string& out);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        int _flush;
 | 
				
			||||||
 | 
					        size_t _compressBufferSize;
 | 
				
			||||||
 | 
					        std::unique_ptr<unsigned char[]> _compressBuffer;
 | 
				
			||||||
 | 
					        z_stream _inflateState;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
#include <algorithm>
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <cctype>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,35 +8,22 @@
 | 
				
			|||||||
#include "IXWebSocketTransport.h"
 | 
					#include "IXWebSocketTransport.h"
 | 
				
			||||||
#include "IXWebSocket.h"
 | 
					#include "IXWebSocket.h"
 | 
				
			||||||
#include "IXSocketConnect.h"
 | 
					#include "IXSocketConnect.h"
 | 
				
			||||||
 | 
					#include "IXNetSystem.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
#include <future>
 | 
					#include <future>
 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <netdb.h>
 | 
					 | 
				
			||||||
#include <stdio.h>
 | 
					 | 
				
			||||||
#include <arpa/inet.h>
 | 
					 | 
				
			||||||
#include <sys/socket.h>
 | 
					 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const int WebSocketServer::kDefaultPort(8080);
 | 
					 | 
				
			||||||
    const std::string WebSocketServer::kDefaultHost("127.0.0.1");
 | 
					 | 
				
			||||||
    const int WebSocketServer::kDefaultTcpBacklog(5);
 | 
					 | 
				
			||||||
    const size_t WebSocketServer::kDefaultMaxConnections(32);
 | 
					 | 
				
			||||||
    const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
 | 
					    const int WebSocketServer::kDefaultHandShakeTimeoutSecs(3); // 3 seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketServer::WebSocketServer(int port,
 | 
					    WebSocketServer::WebSocketServer(int port,
 | 
				
			||||||
                                     const std::string& host,
 | 
					                                     const std::string& host,
 | 
				
			||||||
                                     int backlog,
 | 
					                                     int backlog,
 | 
				
			||||||
                                     size_t maxConnections,
 | 
					                                     size_t maxConnections,
 | 
				
			||||||
                                     int handshakeTimeoutSecs) :
 | 
					                                     int handshakeTimeoutSecs) : SocketServer(port, host, backlog, maxConnections),
 | 
				
			||||||
        _port(port),
 | 
					        _handshakeTimeoutSecs(handshakeTimeoutSecs)
 | 
				
			||||||
        _host(host),
 | 
					 | 
				
			||||||
        _backlog(backlog),
 | 
					 | 
				
			||||||
        _maxConnections(maxConnections),
 | 
					 | 
				
			||||||
        _handshakeTimeoutSecs(handshakeTimeoutSecs),
 | 
					 | 
				
			||||||
        _stop(false)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -46,189 +33,25 @@ namespace ix
 | 
				
			|||||||
        stop();
 | 
					        stop();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _onConnectionCallback = callback;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void WebSocketServer::logError(const std::string& str)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::lock_guard<std::mutex> lock(_logMutex);
 | 
					 | 
				
			||||||
        std::cerr << str << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void WebSocketServer::logInfo(const std::string& str)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::lock_guard<std::mutex> lock(_logMutex);
 | 
					 | 
				
			||||||
        std::cout << str << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    std::pair<bool, std::string> WebSocketServer::listen()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        struct sockaddr_in server; // server address information
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Get a socket for accepting connections.
 | 
					 | 
				
			||||||
        if ((_serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            std::stringstream ss;
 | 
					 | 
				
			||||||
            ss << "WebSocketServer::listen() error creating socket): "
 | 
					 | 
				
			||||||
               << strerror(errno);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return std::make_pair(false, ss.str());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Make that socket reusable. (allow restarting this server at will)
 | 
					 | 
				
			||||||
        int enable = 1;
 | 
					 | 
				
			||||||
        if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            std::stringstream ss;
 | 
					 | 
				
			||||||
            ss << "WebSocketServer::listen() error calling setsockopt(SO_REUSEADDR): "
 | 
					 | 
				
			||||||
               << strerror(errno);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return std::make_pair(false, ss.str());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Bind the socket to the server address.
 | 
					 | 
				
			||||||
        server.sin_family = AF_INET;
 | 
					 | 
				
			||||||
        server.sin_port   = htons(_port);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Using INADDR_ANY trigger a pop-up box as binding to any address is detected 
 | 
					 | 
				
			||||||
        // by the osx firewall. We need to codesign the binary with a self-signed cert
 | 
					 | 
				
			||||||
        // to allow that, but this is a bit of a pain. (this is what node or python would do).
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        // Using INADDR_LOOPBACK also does not work ... while it should.
 | 
					 | 
				
			||||||
        // We default to 127.0.0.1 (localhost)
 | 
					 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        server.sin_addr.s_addr = inet_addr(_host.c_str());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (bind(_serverFd, (struct sockaddr *)&server, sizeof(server)) < 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            std::stringstream ss;
 | 
					 | 
				
			||||||
            ss << "WebSocketServer::listen() error calling bind: "
 | 
					 | 
				
			||||||
               << strerror(errno);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return std::make_pair(false, ss.str());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /*
 | 
					 | 
				
			||||||
         * Listen for connections. Specify the tcp backlog.
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        if (::listen(_serverFd, _backlog) != 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            std::stringstream ss;
 | 
					 | 
				
			||||||
            ss << "WebSocketServer::listen() error calling listen: "
 | 
					 | 
				
			||||||
               << strerror(errno);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return std::make_pair(false, ss.str());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return std::make_pair(true, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void WebSocketServer::start()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (_thread.joinable()) return; // we've already been started
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _thread = std::thread(&WebSocketServer::run, this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void WebSocketServer::wait()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        std::unique_lock<std::mutex> lock(_conditionVariableMutex);
 | 
					 | 
				
			||||||
        _conditionVariable.wait(lock);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void WebSocketServer::stop()
 | 
					    void WebSocketServer::stop()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (!_thread.joinable()) return; // nothing to do
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto clients = getClients();
 | 
					        auto clients = getClients();
 | 
				
			||||||
        for (auto client : clients)
 | 
					        for (auto client : clients)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            client->close();
 | 
					            client->close();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _stop = true;
 | 
					        SocketServer::stop();
 | 
				
			||||||
        _thread.join();
 | 
					 | 
				
			||||||
        _stop = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _conditionVariable.notify_one();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketServer::run()
 | 
					    void WebSocketServer::setOnConnectionCallback(const OnConnectionCallback& callback)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // Set the socket to non blocking mode, so that accept calls are not blocking
 | 
					        _onConnectionCallback = callback;
 | 
				
			||||||
        SocketConnect::configure(_serverFd);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Return value of std::async, ignored
 | 
					 | 
				
			||||||
        std::future<void> f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Select arguments
 | 
					 | 
				
			||||||
        fd_set rfds;
 | 
					 | 
				
			||||||
        struct timeval timeout;
 | 
					 | 
				
			||||||
        timeout.tv_sec = 0;
 | 
					 | 
				
			||||||
        timeout.tv_usec = 10 * 1000; // 10ms
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (;;)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_stop) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            FD_ZERO(&rfds);
 | 
					 | 
				
			||||||
            FD_SET(_serverFd, &rfds);
 | 
					 | 
				
			||||||
            select(_serverFd + 1, &rfds, nullptr, nullptr, &timeout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!FD_ISSET(_serverFd, &rfds))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // We reached the select timeout, and no new connections are pending
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Accept a connection.
 | 
					 | 
				
			||||||
            struct sockaddr_in client; // client address information
 | 
					 | 
				
			||||||
            int clientFd;              // socket connected to client
 | 
					 | 
				
			||||||
            socklen_t addressLen = sizeof(socklen_t);
 | 
					 | 
				
			||||||
            memset(&client, 0, sizeof(client));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ((clientFd = accept(_serverFd, (struct sockaddr *)&client, &addressLen)) < 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (errno != EWOULDBLOCK)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // FIXME: that error should be propagated
 | 
					 | 
				
			||||||
                    std::stringstream ss;
 | 
					 | 
				
			||||||
                    ss << "WebSocketServer::run() error accepting connection: "
 | 
					 | 
				
			||||||
                       << strerror(errno);
 | 
					 | 
				
			||||||
                    logError(ss.str());
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (getConnectedClientsCount() >= _maxConnections)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                std::stringstream ss;
 | 
					 | 
				
			||||||
                ss << "WebSocketServer::run() reached max connections = "
 | 
					 | 
				
			||||||
                   << _maxConnections << ". "
 | 
					 | 
				
			||||||
                   << "Not accepting connection";
 | 
					 | 
				
			||||||
                logError(ss.str());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ::close(clientFd);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Launch the handleConnection work asynchronously in its own thread.
 | 
					 | 
				
			||||||
            //
 | 
					 | 
				
			||||||
            // 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,
 | 
					 | 
				
			||||||
                           &WebSocketServer::handleConnection,
 | 
					 | 
				
			||||||
                           this,
 | 
					 | 
				
			||||||
                           clientFd);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketServer::handleConnection(int fd)
 | 
					    void WebSocketServer::handleConnection(int fd)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        std::shared_ptr<WebSocket> webSocket(new WebSocket);
 | 
					        auto webSocket = std::make_shared<WebSocket>();
 | 
				
			||||||
        _onConnectionCallback(webSocket);
 | 
					        _onConnectionCallback(webSocket);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        webSocket->disableAutomaticReconnection();
 | 
					        webSocket->disableAutomaticReconnection();
 | 
				
			||||||
@@ -276,6 +99,7 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    size_t WebSocketServer::getConnectedClientsCount()
 | 
					    size_t WebSocketServer::getConnectedClientsCount()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return getClients().size();
 | 
					        std::lock_guard<std::mutex> lock(_clientsMutex);
 | 
				
			||||||
 | 
					        return _clients.size();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,67 +16,40 @@
 | 
				
			|||||||
#include <condition_variable>
 | 
					#include <condition_variable>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "IXWebSocket.h"
 | 
					#include "IXWebSocket.h"
 | 
				
			||||||
 | 
					#include "IXSocketServer.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;
 | 
					    using OnConnectionCallback = std::function<void(std::shared_ptr<WebSocket>)>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class WebSocketServer {
 | 
					    class WebSocketServer : public SocketServer {
 | 
				
			||||||
    public:
 | 
					    public:
 | 
				
			||||||
        WebSocketServer(int port = WebSocketServer::kDefaultPort,
 | 
					        WebSocketServer(int port = SocketServer::kDefaultPort,
 | 
				
			||||||
                        const std::string& host = WebSocketServer::kDefaultHost,
 | 
					                        const std::string& host = SocketServer::kDefaultHost,
 | 
				
			||||||
                        int backlog = WebSocketServer::kDefaultTcpBacklog,
 | 
					                        int backlog = SocketServer::kDefaultTcpBacklog,
 | 
				
			||||||
                        size_t maxConnections = WebSocketServer::kDefaultMaxConnections,
 | 
					                        size_t maxConnections = SocketServer::kDefaultMaxConnections,
 | 
				
			||||||
                        int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
 | 
					                        int handshakeTimeoutSecs = WebSocketServer::kDefaultHandShakeTimeoutSecs);
 | 
				
			||||||
        virtual ~WebSocketServer();
 | 
					        virtual ~WebSocketServer();
 | 
				
			||||||
 | 
					        virtual void stop() final;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void setOnConnectionCallback(const OnConnectionCallback& callback);
 | 
					        void setOnConnectionCallback(const OnConnectionCallback& callback);
 | 
				
			||||||
        void start();
 | 
					 | 
				
			||||||
        void wait();
 | 
					 | 
				
			||||||
        void stop();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::pair<bool, std::string> listen();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get all the connected clients
 | 
					        // Get all the connected clients
 | 
				
			||||||
        std::set<std::shared_ptr<WebSocket>> getClients();
 | 
					        std::set<std::shared_ptr<WebSocket>> getClients();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        // Member variables
 | 
					        // Member variables
 | 
				
			||||||
        int _port;
 | 
					 | 
				
			||||||
        std::string _host;
 | 
					 | 
				
			||||||
        int _backlog;
 | 
					 | 
				
			||||||
        size_t _maxConnections;
 | 
					 | 
				
			||||||
        int _handshakeTimeoutSecs;
 | 
					        int _handshakeTimeoutSecs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OnConnectionCallback _onConnectionCallback;
 | 
					        OnConnectionCallback _onConnectionCallback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // socket for accepting connections
 | 
					 | 
				
			||||||
        int _serverFd;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::mutex _clientsMutex;
 | 
					        std::mutex _clientsMutex;
 | 
				
			||||||
        std::set<std::shared_ptr<WebSocket>> _clients;
 | 
					        std::set<std::shared_ptr<WebSocket>> _clients;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        std::mutex _logMutex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::atomic<bool> _stop;
 | 
					 | 
				
			||||||
        std::thread _thread;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        std::condition_variable _conditionVariable;
 | 
					 | 
				
			||||||
        std::mutex _conditionVariableMutex;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const static int kDefaultPort;
 | 
					 | 
				
			||||||
        const static std::string kDefaultHost;
 | 
					 | 
				
			||||||
        const static int kDefaultTcpBacklog;
 | 
					 | 
				
			||||||
        const static size_t kDefaultMaxConnections;
 | 
					 | 
				
			||||||
        const static int kDefaultHandShakeTimeoutSecs;
 | 
					        const static int kDefaultHandShakeTimeoutSecs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Methods
 | 
					        // Methods
 | 
				
			||||||
        void run();
 | 
					        virtual void handleConnection(int fd) final;
 | 
				
			||||||
        void handleConnection(int fd);
 | 
					        virtual size_t getConnectedClientsCount() final;
 | 
				
			||||||
        size_t getConnectedClientsCount();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Logging
 | 
					 | 
				
			||||||
        void logError(const std::string& str);
 | 
					 | 
				
			||||||
        void logInfo(const std::string& str);
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,31 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * The MIT License (MIT)
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * Copyright (c) 2012, 2013 <dhbaird@gmail.com>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					 * of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					 * in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					 * copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					 * furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					 * all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
 | 
					 * THE SOFTWARE.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *  IXWebSocketTransport.cpp
 | 
					 *  IXWebSocketTransport.cpp
 | 
				
			||||||
 *  Author: Benjamin Sergeant
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 *  Copyright (c) 2017-2018 Machine Zone, Inc. All rights reserved.
 | 
					 *  Copyright (c) 2017-2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
@@ -11,14 +35,8 @@
 | 
				
			|||||||
#include "IXWebSocketTransport.h"
 | 
					#include "IXWebSocketTransport.h"
 | 
				
			||||||
#include "IXWebSocketHandshake.h"
 | 
					#include "IXWebSocketHandshake.h"
 | 
				
			||||||
#include "IXWebSocketHttpHeaders.h"
 | 
					#include "IXWebSocketHttpHeaders.h"
 | 
				
			||||||
 | 
					#include "IXUrlParser.h"
 | 
				
			||||||
#ifdef IXWEBSOCKET_USE_TLS
 | 
					#include "IXSocketFactory.h"
 | 
				
			||||||
# ifdef __APPLE__
 | 
					 | 
				
			||||||
#  include "IXSocketAppleSSL.h"
 | 
					 | 
				
			||||||
# else
 | 
					 | 
				
			||||||
#  include "IXSocketOpenSSL.h"
 | 
					 | 
				
			||||||
# endif
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <string.h>
 | 
					#include <string.h>
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
@@ -29,18 +47,26 @@
 | 
				
			|||||||
#include <cstdarg>
 | 
					#include <cstdarg>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <chrono>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    const std::string WebSocketTransport::kHeartBeatPingMessage("ixwebsocket::hearbeat");
 | 
				
			||||||
 | 
					    const int WebSocketTransport::kDefaultHeartBeatPeriod(-1);
 | 
				
			||||||
 | 
					    constexpr size_t WebSocketTransport::kChunkSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketTransport::WebSocketTransport() :
 | 
					    WebSocketTransport::WebSocketTransport() :
 | 
				
			||||||
        _readyState(CLOSED),
 | 
					        _readyState(CLOSED),
 | 
				
			||||||
        _closeCode(0),
 | 
					        _closeCode(0),
 | 
				
			||||||
        _closeWireSize(0),
 | 
					        _closeWireSize(0),
 | 
				
			||||||
        _enablePerMessageDeflate(false),
 | 
					        _enablePerMessageDeflate(false),
 | 
				
			||||||
        _requestInitCancellation(false)
 | 
					        _requestInitCancellation(false),
 | 
				
			||||||
 | 
					        _heartBeatPeriod(kDefaultHeartBeatPeriod),
 | 
				
			||||||
 | 
					        _lastSendTimePoint(std::chrono::steady_clock::now())
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        _readbuf.resize(kChunkSize);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketTransport::~WebSocketTransport()
 | 
					    WebSocketTransport::~WebSocketTransport()
 | 
				
			||||||
@@ -48,10 +74,12 @@ namespace ix
 | 
				
			|||||||
        ;
 | 
					        ;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions)
 | 
					    void WebSocketTransport::configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
 | 
				
			||||||
 | 
					                                       int hearBeatPeriod)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _perMessageDeflateOptions = perMessageDeflateOptions;
 | 
					        _perMessageDeflateOptions = perMessageDeflateOptions;
 | 
				
			||||||
        _enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
 | 
					        _enablePerMessageDeflate = _perMessageDeflateOptions.enabled();
 | 
				
			||||||
 | 
					        _heartBeatPeriod = hearBeatPeriod;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Client
 | 
					    // Client
 | 
				
			||||||
@@ -60,31 +88,21 @@ namespace ix
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        std::string protocol, host, path, query;
 | 
					        std::string protocol, host, path, query;
 | 
				
			||||||
        int port;
 | 
					        int port;
 | 
				
			||||||
 | 
					        bool websocket = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!WebSocketHandshake::parseUrl(url, protocol, host,
 | 
					        if (!UrlParser::parse(url, protocol, host, path, query, port, websocket))
 | 
				
			||||||
                                          path, query, port))
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return WebSocketInitResult(false, 0,
 | 
					            return WebSocketInitResult(false, 0,
 | 
				
			||||||
                                       std::string("Could not parse URL ") + url);
 | 
					                                       std::string("Could not parse URL ") + url);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (protocol == "wss")
 | 
					        bool tls = protocol == "wss";
 | 
				
			||||||
 | 
					        std::string errorMsg;
 | 
				
			||||||
 | 
					        _socket = createSocket(tls, errorMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!_socket)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _socket.reset();
 | 
					            return WebSocketInitResult(false, 0, errorMsg);
 | 
				
			||||||
#ifdef IXWEBSOCKET_USE_TLS
 | 
					 | 
				
			||||||
# ifdef __APPLE__
 | 
					 | 
				
			||||||
             _socket = std::make_shared<SocketAppleSSL>();
 | 
					 | 
				
			||||||
# else
 | 
					 | 
				
			||||||
             _socket = std::make_shared<SocketOpenSSL>();
 | 
					 | 
				
			||||||
# endif
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
            return WebSocketInitResult(false, 0, "TLS is not supported.");
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _socket.reset();
 | 
					 | 
				
			||||||
            _socket = std::make_shared<Socket>();
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketHandshake webSocketHandshake(_requestInitCancellation,
 | 
					        WebSocketHandshake webSocketHandshake(_requestInitCancellation,
 | 
				
			||||||
@@ -149,44 +167,82 @@ namespace ix
 | 
				
			|||||||
        _onCloseCallback = onCloseCallback;
 | 
					        _onCloseCallback = onCloseCallback;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Only consider send time points for that computation.
 | 
				
			||||||
 | 
					    // The receive time points is taken into account in Socket::poll (second parameter).
 | 
				
			||||||
 | 
					    bool WebSocketTransport::heartBeatPeriodExceeded()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_lastSendTimePointMutex);
 | 
				
			||||||
 | 
					        auto now = std::chrono::steady_clock::now();
 | 
				
			||||||
 | 
					        return now - _lastSendTimePoint > std::chrono::seconds(_heartBeatPeriod);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketTransport::poll()
 | 
					    void WebSocketTransport::poll()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _socket->poll(
 | 
					        _socket->poll(
 | 
				
			||||||
            [this]()
 | 
					            [this](PollResultType pollResult)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // If (1) heartbeat is enabled, and (2) no data was received or
 | 
				
			||||||
 | 
					                // send for a duration exceeding our heart-beat period, send a
 | 
				
			||||||
 | 
					                // ping to the server.
 | 
				
			||||||
 | 
					                if (pollResult == PollResultType_Timeout &&
 | 
				
			||||||
 | 
					                    heartBeatPeriodExceeded())
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    std::stringstream ss;
 | 
				
			||||||
 | 
					                    ss << kHeartBeatPingMessage << "::" << _heartBeatPeriod << "s";
 | 
				
			||||||
 | 
					                    sendPing(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                // Make sure we send all the buffered data
 | 
				
			||||||
 | 
					                // there can be a lot of it for large messages.
 | 
				
			||||||
 | 
					                else if (pollResult == PollResultType_SendRequest)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    while (!isSendBufferEmpty() && !_requestInitCancellation)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        sendOnSocket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Sleep 10ms between each send so that we dont busy loop
 | 
				
			||||||
 | 
					                        // A better strategy would be to select on the socket to 
 | 
				
			||||||
 | 
					                        // check whether we can write to it without blocking
 | 
				
			||||||
 | 
					                        std::chrono::duration<double, std::micro> duration(10);
 | 
				
			||||||
 | 
					                        std::this_thread::sleep_for(duration);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (pollResult == PollResultType_ReadyForRead)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    while (true)
 | 
					                    while (true)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                    int N = (int) _rxbuf.size();
 | 
					                        ssize_t ret = _socket->recv((char*)&_readbuf[0], _readbuf.size());
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    int ret;
 | 
					 | 
				
			||||||
                    _rxbuf.resize(N + 1500);
 | 
					 | 
				
			||||||
                    ret = _socket->recv((char*)&_rxbuf[0] + N, 1500);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
 | 
					                        if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
 | 
				
			||||||
                                    _socket->getErrno() == EAGAIN)) {
 | 
					                                        _socket->getErrno() == EAGAIN))
 | 
				
			||||||
                        _rxbuf.resize(N);
 | 
					                        {
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        else if (ret <= 0)
 | 
					                        else if (ret <= 0)
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                        _rxbuf.resize(N);
 | 
					                            _rxbuf.clear();
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            _socket->close();
 | 
					                            _socket->close();
 | 
				
			||||||
                            setReadyState(CLOSED);
 | 
					                            setReadyState(CLOSED);
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        else
 | 
					                        else
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                        _rxbuf.resize(N + ret);
 | 
					                            _rxbuf.insert(_rxbuf.end(),
 | 
				
			||||||
 | 
					                                          _readbuf.begin(),
 | 
				
			||||||
 | 
					                                          _readbuf.begin() + ret);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                if (isSendBufferEmpty() && _readyState == CLOSING) 
 | 
					                else if (pollResult == PollResultType_Error)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _socket->close();
 | 
					                    _socket->close();
 | 
				
			||||||
                    setReadyState(CLOSED);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					                else if (pollResult == PollResultType_CloseRequest)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            _heartBeatPeriod);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool WebSocketTransport::isSendBufferEmpty() const
 | 
					    bool WebSocketTransport::isSendBufferEmpty() const
 | 
				
			||||||
@@ -332,17 +388,39 @@ namespace ix
 | 
				
			|||||||
                || ws.opcode == wsheader_type::CONTINUATION
 | 
					                || ws.opcode == wsheader_type::CONTINUATION
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                unmaskReceiveBuffer(ws);
 | 
					                unmaskReceiveBuffer(ws);
 | 
				
			||||||
                _receivedData.insert(_receivedData.end(),
 | 
					
 | 
				
			||||||
                                     _rxbuf.begin()+ws.header_size,
 | 
					                //
 | 
				
			||||||
                                     _rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed
 | 
					                // Usual case. Small unfragmented messages
 | 
				
			||||||
 | 
					                //
 | 
				
			||||||
 | 
					                if (ws.fin && _chunks.empty())
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    emitMessage(MSG,
 | 
				
			||||||
 | 
					                                std::string(_rxbuf.begin()+ws.header_size,
 | 
				
			||||||
 | 
					                                            _rxbuf.begin()+ws.header_size+(size_t) ws.N),
 | 
				
			||||||
 | 
					                                ws,
 | 
				
			||||||
 | 
					                                onMessageCallback);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    //
 | 
				
			||||||
 | 
					                    // Add intermediary message to our chunk list.
 | 
				
			||||||
 | 
					                    // We use a chunk list instead of a big buffer because resizing
 | 
				
			||||||
 | 
					                    // large buffer can be very costly when we need to re-allocate
 | 
				
			||||||
 | 
					                    // the internal buffer which is slow and can let the internal OS
 | 
				
			||||||
 | 
					                    // receive buffer fill out.
 | 
				
			||||||
 | 
					                    //
 | 
				
			||||||
 | 
					                    _chunks.emplace_back(
 | 
				
			||||||
 | 
					                        std::vector<uint8_t>(_rxbuf.begin()+ws.header_size,
 | 
				
			||||||
 | 
					                                             _rxbuf.begin()+ws.header_size+(size_t)ws.N));
 | 
				
			||||||
                    if (ws.fin)
 | 
					                    if (ws.fin)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                    // fire callback with a string message
 | 
					                        emitMessage(MSG, getMergedChunks(), ws, onMessageCallback);
 | 
				
			||||||
                    std::string stringMessage(_receivedData.begin(),
 | 
					                        _chunks.clear();
 | 
				
			||||||
                                              _receivedData.end());
 | 
					                    }
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
                    emitMessage(MSG, stringMessage, ws, onMessageCallback);
 | 
					                    {
 | 
				
			||||||
                    _receivedData.clear();
 | 
					                        emitMessage(FRAGMENT, std::string(), ws, onMessageCallback);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (ws.opcode == wsheader_type::PING)
 | 
					            else if (ws.opcode == wsheader_type::PING)
 | 
				
			||||||
@@ -392,11 +470,32 @@ namespace ix
 | 
				
			|||||||
                close();
 | 
					                close();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Erase the message that has been processed from the input/read buffer
 | 
				
			||||||
            _rxbuf.erase(_rxbuf.begin(),
 | 
					            _rxbuf.erase(_rxbuf.begin(),
 | 
				
			||||||
                         _rxbuf.begin() + ws.header_size + (size_t) ws.N);
 | 
					                         _rxbuf.begin() + ws.header_size + (size_t) ws.N);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string WebSocketTransport::getMergedChunks() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        size_t length = 0;
 | 
				
			||||||
 | 
					        for (auto&& chunk : _chunks)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            length += chunk.size();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string msg;
 | 
				
			||||||
 | 
					        msg.reserve(length);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (auto&& chunk : _chunks)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::string str(chunk.begin(), chunk.end());
 | 
				
			||||||
 | 
					            msg += str;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return msg;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketTransport::emitMessage(MessageKind messageKind,
 | 
					    void WebSocketTransport::emitMessage(MessageKind messageKind,
 | 
				
			||||||
                                         const std::string& message,
 | 
					                                         const std::string& message,
 | 
				
			||||||
                                         const wsheader_type& ws,
 | 
					                                         const wsheader_type& ws,
 | 
				
			||||||
@@ -405,11 +504,11 @@ namespace ix
 | 
				
			|||||||
        size_t wireSize = message.size();
 | 
					        size_t wireSize = message.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // When the RSV1 bit is 1 it means the message is compressed
 | 
					        // When the RSV1 bit is 1 it means the message is compressed
 | 
				
			||||||
        if (_enablePerMessageDeflate && ws.rsv1)
 | 
					        if (_enablePerMessageDeflate && ws.rsv1 && messageKind != FRAGMENT)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            std::string decompressedMessage;
 | 
					            std::string decompressedMessage;
 | 
				
			||||||
            bool success = _perMessageDeflate.decompress(message, decompressedMessage);
 | 
					            bool success = _perMessageDeflate.decompress(message, decompressedMessage);
 | 
				
			||||||
            onMessageCallback(decompressedMessage, wireSize, not success, messageKind);
 | 
					            onMessageCallback(decompressedMessage, wireSize, !success, messageKind);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -426,9 +525,11 @@ namespace ix
 | 
				
			|||||||
        return static_cast<unsigned>(seconds);
 | 
					        return static_cast<unsigned>(seconds);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketSendInfo WebSocketTransport::sendData(wsheader_type::opcode_type type, 
 | 
					    WebSocketSendInfo WebSocketTransport::sendData(
 | 
				
			||||||
 | 
					        wsheader_type::opcode_type type,
 | 
				
			||||||
        const std::string& message,
 | 
					        const std::string& message,
 | 
				
			||||||
                                                   bool compress)
 | 
					        bool compress,
 | 
				
			||||||
 | 
					        const OnProgressCallback& onProgressCallback)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (_readyState == CLOSING || _readyState == CLOSED)
 | 
					        if (_readyState == CLOSING || _readyState == CLOSED)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -445,15 +546,87 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (compress)
 | 
					        if (compress)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            bool success = _perMessageDeflate.compress(message, compressedMessage);
 | 
					            if (!_perMessageDeflate.compress(message, compressedMessage))
 | 
				
			||||||
            compressionError = !success;
 | 
					            {
 | 
				
			||||||
 | 
					                bool success = false;
 | 
				
			||||||
 | 
					                compressionError = true;
 | 
				
			||||||
 | 
					                payloadSize = 0;
 | 
				
			||||||
 | 
					                wireSize = 0;
 | 
				
			||||||
 | 
					                return WebSocketSendInfo(success, compressionError, payloadSize, wireSize);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            compressionError = false;
 | 
				
			||||||
            wireSize = compressedMessage.size();
 | 
					            wireSize = compressedMessage.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            message_begin = compressedMessage.begin();
 | 
					            message_begin = compressedMessage.begin();
 | 
				
			||||||
            message_end = compressedMessage.end();
 | 
					            message_end = compressedMessage.end();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        uint64_t message_size = wireSize;
 | 
					        // Common case for most message. No fragmentation required.
 | 
				
			||||||
 | 
					        if (wireSize < kChunkSize)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            sendFragment(type, true, message_begin, message_end, compress);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // Large messages need to be fragmented
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // Rules:
 | 
				
			||||||
 | 
					            // First message needs to specify a proper type (BINARY or TEXT)
 | 
				
			||||||
 | 
					            // Intermediary and last messages need to be of type CONTINUATION
 | 
				
			||||||
 | 
					            // Last message must set the fin byte.
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            auto steps = wireSize / kChunkSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            std::string::const_iterator begin = message_begin;
 | 
				
			||||||
 | 
					            std::string::const_iterator end = message_end;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (uint64_t i = 0 ; i < steps; ++i)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                bool firstStep = i == 0;
 | 
				
			||||||
 | 
					                bool lastStep = (i+1) == steps;
 | 
				
			||||||
 | 
					                bool fin = lastStep;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                end = begin + kChunkSize;
 | 
				
			||||||
 | 
					                if (lastStep)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    end = message_end;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                auto opcodeType = type;
 | 
				
			||||||
 | 
					                if (!firstStep)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    opcodeType = wsheader_type::CONTINUATION;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Send message
 | 
				
			||||||
 | 
					                sendFragment(opcodeType, fin, begin, end, compress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (onProgressCallback && !onProgressCallback((int)i, (int) steps))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                begin += kChunkSize;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Request to flush the send buffer on the background thread if it isn't empty
 | 
				
			||||||
 | 
					        if (!isSendBufferEmpty())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _socket->wakeUpFromPoll(Socket::kSendRequest);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketTransport::sendFragment(wsheader_type::opcode_type type,
 | 
				
			||||||
 | 
					                                          bool fin,
 | 
				
			||||||
 | 
					                                          std::string::const_iterator message_begin,
 | 
				
			||||||
 | 
					                                          std::string::const_iterator message_end,
 | 
				
			||||||
 | 
					                                          bool compress)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        auto message_size = message_end - message_begin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unsigned x = getRandomUnsigned();
 | 
					        unsigned x = getRandomUnsigned();
 | 
				
			||||||
        uint8_t masking_key[4] = {};
 | 
					        uint8_t masking_key[4] = {};
 | 
				
			||||||
@@ -466,7 +639,13 @@ namespace ix
 | 
				
			|||||||
        header.assign(2 +
 | 
					        header.assign(2 +
 | 
				
			||||||
                      (message_size >= 126 ? 2 : 0) +
 | 
					                      (message_size >= 126 ? 2 : 0) +
 | 
				
			||||||
                      (message_size >= 65536 ? 6 : 0) + 4, 0);
 | 
					                      (message_size >= 65536 ? 6 : 0) + 4, 0);
 | 
				
			||||||
        header[0] = 0x80 | type;
 | 
					        header[0] = type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // The fin bit indicate that this is the last fragment. Fin is French for end.
 | 
				
			||||||
 | 
					        if (fin)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            header[0] |= 0x80;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This bit indicate that the frame is compressed
 | 
					        // This bit indicate that the frame is compressed
 | 
				
			||||||
        if (compress)
 | 
					        if (compress)
 | 
				
			||||||
@@ -518,8 +697,6 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Now actually send this data
 | 
					        // Now actually send this data
 | 
				
			||||||
        sendOnSocket();
 | 
					        sendOnSocket();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        return WebSocketSendInfo(true, compressionError, payloadSize, wireSize);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
 | 
					    WebSocketSendInfo WebSocketTransport::sendPing(const std::string& message)
 | 
				
			||||||
@@ -528,9 +705,13 @@ namespace ix
 | 
				
			|||||||
        return sendData(wsheader_type::PING, message, compress);
 | 
					        return sendData(wsheader_type::PING, message, compress);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    WebSocketSendInfo WebSocketTransport::sendBinary(const std::string& message) 
 | 
					    WebSocketSendInfo WebSocketTransport::sendBinary(
 | 
				
			||||||
 | 
					        const std::string& message,
 | 
				
			||||||
 | 
					        const OnProgressCallback& onProgressCallback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return sendData(wsheader_type::BINARY_FRAME, message, _enablePerMessageDeflate);
 | 
					        return sendData(wsheader_type::BINARY_FRAME, message,
 | 
				
			||||||
 | 
					                        _enablePerMessageDeflate, onProgressCallback);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketTransport::sendOnSocket()
 | 
					    void WebSocketTransport::sendOnSocket()
 | 
				
			||||||
@@ -539,7 +720,7 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        while (_txbuf.size())
 | 
					        while (_txbuf.size())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            int ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
 | 
					            ssize_t ret = _socket->send((char*)&_txbuf[0], _txbuf.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
 | 
					            if (ret < 0 && (_socket->getErrno() == EWOULDBLOCK ||
 | 
				
			||||||
                            _socket->getErrno() == EAGAIN))
 | 
					                            _socket->getErrno() == EAGAIN))
 | 
				
			||||||
@@ -558,6 +739,9 @@ namespace ix
 | 
				
			|||||||
                _txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
 | 
					                _txbuf.erase(_txbuf.begin(), _txbuf.begin() + ret);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lck(_lastSendTimePointMutex);
 | 
				
			||||||
 | 
					        _lastSendTimePoint = std::chrono::steady_clock::now();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void WebSocketTransport::close()
 | 
					    void WebSocketTransport::close()
 | 
				
			||||||
@@ -578,8 +762,17 @@ namespace ix
 | 
				
			|||||||
        sendData(wsheader_type::CLOSE, normalClosure, compress);
 | 
					        sendData(wsheader_type::CLOSE, normalClosure, compress);
 | 
				
			||||||
        setReadyState(CLOSING);
 | 
					        setReadyState(CLOSING);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _socket->wakeUpFromPoll();
 | 
					        _socket->wakeUpFromPoll(Socket::kCloseRequest);
 | 
				
			||||||
        _socket->close();
 | 
					        _socket->close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _closeCode = 1000;
 | 
				
			||||||
 | 
					        setReadyState(CLOSED);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t WebSocketTransport::bufferedAmount() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::lock_guard<std::mutex> lock(_txbufMutex);
 | 
				
			||||||
 | 
					        return _txbuf.size();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace ix
 | 
					} // namespace ix
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,7 @@
 | 
				
			|||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <mutex>
 | 
					#include <mutex>
 | 
				
			||||||
#include <atomic>
 | 
					#include <atomic>
 | 
				
			||||||
 | 
					#include <list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "IXWebSocketSendInfo.h"
 | 
					#include "IXWebSocketSendInfo.h"
 | 
				
			||||||
#include "IXWebSocketPerMessageDeflate.h"
 | 
					#include "IXWebSocketPerMessageDeflate.h"
 | 
				
			||||||
@@ -23,6 +24,7 @@
 | 
				
			|||||||
#include "IXWebSocketHttpHeaders.h"
 | 
					#include "IXWebSocketHttpHeaders.h"
 | 
				
			||||||
#include "IXCancellationRequest.h"
 | 
					#include "IXCancellationRequest.h"
 | 
				
			||||||
#include "IXWebSocketHandshake.h"
 | 
					#include "IXWebSocketHandshake.h"
 | 
				
			||||||
 | 
					#include "IXProgressCallback.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -43,7 +45,8 @@ namespace ix
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            MSG,
 | 
					            MSG,
 | 
				
			||||||
            PING,
 | 
					            PING,
 | 
				
			||||||
            PONG
 | 
					            PONG,
 | 
				
			||||||
 | 
					            FRAGMENT
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using OnMessageCallback = std::function<void(const std::string&,
 | 
					        using OnMessageCallback = std::function<void(const std::string&,
 | 
				
			||||||
@@ -57,7 +60,8 @@ namespace ix
 | 
				
			|||||||
        WebSocketTransport();
 | 
					        WebSocketTransport();
 | 
				
			||||||
        ~WebSocketTransport();
 | 
					        ~WebSocketTransport();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions);
 | 
					        void configure(const WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
 | 
				
			||||||
 | 
					                       int hearBeatPeriod);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebSocketInitResult connectToUrl(const std::string& url, // Client
 | 
					        WebSocketInitResult connectToUrl(const std::string& url, // Client
 | 
				
			||||||
                                         int timeoutSecs);
 | 
					                                         int timeoutSecs);
 | 
				
			||||||
@@ -65,17 +69,18 @@ namespace ix
 | 
				
			|||||||
                                            int timeoutSecs);
 | 
					                                            int timeoutSecs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void poll();
 | 
					        void poll();
 | 
				
			||||||
        WebSocketSendInfo sendBinary(const std::string& message);
 | 
					        WebSocketSendInfo sendBinary(const std::string& message,
 | 
				
			||||||
 | 
					                                     const OnProgressCallback& onProgressCallback);
 | 
				
			||||||
        WebSocketSendInfo sendPing(const std::string& message);
 | 
					        WebSocketSendInfo sendPing(const std::string& message);
 | 
				
			||||||
        void close();
 | 
					        void close();
 | 
				
			||||||
        ReadyStateValues getReadyState() const;
 | 
					        ReadyStateValues getReadyState() const;
 | 
				
			||||||
        void setReadyState(ReadyStateValues readyStateValue);
 | 
					        void setReadyState(ReadyStateValues readyStateValue);
 | 
				
			||||||
        void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
 | 
					        void setOnCloseCallback(const OnCloseCallback& onCloseCallback);
 | 
				
			||||||
        void dispatch(const OnMessageCallback& onMessageCallback);
 | 
					        void dispatch(const OnMessageCallback& onMessageCallback);
 | 
				
			||||||
 | 
					        size_t bufferedAmount() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
        std::string _url;
 | 
					        std::string _url;
 | 
				
			||||||
        std::string _origin;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        struct wsheader_type {
 | 
					        struct wsheader_type {
 | 
				
			||||||
            unsigned header_size;
 | 
					            unsigned header_size;
 | 
				
			||||||
@@ -95,13 +100,31 @@ namespace ix
 | 
				
			|||||||
            uint8_t masking_key[4];
 | 
					            uint8_t masking_key[4];
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Buffer for reading from our socket. That buffer is never resized.
 | 
				
			||||||
 | 
					        std::vector<uint8_t> _readbuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Contains all messages that were fetched in the last socket read.
 | 
				
			||||||
 | 
					        // This could be a mix of control messages (Close, Ping, etc...) and
 | 
				
			||||||
 | 
					        // data messages. That buffer
 | 
				
			||||||
        std::vector<uint8_t> _rxbuf;
 | 
					        std::vector<uint8_t> _rxbuf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Contains all messages that are waiting to be sent
 | 
				
			||||||
        std::vector<uint8_t> _txbuf;
 | 
					        std::vector<uint8_t> _txbuf;
 | 
				
			||||||
        mutable std::mutex _txbufMutex;
 | 
					        mutable std::mutex _txbufMutex;
 | 
				
			||||||
        std::vector<uint8_t> _receivedData;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Hold fragments for multi-fragments messages in a list. We support receiving very large
 | 
				
			||||||
 | 
					        // messages (tested messages up to 700M) and we cannot put them in a single
 | 
				
			||||||
 | 
					        // buffer that is resized, as this operation can be slow when a buffer has its
 | 
				
			||||||
 | 
					        // size increased 2 fold, while appending to a list has a fixed cost.
 | 
				
			||||||
 | 
					        std::list<std::vector<uint8_t>> _chunks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Fragments are 32K long
 | 
				
			||||||
 | 
					        static constexpr size_t kChunkSize = 1 << 15;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Underlying TCP socket
 | 
				
			||||||
        std::shared_ptr<Socket> _socket;
 | 
					        std::shared_ptr<Socket> _socket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Hold the state of the connection (OPEN, CLOSED, etc...)
 | 
				
			||||||
        std::atomic<ReadyStateValues> _readyState;
 | 
					        std::atomic<ReadyStateValues> _readyState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OnCloseCallback _onCloseCallback;
 | 
					        OnCloseCallback _onCloseCallback;
 | 
				
			||||||
@@ -110,6 +133,7 @@ namespace ix
 | 
				
			|||||||
        size_t _closeWireSize;
 | 
					        size_t _closeWireSize;
 | 
				
			||||||
        mutable std::mutex _closeDataMutex;
 | 
					        mutable std::mutex _closeDataMutex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Data used for Per Message Deflate compression (with zlib)
 | 
				
			||||||
        WebSocketPerMessageDeflate _perMessageDeflate;
 | 
					        WebSocketPerMessageDeflate _perMessageDeflate;
 | 
				
			||||||
        WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
 | 
					        WebSocketPerMessageDeflateOptions _perMessageDeflateOptions;
 | 
				
			||||||
        std::atomic<bool> _enablePerMessageDeflate;
 | 
					        std::atomic<bool> _enablePerMessageDeflate;
 | 
				
			||||||
@@ -117,9 +141,26 @@ namespace ix
 | 
				
			|||||||
        // Used to cancel dns lookup + socket connect + http upgrade
 | 
					        // Used to cancel dns lookup + socket connect + http upgrade
 | 
				
			||||||
        std::atomic<bool> _requestInitCancellation;
 | 
					        std::atomic<bool> _requestInitCancellation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Optional Heartbeat
 | 
				
			||||||
 | 
					        int _heartBeatPeriod;
 | 
				
			||||||
 | 
					        static const int kDefaultHeartBeatPeriod;
 | 
				
			||||||
 | 
					        const static std::string kHeartBeatPingMessage;
 | 
				
			||||||
 | 
					        mutable std::mutex _lastSendTimePointMutex;
 | 
				
			||||||
 | 
					        std::chrono::time_point<std::chrono::steady_clock> _lastSendTimePoint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // No data was send through the socket for longer that the hearbeat period
 | 
				
			||||||
 | 
					        bool heartBeatPeriodExceeded();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void sendOnSocket();
 | 
					        void sendOnSocket();
 | 
				
			||||||
        WebSocketSendInfo sendData(wsheader_type::opcode_type type,
 | 
					        WebSocketSendInfo sendData(wsheader_type::opcode_type type,
 | 
				
			||||||
                                   const std::string& message,
 | 
					                                   const std::string& message,
 | 
				
			||||||
 | 
					                                   bool compress,
 | 
				
			||||||
 | 
					                                   const OnProgressCallback& onProgressCallback = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void sendFragment(wsheader_type::opcode_type type,
 | 
				
			||||||
 | 
					                          bool fin,
 | 
				
			||||||
 | 
					                          std::string::const_iterator begin,
 | 
				
			||||||
 | 
					                          std::string::const_iterator end,
 | 
				
			||||||
                          bool compress);
 | 
					                          bool compress);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void emitMessage(MessageKind messageKind,
 | 
					        void emitMessage(MessageKind messageKind,
 | 
				
			||||||
@@ -137,5 +178,7 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        unsigned getRandomUnsigned();
 | 
					        unsigned getRandomUnsigned();
 | 
				
			||||||
        void unmaskReceiveBuffer(const wsheader_type& ws);
 | 
					        void unmaskReceiveBuffer(const wsheader_type& ws);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string getMergedChunks() const;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								ixwebsocket/windows/IXSetThreadName_windows.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ixwebsocket/windows/IXSetThreadName_windows.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXSetThreadName_windows.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include "../IXSetThreadName.h"
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void setThreadName(const std::string& name)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // FIXME
 | 
				
			||||||
 | 
					        std::cerr << "setThreadName not implemented on Windows yet" << std::endl;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										33
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								makefile
									
									
									
									
									
								
							@@ -1,14 +1,21 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# This makefile is just used to easily work with docker (linux build)
 | 
					# This makefile is just used to easily work with docker (linux build)
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
all: run
 | 
					all: brew
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					brew:
 | 
				
			||||||
 | 
						mkdir -p build && (cd build ; cmake .. ; make -j install)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: docker
 | 
					.PHONY: docker
 | 
				
			||||||
docker:
 | 
					docker:
 | 
				
			||||||
	docker build -t ws_connect:latest .
 | 
						docker build -t ws:latest .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run: docker
 | 
					run:
 | 
				
			||||||
	docker run --cap-add sys_ptrace -it ws_connect:latest bash
 | 
						docker run --cap-add sys_ptrace -it ws:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# this is helpful to remove trailing whitespaces
 | 
				
			||||||
 | 
					trail:
 | 
				
			||||||
 | 
						sh third_party/remote_trailing_whitespaces.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build:
 | 
					build:
 | 
				
			||||||
	(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
 | 
						(cd examples/satori_publisher ; mkdir -p build ; cd build ; cmake .. ; make)
 | 
				
			||||||
@@ -24,8 +31,24 @@ test_server:
 | 
				
			|||||||
	(cd test && npm i ws && node broadcast-server.js)
 | 
						(cd test && npm i ws && node broadcast-server.js)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# env TEST=Websocket_server make test
 | 
					# env TEST=Websocket_server make test
 | 
				
			||||||
 | 
					# env TEST=Websocket_chat make test
 | 
				
			||||||
 | 
					# env TEST=heartbeat make test
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
	(cd test && sh run.sh)
 | 
						python test/run.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ws_test:
 | 
				
			||||||
 | 
						(cd ws ; sh test_ws.sh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# For the fork that is configured with appveyor
 | 
				
			||||||
 | 
					rebase_upstream:
 | 
				
			||||||
 | 
						git fetch upstream
 | 
				
			||||||
 | 
						git checkout master
 | 
				
			||||||
 | 
						git reset --hard upstream/master
 | 
				
			||||||
 | 
						git push origin master --force
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install_cmake_for_linux:
 | 
				
			||||||
 | 
						mkdir -p /tmp/cmake
 | 
				
			||||||
 | 
						(cd /tmp/cmake ; curl -L -O https://github.com/Kitware/CMake/releases/download/v3.14.0-rc4/cmake-3.14.0-rc4-Linux-x86_64.tar.gz ; tar zxf cmake-3.14.0-rc4-Linux-x86_64.tar.gz)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: test
 | 
					.PHONY: test
 | 
				
			||||||
.PHONY: build
 | 
					.PHONY: build
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,28 +2,48 @@
 | 
				
			|||||||
# Author: Benjamin Sergeant
 | 
					# Author: Benjamin Sergeant
 | 
				
			||||||
# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
					# Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					 | 
				
			||||||
cmake_minimum_required (VERSION 3.4.1)
 | 
					cmake_minimum_required (VERSION 3.4.1)
 | 
				
			||||||
project (ixwebsocket_unittest)
 | 
					project (ixwebsocket_unittest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (CMAKE_CXX_STANDARD 11)
 | 
					set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../third_party/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
 | 
				
			||||||
 | 
					find_package(Sanitizers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set (CMAKE_CXX_STANDARD 14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (NOT WIN32)
 | 
				
			||||||
  option(USE_TLS "Add TLS support" ON)
 | 
					  option(USE_TLS "Add TLS support" ON)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
 | 
					add_subdirectory(${PROJECT_SOURCE_DIR}/.. ixwebsocket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include_directories(
 | 
					include_directories(
 | 
				
			||||||
  ${PROJECT_SOURCE_DIR}/Catch2/single_include
 | 
					  ${PROJECT_SOURCE_DIR}/Catch2/single_include
 | 
				
			||||||
 | 
					  ../third_party/msgpack11
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_executable(ixwebsocket_unittest 
 | 
					# Shared sources
 | 
				
			||||||
 | 
					set (SOURCES 
 | 
				
			||||||
  test_runner.cpp
 | 
					  test_runner.cpp
 | 
				
			||||||
  cmd_websocket_chat.cpp
 | 
					 | 
				
			||||||
  IXWebSocketServerTest.cpp
 | 
					 | 
				
			||||||
  IXTest.cpp
 | 
					  IXTest.cpp
 | 
				
			||||||
  msgpack11.cpp
 | 
					  ../third_party/msgpack11/msgpack11.cpp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  IXDNSLookupTest.cpp
 | 
				
			||||||
 | 
					  IXSocketTest.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Some unittest don't work on windows yet
 | 
				
			||||||
 | 
					if (NOT WIN32)
 | 
				
			||||||
 | 
					  list(APPEND SOURCES 
 | 
				
			||||||
 | 
					    IXWebSocketServerTest.cpp
 | 
				
			||||||
 | 
					    IXWebSocketHeartBeatTest.cpp
 | 
				
			||||||
 | 
					    cmd_websocket_chat.cpp
 | 
				
			||||||
 | 
					    IXWebSocketTestConnectionDisconnection.cpp
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable(ixwebsocket_unittest ${SOURCES})
 | 
				
			||||||
 | 
					add_sanitizers(ixwebsocket_unittest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (APPLE AND USE_TLS)
 | 
					if (APPLE AND USE_TLS)
 | 
				
			||||||
    target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
 | 
					    target_link_libraries(ixwebsocket_unittest "-framework foundation" "-framework security")
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										50
									
								
								test/IXDNSLookupTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								test/IXDNSLookupTest.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXDNSLookupTest.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2018 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "catch.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXTest.h"
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXDNSLookup.h>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST_CASE("dns", "[net]")
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    SECTION("Test resolving a known hostname")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        DNSLookup dnsLookup("www.google.com", 80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string errMsg;
 | 
				
			||||||
 | 
					        struct addrinfo* res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res = dnsLookup.resolve(errMsg, [] { return false; });
 | 
				
			||||||
 | 
					        std::cerr << "Error message: " << errMsg << std::endl;
 | 
				
			||||||
 | 
					        REQUIRE(res != nullptr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SECTION("Test resolving a non-existing hostname")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        DNSLookup dnsLookup("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww", 80);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string errMsg;
 | 
				
			||||||
 | 
					        struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return false; });
 | 
				
			||||||
 | 
					        std::cerr << "Error message: " << errMsg << std::endl;
 | 
				
			||||||
 | 
					        REQUIRE(res == nullptr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SECTION("Test resolving a good hostname, with cancellation")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        DNSLookup dnsLookup("www.google.com", 80, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string errMsg;
 | 
				
			||||||
 | 
					        // The callback returning true means we are requesting cancellation
 | 
				
			||||||
 | 
					        struct addrinfo* res = dnsLookup.resolve(errMsg, [] { return true; });
 | 
				
			||||||
 | 
					        std::cerr << "Error message: " << errMsg << std::endl;
 | 
				
			||||||
 | 
					        REQUIRE(res == nullptr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										99
									
								
								test/IXSocketTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								test/IXSocketTest.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXSocketTest.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2019 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXSocket.h>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXCancellationRequest.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(__APPLE__) or defined(__linux__)
 | 
				
			||||||
 | 
					# ifdef __APPLE__
 | 
				
			||||||
 | 
					#  include <ixwebsocket/IXSocketAppleSSL.h>
 | 
				
			||||||
 | 
					# else
 | 
				
			||||||
 | 
					#  include <ixwebsocket/IXSocketOpenSSL.h>
 | 
				
			||||||
 | 
					# endif
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXTest.h"
 | 
				
			||||||
 | 
					#include "catch.hpp"
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace ix
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    void testSocket(const std::string& host,
 | 
				
			||||||
 | 
					                    int port,
 | 
				
			||||||
 | 
					                    const std::string& request,
 | 
				
			||||||
 | 
					                    std::shared_ptr<Socket> socket,
 | 
				
			||||||
 | 
					                    int expectedStatus,
 | 
				
			||||||
 | 
					                    int timeoutSecs)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string errMsg;
 | 
				
			||||||
 | 
					        static std::atomic<bool> requestInitCancellation(false);
 | 
				
			||||||
 | 
					        auto isCancellationRequested =
 | 
				
			||||||
 | 
					            makeCancellationRequestWithTimeout(timeoutSecs, requestInitCancellation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool success = socket->connect(host, port, errMsg, isCancellationRequested);
 | 
				
			||||||
 | 
					        Logger() << "errMsg: " << errMsg;
 | 
				
			||||||
 | 
					        REQUIRE(success);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "Sending request: " << request
 | 
				
			||||||
 | 
					                  << "to " << host << ":" << port
 | 
				
			||||||
 | 
					                  << std::endl;
 | 
				
			||||||
 | 
					        REQUIRE(socket->writeBytes(request, isCancellationRequested));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto lineResult = socket->readLine(isCancellationRequested);
 | 
				
			||||||
 | 
					        auto lineValid = lineResult.first;
 | 
				
			||||||
 | 
					        auto line = lineResult.second;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::cout << "read error: " << strerror(Socket::getErrno()) << std::endl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        REQUIRE(lineValid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int status = -1;
 | 
				
			||||||
 | 
					        REQUIRE(sscanf(line.c_str(), "HTTP/1.1 %d", &status) == 1);
 | 
				
			||||||
 | 
					        REQUIRE(status == expectedStatus);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST_CASE("socket", "[socket]")
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    SECTION("Connect to google HTTP server. Send GET request without header. Should return 200")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::shared_ptr<Socket> socket(new Socket);
 | 
				
			||||||
 | 
					        std::string host("www.google.com");
 | 
				
			||||||
 | 
					        int port = 80;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::stringstream ss;
 | 
				
			||||||
 | 
					        ss << "GET / HTTP/1.1\r\n";
 | 
				
			||||||
 | 
					        ss << "Host: " << host << "\r\n";
 | 
				
			||||||
 | 
					        ss << "\r\n";
 | 
				
			||||||
 | 
					        std::string request(ss.str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int expectedStatus = 200;
 | 
				
			||||||
 | 
					        int timeoutSecs = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if defined(__APPLE__) or defined(__linux__)
 | 
				
			||||||
 | 
					    SECTION("Connect to google HTTPS server. Send GET request without header. Should return 200")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					# ifdef __APPLE__
 | 
				
			||||||
 | 
					        std::shared_ptr<Socket> socket = std::make_shared<SocketAppleSSL>();
 | 
				
			||||||
 | 
					# else
 | 
				
			||||||
 | 
					        std::shared_ptr<Socket> socket = std::make_shared<SocketOpenSSL>();
 | 
				
			||||||
 | 
					# endif
 | 
				
			||||||
 | 
					        std::string host("www.google.com");
 | 
				
			||||||
 | 
					        int port = 443;
 | 
				
			||||||
 | 
					        std::string request("GET / HTTP/1.1\r\n\r\n");
 | 
				
			||||||
 | 
					        int expectedStatus = 200;
 | 
				
			||||||
 | 
					        int timeoutSecs = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        testSocket(host, port, request, socket, expectedStatus, timeoutSecs);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,18 +6,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "IXTest.h"
 | 
					#include "IXTest.h"
 | 
				
			||||||
#include <ixwebsocket/IXWebSocket.h>
 | 
					#include <ixwebsocket/IXWebSocket.h>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXNetSystem.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <chrono>
 | 
					#include <chrono>
 | 
				
			||||||
#include <thread>
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <fstream>
 | 
					#include <fstream>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
#include <stdlib.h>
 | 
					#include <stdlib.h>
 | 
				
			||||||
 | 
					#include <stack>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    std::atomic<size_t> incomingBytes(0);
 | 
					    std::atomic<size_t> incomingBytes(0);
 | 
				
			||||||
    std::atomic<size_t> outgoingBytes(0);
 | 
					    std::atomic<size_t> outgoingBytes(0);
 | 
				
			||||||
 | 
					    std::mutex Logger::_mutex;
 | 
				
			||||||
 | 
					    std::stack<int> freePorts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void setupWebSocketTrafficTrackerCallback()
 | 
					    void setupWebSocketTrafficTrackerCallback()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -38,9 +43,9 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void reportWebSocketTraffic()
 | 
					    void reportWebSocketTraffic()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        std::cout << incomingBytes << std::endl;
 | 
					        Logger() << incomingBytes;
 | 
				
			||||||
        std::cout << "Incoming bytes: " << incomingBytes << std::endl;
 | 
					        Logger() << "Incoming bytes: " << incomingBytes;
 | 
				
			||||||
        std::cout << "Outgoing bytes: " << outgoingBytes << std::endl;
 | 
					        Logger() << "Outgoing bytes: " << outgoingBytes;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void msleep(int ms)
 | 
					    void msleep(int ms)
 | 
				
			||||||
@@ -58,4 +63,89 @@ namespace ix
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return std::to_string(seconds);
 | 
					        return std::to_string(seconds);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void log(const std::string& msg)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Logger() << msg;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int getAnyFreePortSimple()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        static int defaultPort = 8090;
 | 
				
			||||||
 | 
					        return defaultPort++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int getAnyFreePort()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int defaultPort = 8090;
 | 
				
			||||||
 | 
					        int sockfd;
 | 
				
			||||||
 | 
					        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log("Cannot compute a free port. socket error.");
 | 
				
			||||||
 | 
					            return defaultPort;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int enable = 1;
 | 
				
			||||||
 | 
					        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
 | 
				
			||||||
 | 
					                       (char*) &enable, sizeof(enable)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log("Cannot compute a free port. setsockopt error.");
 | 
				
			||||||
 | 
					            return defaultPort;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Bind to port 0. This is the standard way to get a free port.
 | 
				
			||||||
 | 
					        struct sockaddr_in server; // server address information
 | 
				
			||||||
 | 
					        server.sin_family = AF_INET;
 | 
				
			||||||
 | 
					        server.sin_port   = htons(0);
 | 
				
			||||||
 | 
					        server.sin_addr.s_addr = inet_addr("127.0.0.1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log("Cannot compute a free port. bind error.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::close(sockfd);
 | 
				
			||||||
 | 
					            return defaultPort;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        struct sockaddr_in sa; // server address information
 | 
				
			||||||
 | 
					        unsigned int len;
 | 
				
			||||||
 | 
					        if (getsockname(sockfd, (struct sockaddr *) &sa, &len) < 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log("Cannot compute a free port. getsockname error.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ::close(sockfd);
 | 
				
			||||||
 | 
					            return defaultPort;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int port = ntohs(sa.sin_port);
 | 
				
			||||||
 | 
					        ::close(sockfd);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return port;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int getFreePort()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        while (true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					#if defined(__has_feature)
 | 
				
			||||||
 | 
					# if __has_feature(address_sanitizer)
 | 
				
			||||||
 | 
					            int port = getAnyFreePortSimple();
 | 
				
			||||||
 | 
					# else
 | 
				
			||||||
 | 
					            int port = getAnyFreePort();
 | 
				
			||||||
 | 
					# endif
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					            int port = getAnyFreePort();
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            // Only port above 1024 can be used by non root users, but for some
 | 
				
			||||||
 | 
					            // reason I got port 7 returned with macOS when binding on port 0...
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            if (port > 1024)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return port;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,8 @@
 | 
				
			|||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <vector>
 | 
					#include <vector>
 | 
				
			||||||
#include <sstream>
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ix
 | 
					namespace ix
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -21,4 +23,35 @@ namespace ix
 | 
				
			|||||||
    // Record and report websocket traffic
 | 
					    // Record and report websocket traffic
 | 
				
			||||||
    void setupWebSocketTrafficTrackerCallback();
 | 
					    void setupWebSocketTrafficTrackerCallback();
 | 
				
			||||||
    void reportWebSocketTraffic();
 | 
					    void reportWebSocketTraffic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    struct Logger
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            Logger& operator<<(const std::string& msg)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::lock_guard<std::mutex> lock(_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                std::cerr << msg;
 | 
				
			||||||
 | 
					                std::cerr << std::endl;
 | 
				
			||||||
 | 
					                return *this;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            template <typename T>
 | 
				
			||||||
 | 
					            Logger& operator<<(T const& obj)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::lock_guard<std::mutex> lock(_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                std::cerr << obj;
 | 
				
			||||||
 | 
					                std::cerr << std::endl;
 | 
				
			||||||
 | 
					                return *this;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            static std::mutex _mutex;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void log(const std::string& msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool computeFreePorts(int count);
 | 
				
			||||||
 | 
					    int getFreePort();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										222
									
								
								test/IXWebSocketHeartBeatTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								test/IXWebSocketHeartBeatTest.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 *  IXWebSocketHeartBeatTest.cpp
 | 
				
			||||||
 | 
					 *  Author: Benjamin Sergeant
 | 
				
			||||||
 | 
					 *  Copyright (c) 2019 Machine Zone. All rights reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include <queue>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXWebSocket.h>
 | 
				
			||||||
 | 
					#include <ixwebsocket/IXWebSocketServer.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "IXTest.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "catch.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace ix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    class WebSocketClient
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            WebSocketClient(int port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            void subscribe(const std::string& channel);
 | 
				
			||||||
 | 
					            void start();
 | 
				
			||||||
 | 
					            void stop();
 | 
				
			||||||
 | 
					            bool isReady() const;
 | 
				
			||||||
 | 
					            void sendMessage(const std::string& text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private:
 | 
				
			||||||
 | 
					            ix::WebSocket _webSocket;
 | 
				
			||||||
 | 
					            int _port;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WebSocketClient::WebSocketClient(int port)
 | 
				
			||||||
 | 
					        : _port(port)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool WebSocketClient::isReady() const
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return _webSocket.getReadyState() == ix::WebSocket_ReadyState_Open;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketClient::stop()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _webSocket.stop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketClient::start()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        std::string url;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            std::stringstream ss;
 | 
				
			||||||
 | 
					            ss << "ws://localhost:"
 | 
				
			||||||
 | 
					               << _port
 | 
				
			||||||
 | 
					               << "/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            url = ss.str();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _webSocket.setUrl(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // The important bit for this test.
 | 
				
			||||||
 | 
					        // Set a 1 second hearbeat ; if no traffic is present on the connection for 1 second
 | 
				
			||||||
 | 
					        // a ping message will be sent by the client.
 | 
				
			||||||
 | 
					        _webSocket.setHeartBeatPeriod(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::stringstream ss;
 | 
				
			||||||
 | 
					        log(std::string("Connecting to url: ") + url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _webSocket.setOnMessageCallback(
 | 
				
			||||||
 | 
					            [](ix::WebSocketMessageType messageType,
 | 
				
			||||||
 | 
					               const std::string& str,
 | 
				
			||||||
 | 
					               size_t wireSize,
 | 
				
			||||||
 | 
					               const ix::WebSocketErrorInfo& error,
 | 
				
			||||||
 | 
					               const ix::WebSocketOpenInfo& openInfo,
 | 
				
			||||||
 | 
					                   const ix::WebSocketCloseInfo& closeInfo)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                std::stringstream ss;
 | 
				
			||||||
 | 
					                if (messageType == ix::WebSocket_MessageType_Open)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    log("client connected");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Close)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    log("client disconnected");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Error)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Error ! " << error.reason;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Pong)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Received pong message " << str;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Ping)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Received ping message " << str;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (messageType == ix::WebSocket_MessageType_Message)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Received message " << str;
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ss << "Invalid ix::WebSocketMessageType";
 | 
				
			||||||
 | 
					                    log(ss.str());
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _webSocket.start();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void WebSocketClient::sendMessage(const std::string& text)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _webSocket.send(text);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool startServer(ix::WebSocketServer& server, std::atomic<int>& receivedPingMessages)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // A dev/null server
 | 
				
			||||||
 | 
					        server.setOnConnectionCallback(
 | 
				
			||||||
 | 
					            [&server, &receivedPingMessages](std::shared_ptr<ix::WebSocket> webSocket)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                webSocket->setOnMessageCallback(
 | 
				
			||||||
 | 
					                    [webSocket, &server, &receivedPingMessages](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)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            Logger() << "New server connection";
 | 
				
			||||||
 | 
					                            Logger() << "Uri: " << openInfo.uri;
 | 
				
			||||||
 | 
					                            Logger() << "Headers:";
 | 
				
			||||||
 | 
					                            for (auto it : openInfo.headers)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                Logger() << it.first << ": " << it.second;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else if (messageType == ix::WebSocket_MessageType_Close)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            log("Server closed connection");
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else if (messageType == ix::WebSocket_MessageType_Ping)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            log("Server received a ping");
 | 
				
			||||||
 | 
					                            receivedPingMessages++;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        auto res = server.listen();
 | 
				
			||||||
 | 
					        if (!res.first)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log(res.second);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        server.start();
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST_CASE("Websocket_heartbeat", "[heartbeat]")
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    SECTION("Make sure that ping messages are sent during heartbeat.")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ix::setupWebSocketTrafficTrackerCallback();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int port = getFreePort();
 | 
				
			||||||
 | 
					        ix::WebSocketServer server(port);
 | 
				
			||||||
 | 
					        std::atomic<int> serverReceivedPingMessages(0);
 | 
				
			||||||
 | 
					        REQUIRE(startServer(server, serverReceivedPingMessages));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::string session = ix::generateSessionId();
 | 
				
			||||||
 | 
					        WebSocketClient webSocketClientA(port);
 | 
				
			||||||
 | 
					        WebSocketClient webSocketClientB(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webSocketClientA.start();
 | 
				
			||||||
 | 
					        webSocketClientB.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Wait for all chat instance to be ready
 | 
				
			||||||
 | 
					        while (true)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (webSocketClientA.isReady() && webSocketClientB.isReady()) break;
 | 
				
			||||||
 | 
					            ix::msleep(10);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        REQUIRE(server.getClients().size() == 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ix::msleep(900);
 | 
				
			||||||
 | 
					        webSocketClientB.sendMessage("hello world");
 | 
				
			||||||
 | 
					        ix::msleep(900);
 | 
				
			||||||
 | 
					        webSocketClientB.sendMessage("hello world");
 | 
				
			||||||
 | 
					        ix::msleep(900);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        webSocketClientA.stop();
 | 
				
			||||||
 | 
					        webSocketClientB.stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        REQUIRE(serverReceivedPingMessages >= 2);
 | 
				
			||||||
 | 
					        REQUIRE(serverReceivedPingMessages <= 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Give us 500ms for the server to notice that clients went away
 | 
				
			||||||
 | 
					        ix::msleep(500);
 | 
				
			||||||
 | 
					        REQUIRE(server.getClients().size() == 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ix::reportWebSocketTraffic();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user