Add websocket client
This commit is contained in:
@ -14,6 +14,7 @@ src/Http/HttpUtils.cpp
|
||||
src/Http/HttpClient.cpp
|
||||
src/Http/HttpStream.cpp
|
||||
src/Http/ContentDisposition.cpp
|
||||
src/Http/WebSocket.cpp
|
||||
src/Mail/Smtp.cpp
|
||||
src/Serialization/Json.cpp
|
||||
src/Streams/FileStream.cpp
|
||||
@ -74,6 +75,16 @@ endif()
|
||||
set(MBEDTLS_DIR "" CACHE PATH "Mbed tls directory")
|
||||
|
||||
function(TESSESFRAMEWORK_LINKDEPS TessesFramework_TARGET)
|
||||
target_include_directories(${TessesFramework_TARGET}
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
target_include_directories(${TessesFramework_TARGET}
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
if(WIN32)
|
||||
target_link_libraries(${TessesFramework_TARGET} PUBLIC iphlpapi)
|
||||
endif()
|
||||
@ -100,16 +111,7 @@ target_link_directories(${TessesFramework_TARGET} PUBLIC ${MBEDTLS_DIR}/lib)
|
||||
endif()
|
||||
target_link_libraries(${TessesFramework_TARGET} PUBLIC mbedtls mbedx509 mbedcrypto)
|
||||
endif()
|
||||
target_include_directories(${TessesFramework_TARGET}
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
target_include_directories(${TessesFramework_TARGET}
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
|
||||
if("${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoWii" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "NintendoGameCube")
|
||||
target_link_libraries(${TessesFramework_TARGET} PUBLIC fat)
|
||||
endif()
|
||||
@ -203,7 +205,10 @@ if(TESSESFRAMEWORK_ENABLE_EXAMPLES)
|
||||
|
||||
add_executable(printjsondecodedemoji examples/printjsondecodedemoji.cpp)
|
||||
target_link_libraries(printjsondecodedemoji PUBLIC tessesframework)
|
||||
|
||||
|
||||
add_executable(wsecho examples/wsecho.cpp)
|
||||
target_link_libraries(wsecho PUBLIC tessesframework)
|
||||
|
||||
endif()
|
||||
|
||||
if(TESSESFRAMEWORK_ENABLE_APPS)
|
||||
|
||||
@ -10,12 +10,8 @@ int main(int argc, char** argv)
|
||||
printf("USAGE: %s <url> <path>\n",argv[0]);
|
||||
return 1;
|
||||
}
|
||||
FileStream strm(argv[2],"wb");
|
||||
|
||||
HttpRequest req;
|
||||
req.url = argv[1];
|
||||
HttpResponse resp(req);
|
||||
resp.CopyToStream(&strm);
|
||||
std::string path = argv[2];
|
||||
Tesses::Framework::Http::DownloadToFileSimple(argv[1],path);
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
48
examples/wsecho.cpp
Normal file
48
examples/wsecho.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "TessesFramework/Http/HttpClient.hpp"
|
||||
#include "TessesFramework/Threading/Thread.hpp"
|
||||
#include <iostream>
|
||||
using namespace Tesses::Framework::Http;
|
||||
|
||||
class WebSocketConn : public WebSocketConnection
|
||||
{
|
||||
|
||||
public:
|
||||
void OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping, std::function<void()> close)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
std::cout << "> ";
|
||||
std::string req;
|
||||
std::getline(std::cin, req);
|
||||
if(req == "exit")
|
||||
break;
|
||||
else
|
||||
SendWebSocketMessage(sendMessage,req);
|
||||
}
|
||||
close();
|
||||
|
||||
}
|
||||
void OnReceive(WebSocketMessage& message)
|
||||
{
|
||||
std::cout << "Message: " << message.ToString() << std::endl;
|
||||
|
||||
|
||||
}
|
||||
void OnClose(bool clean)
|
||||
{
|
||||
std::cout << (clean ? "Closed cleanly" : "Closed unclean") << std::endl;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Tesses::Framework::TF_Init();
|
||||
HttpDictionary reqHeaders;
|
||||
WebSocketConn conn;
|
||||
WebSocketClient("ws://echo.websocket.org/",reqHeaders,conn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
#include "../Streams/Stream.hpp"
|
||||
#include "HttpUtils.hpp"
|
||||
|
||||
#include "WebSocket.hpp"
|
||||
#include "TessesFramework/Filesystem/LocalFS.hpp"
|
||||
#include "TessesFramework/Filesystem/VFSFix.hpp"
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
|
||||
@ -64,9 +66,19 @@ namespace Tesses::Framework::Http
|
||||
std::string ReadAsString();
|
||||
Tesses::Framework::Streams::Stream* ReadAsStream();
|
||||
void CopyToStream(Tesses::Framework::Streams::Stream* strm);
|
||||
|
||||
Tesses::Framework::Streams::Stream* GetInternalStream();
|
||||
~HttpResponse();
|
||||
};
|
||||
|
||||
void DownloadToStreamSimple(std::string url, Tesses::Framework::Streams::Stream* strm);
|
||||
void DownloadToStreamSimple(std::string url, Tesses::Framework::Streams::Stream& strm);
|
||||
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFS* vfs, Tesses::Framework::Filesystem::VFSPath path);
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFS& vfs, Tesses::Framework::Filesystem::VFSPath path);
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFSPath path);
|
||||
std::string DownloadToStringSimple(std::string url);
|
||||
bool WebSocketClientSuccessDefault(HttpDictionary& dict,bool v);
|
||||
void WebSocketClient(std::string url, HttpDictionary& requestHeaders, WebSocketConnection& wsc, std::function<bool(HttpDictionary&,bool)> cb=WebSocketClientSuccessDefault);
|
||||
void WebSocketClient(std::string url, HttpDictionary& requestHeaders, WebSocketConnection* wsc, std::function<bool(HttpDictionary&,bool)> cb=WebSocketClientSuccessDefault);
|
||||
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
#include "../Threading/Thread.hpp"
|
||||
#include "../Date/Date.hpp"
|
||||
#include <unordered_map>
|
||||
#include "WebSocket.hpp"
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
class ServerContextData {
|
||||
@ -12,36 +13,7 @@ namespace Tesses::Framework::Http
|
||||
virtual ~ServerContextData();
|
||||
};
|
||||
|
||||
class WebSocketMessage {
|
||||
public:
|
||||
std::vector<uint8_t> data;
|
||||
bool isBinary;
|
||||
WebSocketMessage();
|
||||
WebSocketMessage(std::vector<uint8_t> data);
|
||||
WebSocketMessage(const void* data, size_t len);
|
||||
WebSocketMessage(std::string message);
|
||||
std::string ToString();
|
||||
};
|
||||
|
||||
class WebSocketConnection {
|
||||
public:
|
||||
virtual void OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping)=0;
|
||||
virtual void OnReceive(WebSocketMessage& message)=0;
|
||||
virtual void OnClose(bool clean)=0;
|
||||
virtual ~WebSocketConnection();
|
||||
};
|
||||
class CallbackWebSocketConnection : public WebSocketConnection {
|
||||
public:
|
||||
std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>)> onOpen;
|
||||
std::function<void(WebSocketMessage&)> onReceive;
|
||||
std::function<void(bool)> onClose;
|
||||
CallbackWebSocketConnection();
|
||||
CallbackWebSocketConnection(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose);
|
||||
|
||||
void OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping);
|
||||
void OnReceive(WebSocketMessage& message);
|
||||
void OnClose(bool clean);
|
||||
};
|
||||
|
||||
|
||||
class ServerContext {
|
||||
bool sent;
|
||||
@ -86,7 +58,7 @@ namespace Tesses::Framework::Http
|
||||
ServerContext& WithMimeType(std::string mime);
|
||||
ServerContext& WithContentDisposition(std::string filename, bool isInline);
|
||||
ServerContext& WriteHeaders();
|
||||
void StartWebSocketSession(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose);
|
||||
void StartWebSocketSession(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose);
|
||||
void StartWebSocketSession(WebSocketConnection& connection);
|
||||
|
||||
template<class T>
|
||||
|
||||
43
include/TessesFramework/Http/WebSocket.hpp
Normal file
43
include/TessesFramework/Http/WebSocket.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
class WebSocketMessage {
|
||||
public:
|
||||
std::vector<uint8_t> data;
|
||||
bool isBinary;
|
||||
WebSocketMessage();
|
||||
WebSocketMessage(std::vector<uint8_t> data);
|
||||
WebSocketMessage(const void* data, size_t len);
|
||||
WebSocketMessage(std::string message);
|
||||
std::string ToString();
|
||||
|
||||
|
||||
|
||||
};
|
||||
void SendWebSocketMessage(std::function<void(WebSocketMessage&)> cb, std::string text);
|
||||
class WebSocketConnection {
|
||||
|
||||
public:
|
||||
virtual void OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping,std::function<void()> close)=0;
|
||||
virtual void OnReceive(WebSocketMessage& message)=0;
|
||||
virtual void OnClose(bool clean)=0;
|
||||
virtual ~WebSocketConnection();
|
||||
};
|
||||
class CallbackWebSocketConnection : public WebSocketConnection {
|
||||
public:
|
||||
std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>,std::function<void()>)> onOpen;
|
||||
std::function<void(WebSocketMessage&)> onReceive;
|
||||
std::function<void(bool)> onClose;
|
||||
CallbackWebSocketConnection();
|
||||
CallbackWebSocketConnection(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose);
|
||||
|
||||
void OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping,std::function<void()> closeFn);
|
||||
void OnReceive(WebSocketMessage& message);
|
||||
void OnClose(bool clean);
|
||||
};
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
#include "TessesFramework/Http/HttpClient.hpp"
|
||||
#include "TessesFramework/Crypto/ClientTLSStream.hpp"
|
||||
#include "TessesFramework/Crypto/MbedHelpers.hpp"
|
||||
#include "TessesFramework/Streams/NetworkStream.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamWriter.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamReader.hpp"
|
||||
#include "TessesFramework/Http/HttpStream.hpp"
|
||||
#include "TessesFramework/Streams/BufferedStream.hpp"
|
||||
#include "TessesFramework/Threading/Mutex.hpp"
|
||||
#include "TessesFramework/Threading/Thread.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
using Stream = Tesses::Framework::Streams::Stream;
|
||||
@ -90,7 +94,6 @@ namespace Tesses::Framework::Http
|
||||
}
|
||||
|
||||
std::string request = method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
||||
|
||||
for(auto headers : requestHeaders.kvp)
|
||||
{
|
||||
for(auto item : headers.second)
|
||||
@ -117,11 +120,11 @@ namespace Tesses::Framework::Http
|
||||
|
||||
Stream* HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle)
|
||||
{
|
||||
if(uri.scheme == "http:")
|
||||
if(uri.scheme == "http:" || uri.scheme == "ws:")
|
||||
{
|
||||
return new NetworkStream(uri.host,uri.GetPort(),false,false,false);
|
||||
}
|
||||
else if(uri.scheme == "https:")
|
||||
else if(uri.scheme == "https:" || uri.scheme == "wss:")
|
||||
{
|
||||
auto netStrm = new NetworkStream(uri.host,uri.GetPort(),false,false,false);
|
||||
if(netStrm == nullptr) return netStrm;
|
||||
@ -130,6 +133,10 @@ namespace Tesses::Framework::Http
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
Tesses::Framework::Streams::Stream* HttpResponse::GetInternalStream()
|
||||
{
|
||||
return this->handleStrm;
|
||||
}
|
||||
|
||||
HttpResponse::~HttpResponse()
|
||||
{
|
||||
@ -276,4 +283,370 @@ namespace Tesses::Framework::Http
|
||||
|
||||
return new HttpStream(this->handleStrm,false,length,true,version=="HTTP/1.1");
|
||||
}
|
||||
|
||||
void DownloadToStreamSimple(std::string url, Tesses::Framework::Streams::Stream* strm)
|
||||
{
|
||||
if(strm == nullptr) throw TextException("strm is null");
|
||||
HttpRequest request;
|
||||
request.url = url;
|
||||
request.followRedirects=true;
|
||||
request.method = "GET";
|
||||
HttpResponse response(request);
|
||||
if(response.statusCode < 200 || response.statusCode > 299) throw TextException("Status code does not indicate success: " + std::to_string(response.statusCode) + " " + HttpUtils::StatusCodeString(response.statusCode));
|
||||
response.CopyToStream(strm);
|
||||
}
|
||||
void DownloadToStreamSimple(std::string url, Tesses::Framework::Streams::Stream& strm)
|
||||
{
|
||||
DownloadToStreamSimple(url,&strm);
|
||||
}
|
||||
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFS* vfs, Tesses::Framework::Filesystem::VFSPath path)
|
||||
{
|
||||
if(vfs == nullptr) throw TextException("vfs is null");
|
||||
auto strm = vfs->OpenFile(path,"wb");
|
||||
if(strm == nullptr) throw TextException("strm is null");
|
||||
DownloadToStreamSimple(url,strm);
|
||||
delete strm;
|
||||
}
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFS& vfs, Tesses::Framework::Filesystem::VFSPath path)
|
||||
{
|
||||
auto strm = vfs.OpenFile(path,"wb");
|
||||
if(strm == nullptr) throw TextException("strm is null");
|
||||
DownloadToStreamSimple(url,strm);
|
||||
delete strm;
|
||||
}
|
||||
void DownloadToFileSimple(std::string url, Tesses::Framework::Filesystem::VFSPath path)
|
||||
{
|
||||
DownloadToFileSimple(url,Tesses::Framework::Filesystem::LocalFS,path);
|
||||
}
|
||||
std::string DownloadToStringSimple(std::string url)
|
||||
{
|
||||
HttpRequest request;
|
||||
request.url = url;
|
||||
request.followRedirects=true;
|
||||
request.method = "GET";
|
||||
HttpResponse response(request);
|
||||
if(response.statusCode < 200 || response.statusCode > 299) throw TextException("Status code does not indicate success: " + std::to_string(response.statusCode) + " " + HttpUtils::StatusCodeString(response.statusCode));
|
||||
return response.ReadAsString();
|
||||
}
|
||||
bool WebSocketClientSuccessDefault(HttpDictionary& dict,bool v)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void WebSocketClient(std::string url, HttpDictionary& requestHeaders, WebSocketConnection& wsc, std::function<bool(HttpDictionary&,bool)> cb)
|
||||
{
|
||||
WebSocketClient(url,requestHeaders, &wsc,cb);
|
||||
}
|
||||
|
||||
class WSClient {
|
||||
public:
|
||||
std::atomic<bool> closed;
|
||||
Threading::Mutex mtx;
|
||||
|
||||
WebSocketConnection* conn;
|
||||
Stream* strm;
|
||||
void close()
|
||||
{
|
||||
mtx.Lock();
|
||||
this->closed=true;
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0x9;
|
||||
strm->WriteByte(firstByte);
|
||||
strm->WriteByte(0);
|
||||
|
||||
mtx.Unlock();
|
||||
|
||||
}
|
||||
void write_len_bytes(uint64_t len)
|
||||
{
|
||||
if(len < 126)
|
||||
{
|
||||
strm->WriteByte((uint8_t)len | (uint8_t)128U);
|
||||
}
|
||||
else if(len < 65535)
|
||||
{
|
||||
uint8_t b[3];
|
||||
b[0] = 254;
|
||||
b[1] = (uint8_t)(len >> 8);
|
||||
b[2] = (uint8_t)len;
|
||||
|
||||
strm->WriteBlock(b,sizeof(b));
|
||||
}
|
||||
else {
|
||||
|
||||
uint8_t b[9];
|
||||
b[0] = 255;
|
||||
|
||||
b[1] = (uint8_t)(len >> 56);
|
||||
b[2] = (uint8_t)(len >> 48);
|
||||
b[3] = (uint8_t)(len >> 40);
|
||||
b[4] = (uint8_t)(len >> 32);
|
||||
b[5] = (uint8_t)(len >> 24);
|
||||
b[6] = (uint8_t)(len >> 16);
|
||||
b[7] = (uint8_t)(len >> 8);
|
||||
b[8] = (uint8_t)len;
|
||||
|
||||
strm->WriteBlock(b,sizeof(b));
|
||||
}
|
||||
}
|
||||
uint64_t get_long()
|
||||
{
|
||||
uint8_t buff[8];
|
||||
if(strm->ReadBlock(buff,sizeof(buff)) != sizeof(buff)) return 0;
|
||||
|
||||
uint64_t v = 0;
|
||||
v |= (uint64_t)buff[0] << 56;
|
||||
v |= (uint64_t)buff[1] << 48;
|
||||
v |= (uint64_t)buff[2] << 40;
|
||||
v |= (uint64_t)buff[3] << 32;
|
||||
v |= (uint64_t)buff[4] << 24;
|
||||
v |= (uint64_t)buff[5] << 16;
|
||||
v |= (uint64_t)buff[6] << 8;
|
||||
v |= (uint64_t)buff[7];
|
||||
return v;
|
||||
}
|
||||
uint16_t get_short()
|
||||
{
|
||||
uint8_t buff[2];
|
||||
if(strm->ReadBlock(buff,sizeof(buff)) != sizeof(buff)) return 0;
|
||||
|
||||
uint16_t v = 0;
|
||||
v |= (uint16_t)buff[0] << 8;
|
||||
v |= (uint16_t)buff[1];
|
||||
return v;
|
||||
}
|
||||
void send_msg(WebSocketMessage* msg)
|
||||
{
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t opcode = msg->isBinary ? 0x2 : 0x1;
|
||||
|
||||
size_t lengthLastByte = msg->data.size() % 4096;
|
||||
size_t fullPackets = (msg->data.size() - lengthLastByte) / 4096;
|
||||
size_t noPackets = lengthLastByte > 0 ? fullPackets+1 : fullPackets;
|
||||
size_t offset = 0;
|
||||
std::vector<uint8_t> mask;
|
||||
mask.resize(4);
|
||||
for(size_t i = 0; i < noPackets; i++)
|
||||
{
|
||||
bool fin = i == noPackets - 1;
|
||||
uint8_t finField = fin ? 0b10000000 : 0;
|
||||
uint8_t opcode2 = i == 0 ? opcode : 0;
|
||||
uint8_t firstByte = finField | (opcode2 & 0xF);
|
||||
|
||||
size_t len = std::min((size_t)4096,msg->data.size()- offset);
|
||||
|
||||
strm->WriteByte(firstByte);
|
||||
write_len_bytes((uint64_t)len);
|
||||
|
||||
Crypto::RandomBytes(mask,"Mask it");
|
||||
strm->WriteBlock(mask.data(),mask.size());
|
||||
for(size_t i = offset; i < offset+len; i++)
|
||||
msg->data[i] ^= mask[i%4];
|
||||
strm->WriteBlock(msg->data.data() + offset,len);
|
||||
for(size_t i = offset; i < offset+len; i++)
|
||||
msg->data[i] ^= mask[i%4];
|
||||
offset += len;
|
||||
}
|
||||
mtx.Unlock();
|
||||
}
|
||||
void ping_send(std::vector<uint8_t>& pData)
|
||||
{
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0x9;
|
||||
strm->WriteByte(firstByte);
|
||||
write_len_bytes((uint64_t)pData.size());
|
||||
std::vector<uint8_t> mask;
|
||||
mask.resize(4);
|
||||
Crypto::RandomBytes(mask,"Mask it");
|
||||
strm->WriteBlock(mask.data(),mask.size());
|
||||
for(size_t i = 0; i < pData.size(); i++)
|
||||
pData[i] ^= mask[i%4];
|
||||
strm->WriteBlock(pData.data(),pData.size());
|
||||
for(size_t i = 0; i < pData.size(); i++)
|
||||
pData[i] ^= mask[i%4];
|
||||
mtx.Unlock();
|
||||
}
|
||||
void pong_send(std::vector<uint8_t>& pData)
|
||||
{
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0xA;
|
||||
strm->WriteByte(firstByte);
|
||||
write_len_bytes((uint64_t)pData.size());
|
||||
strm->WriteBlock(pData.data(),pData.size());
|
||||
mtx.Unlock();
|
||||
}
|
||||
bool read_packet(uint8_t len,std::vector<uint8_t>& data)
|
||||
{
|
||||
|
||||
uint8_t realLen=len & 127;
|
||||
bool masked=(len & 0b10000000) > 0;
|
||||
uint64_t reallen2 = realLen >= 126 ? realLen > 126 ? get_long() : get_short() : realLen;
|
||||
uint8_t mask[4];
|
||||
if(masked)
|
||||
{
|
||||
if(strm->ReadBlock(mask,sizeof(mask)) != sizeof(mask)) return false;
|
||||
}
|
||||
size_t offset = data.size();
|
||||
data.resize(offset+(size_t)reallen2);
|
||||
if(data.size() < ((uint64_t)offset+reallen2)) return false;
|
||||
if(strm->ReadBlock(data.data()+offset,(size_t)reallen2) != (size_t)reallen2) return false;
|
||||
if(masked)
|
||||
{
|
||||
for(size_t i = 0; i < (size_t)reallen2; i++)
|
||||
{
|
||||
data[i+offset] ^= mask[i%4];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WSClient(Tesses::Framework::Streams::Stream* strm,WebSocketConnection* conn)
|
||||
{
|
||||
this->strm = strm;
|
||||
this->conn = conn;
|
||||
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
this->closed=false;
|
||||
bool hasMessage =false;
|
||||
|
||||
WebSocketMessage message;
|
||||
message.isBinary=false;
|
||||
message.data={};
|
||||
|
||||
|
||||
while(!strm->EndOfStream())
|
||||
{
|
||||
|
||||
uint8_t frame_start[2];
|
||||
if(strm->ReadBlock(frame_start,2) != 2) return;
|
||||
|
||||
|
||||
|
||||
uint8_t opcode = frame_start[0] & 0xF;
|
||||
bool fin = (frame_start[0] & 0b10000000) > 0;
|
||||
switch(opcode)
|
||||
{
|
||||
case 0x0:
|
||||
if(!hasMessage) break;
|
||||
read_packet(frame_start[1], message.data);
|
||||
break;
|
||||
case 0x1:
|
||||
case 0x2:
|
||||
hasMessage=true;
|
||||
message.data = {};
|
||||
message.isBinary = opcode == 0x2;
|
||||
|
||||
read_packet(frame_start[1], message.data);
|
||||
break;
|
||||
case 0x8:
|
||||
if(!this->closed) this->close();
|
||||
this->conn->OnClose(true);
|
||||
return;
|
||||
case 0x9:
|
||||
{
|
||||
std::vector<uint8_t> b;
|
||||
read_packet(frame_start[1],b);
|
||||
pong_send(b);
|
||||
}
|
||||
break;
|
||||
case 0xA:
|
||||
{
|
||||
std::vector<uint8_t> b;
|
||||
read_packet(frame_start[1],b);
|
||||
}
|
||||
}
|
||||
if(fin && hasMessage)
|
||||
{
|
||||
hasMessage=false;
|
||||
this->conn->OnReceive(message);
|
||||
message.data={};
|
||||
}
|
||||
}
|
||||
this->conn->OnClose(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
void WebSocketClient(std::string url, HttpDictionary& requestHeaders, WebSocketConnection* wsc, std::function<bool(HttpDictionary&,bool)> cb)
|
||||
{
|
||||
HttpRequest req;
|
||||
req.url = url;
|
||||
req.requestHeaders = requestHeaders;
|
||||
req.followRedirects=true;
|
||||
|
||||
std::string hash = "";
|
||||
|
||||
if(Crypto::HaveCrypto())
|
||||
{
|
||||
std::vector<uint8_t> code;
|
||||
code.resize(16);
|
||||
Crypto::RandomBytes(code,"Tesses::Framework::Http::WebSocketClient");
|
||||
hash = Crypto::Base64_Encode(code);
|
||||
}
|
||||
|
||||
req.requestHeaders.SetValue("Sec-WebSocket-Key", hash);
|
||||
req.requestHeaders.SetValue("Sec-WebSocket-Version","13");
|
||||
req.requestHeaders.SetValue("Upgrade","websocket");
|
||||
req.requestHeaders.SetValue("Connection","Upgrade");
|
||||
|
||||
HttpResponse resp(req);
|
||||
|
||||
std::string accept="0uytbGS5rkQTa6saiHK4AQ==";
|
||||
|
||||
if(resp.statusCode != 101 || !resp.responseHeaders.TryGetFirst("Sec-WebSocket-Accept",accept) || !resp.responseHeaders.AnyEquals("Connection","Upgrade") || !resp.responseHeaders.AnyEquals("Upgrade","websocket"))
|
||||
{
|
||||
cb(resp.responseHeaders,false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(Crypto::HaveCrypto())
|
||||
{
|
||||
std::string txt=hash;
|
||||
hash += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
if(Crypto::Base64_Encode(Crypto::Sha1::ComputeHash((const uint8_t*)hash.data(),hash.size())) != accept)
|
||||
{
|
||||
cb(resp.responseHeaders,false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if(accept != "Swjoe56alHg6mvKmKyiDd3tpNqc=")
|
||||
{
|
||||
cb(resp.responseHeaders,false);
|
||||
return;
|
||||
}
|
||||
if(!cb(resp.responseHeaders,true))
|
||||
{
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WSClient clt(resp.GetInternalStream(),wsc);
|
||||
|
||||
Threading::Thread thrd([&clt,wsc]()->void{
|
||||
try {
|
||||
wsc->OnOpen([&clt](WebSocketMessage& msg)->void {
|
||||
clt.send_msg(&msg);
|
||||
},[&clt]()->void {
|
||||
std::vector<uint8_t> p = {(uint8_t)'p',(uint8_t)'i',(uint8_t)'n',(uint8_t)'g'};
|
||||
clt.ping_send(p);
|
||||
},[&clt]()->void {clt.close();});
|
||||
}catch(...) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
clt.Start();
|
||||
thrd.Join();
|
||||
}
|
||||
|
||||
}
|
||||
@ -33,6 +33,21 @@ namespace Tesses::Framework::Http
|
||||
WebSocketConnection* conn;
|
||||
Stream* strm;
|
||||
std::atomic_bool hasInit;
|
||||
std::atomic<bool> closed;
|
||||
void close()
|
||||
{
|
||||
mtx.Lock();
|
||||
closed=true;
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0x9;
|
||||
strm->WriteByte(firstByte);
|
||||
strm->WriteByte(0);
|
||||
|
||||
delete strm;
|
||||
this->strm = nullptr;
|
||||
mtx.Unlock();
|
||||
this->conn->OnClose(true);
|
||||
}
|
||||
void write_len_bytes(uint64_t len)
|
||||
{
|
||||
if(len < 126)
|
||||
@ -95,6 +110,7 @@ namespace Tesses::Framework::Http
|
||||
{
|
||||
while(!hasInit);
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t opcode = msg->isBinary ? 0x2 : 0x1;
|
||||
|
||||
size_t lengthLastByte = msg->data.size() % 4096;
|
||||
@ -119,7 +135,9 @@ namespace Tesses::Framework::Http
|
||||
}
|
||||
void ping_send(std::vector<uint8_t>& pData)
|
||||
{
|
||||
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0x9;
|
||||
strm->WriteByte(firstByte);
|
||||
@ -130,6 +148,7 @@ namespace Tesses::Framework::Http
|
||||
void pong_send(std::vector<uint8_t>& pData)
|
||||
{
|
||||
mtx.Lock();
|
||||
|
||||
uint8_t finField = 0b10000000 ;
|
||||
uint8_t firstByte= finField | 0xA;
|
||||
strm->WriteByte(firstByte);
|
||||
@ -139,6 +158,7 @@ namespace Tesses::Framework::Http
|
||||
}
|
||||
bool read_packet(uint8_t len,std::vector<uint8_t>& data)
|
||||
{
|
||||
|
||||
uint8_t realLen=len & 127;
|
||||
bool masked=(len & 0b10000000) > 0;
|
||||
uint64_t reallen2 = realLen >= 126 ? realLen > 126 ? get_long() : get_short() : realLen;
|
||||
@ -172,6 +192,7 @@ namespace Tesses::Framework::Http
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
this->closed=false;
|
||||
std::string key;
|
||||
if(ctx->requestHeaders.TryGetFirst("Sec-WebSocket-Key", key) && !key.empty())
|
||||
{
|
||||
@ -209,11 +230,11 @@ namespace Tesses::Framework::Http
|
||||
message.data={};
|
||||
hasInit=true;
|
||||
|
||||
while(!strm->EndOfStream())
|
||||
while( !strm->EndOfStream())
|
||||
{
|
||||
|
||||
uint8_t frame_start[2];
|
||||
if(strm->ReadBlock(frame_start,2) != 2) return;
|
||||
if( strm->ReadBlock(frame_start,2) != 2) return;
|
||||
|
||||
|
||||
uint8_t opcode = frame_start[0] & 0xF;
|
||||
@ -233,6 +254,8 @@ namespace Tesses::Framework::Http
|
||||
read_packet(frame_start[1], message.data);
|
||||
break;
|
||||
case 0x8:
|
||||
|
||||
if(!this->closed) this->close();
|
||||
this->conn->OnClose(true);
|
||||
return;
|
||||
case 0x9:
|
||||
@ -1007,7 +1030,7 @@ namespace Tesses::Framework::Http
|
||||
{
|
||||
|
||||
}
|
||||
void ServerContext::StartWebSocketSession(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose)
|
||||
void ServerContext::StartWebSocketSession(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose)
|
||||
{
|
||||
CallbackWebSocketConnection wsc(onOpen,onReceive,onClose);
|
||||
StartWebSocketSession(wsc);
|
||||
@ -1023,7 +1046,7 @@ namespace Tesses::Framework::Http
|
||||
},[&svr]()->void {
|
||||
std::vector<uint8_t> p = {(uint8_t)'p',(uint8_t)'i',(uint8_t)'n',(uint8_t)'g'};
|
||||
svr.ping_send(p);
|
||||
});
|
||||
},[&svr]()->void{svr.close();});
|
||||
}catch(...) {
|
||||
|
||||
}
|
||||
@ -1033,60 +1056,4 @@ namespace Tesses::Framework::Http
|
||||
thrd.Join();
|
||||
}
|
||||
|
||||
CallbackWebSocketConnection::CallbackWebSocketConnection()
|
||||
{
|
||||
|
||||
}
|
||||
CallbackWebSocketConnection::CallbackWebSocketConnection(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose)
|
||||
{
|
||||
this->onOpen = onOpen;
|
||||
this->onReceive = onReceive;
|
||||
this->onClose = onClose;
|
||||
}
|
||||
|
||||
void CallbackWebSocketConnection::OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping)
|
||||
{
|
||||
if(this->onOpen)
|
||||
this->onOpen(sendMessage,ping);
|
||||
}
|
||||
void CallbackWebSocketConnection::OnReceive(WebSocketMessage& message)
|
||||
{
|
||||
if(this->onReceive)
|
||||
this->onReceive(message);
|
||||
}
|
||||
void CallbackWebSocketConnection::OnClose(bool clean)
|
||||
{
|
||||
if(this->onClose)
|
||||
this->onClose(clean);
|
||||
}
|
||||
|
||||
|
||||
WebSocketMessage::WebSocketMessage()
|
||||
{
|
||||
this->isBinary=false;
|
||||
this->data={};
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(std::vector<uint8_t> data)
|
||||
{
|
||||
this->isBinary = true;
|
||||
this->data = data;
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(const void* data, size_t len)
|
||||
{
|
||||
this->isBinary=true;
|
||||
this->data={};
|
||||
this->data.insert(this->data.end(),(uint8_t*)data,((uint8_t*)data)+len);
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(std::string message)
|
||||
{
|
||||
this->isBinary=false;
|
||||
this->data={};
|
||||
this->data.insert(this->data.end(),message.begin(), message.end());
|
||||
}
|
||||
std::string WebSocketMessage::ToString()
|
||||
{
|
||||
std::string str = {};
|
||||
str.insert(str.end(),this->data.begin(),this->data.end());
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,9 +74,9 @@ namespace Tesses::Framework::Http {
|
||||
{
|
||||
if(this->port != 0) return this->port;
|
||||
|
||||
if(this->scheme == "http:")
|
||||
if(this->scheme == "http:" || this->scheme == "ws:")
|
||||
return 80;
|
||||
if(this->scheme == "https:")
|
||||
if(this->scheme == "https:" || this->scheme == "wss:")
|
||||
return 443;
|
||||
if(this->scheme == "sftp:")
|
||||
return 22;
|
||||
|
||||
67
src/Http/WebSocket.cpp
Normal file
67
src/Http/WebSocket.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "TessesFramework/Http/WebSocket.hpp"
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
|
||||
CallbackWebSocketConnection::CallbackWebSocketConnection()
|
||||
{
|
||||
|
||||
}
|
||||
CallbackWebSocketConnection::CallbackWebSocketConnection(std::function<void(std::function<void(WebSocketMessage&)>,std::function<void()>,std::function<void()>)> onOpen, std::function<void(WebSocketMessage&)> onReceive, std::function<void(bool)> onClose)
|
||||
{
|
||||
this->onOpen = onOpen;
|
||||
this->onReceive = onReceive;
|
||||
this->onClose = onClose;
|
||||
}
|
||||
|
||||
void CallbackWebSocketConnection::OnOpen(std::function<void(WebSocketMessage&)> sendMessage, std::function<void()> ping, std::function<void()> closeConnection)
|
||||
{
|
||||
if(this->onOpen)
|
||||
this->onOpen(sendMessage,ping,closeConnection);
|
||||
}
|
||||
void CallbackWebSocketConnection::OnReceive(WebSocketMessage& message)
|
||||
{
|
||||
if(this->onReceive)
|
||||
this->onReceive(message);
|
||||
}
|
||||
void CallbackWebSocketConnection::OnClose(bool clean)
|
||||
{
|
||||
if(this->onClose)
|
||||
this->onClose(clean);
|
||||
}
|
||||
|
||||
|
||||
WebSocketMessage::WebSocketMessage()
|
||||
{
|
||||
this->isBinary=false;
|
||||
this->data={};
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(std::vector<uint8_t> data)
|
||||
{
|
||||
this->isBinary = true;
|
||||
this->data = data;
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(const void* data, size_t len)
|
||||
{
|
||||
this->isBinary=true;
|
||||
this->data={};
|
||||
this->data.insert(this->data.end(),(uint8_t*)data,((uint8_t*)data)+len);
|
||||
}
|
||||
WebSocketMessage::WebSocketMessage(std::string message)
|
||||
{
|
||||
this->isBinary=false;
|
||||
this->data={};
|
||||
this->data.insert(this->data.end(),message.begin(), message.end());
|
||||
}
|
||||
std::string WebSocketMessage::ToString()
|
||||
{
|
||||
std::string str = {};
|
||||
str.insert(str.end(),this->data.begin(),this->data.end());
|
||||
return str;
|
||||
}
|
||||
|
||||
void SendWebSocketMessage(std::function<void(WebSocketMessage&)> cb, std::string text)
|
||||
{
|
||||
WebSocketMessage message(text);
|
||||
cb(message);
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@
|
||||
#include "TessesFramework/Http/HttpUtils.hpp"
|
||||
#include <iostream>
|
||||
using HttpUtils = Tesses::Framework::Http::HttpUtils;
|
||||
|
||||
#if defined(TESSESFRAMEWORK_ENABLE_NETWORKING)
|
||||
|
||||
|
||||
@ -26,7 +25,6 @@ using HttpUtils = Tesses::Framework::Http::HttpUtils;
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#pragma comment(lib, "Iphlpapi2.lib")
|
||||
#else
|
||||
extern "C" {
|
||||
#include <netinet/in.h>
|
||||
@ -468,6 +466,7 @@ namespace Tesses::Framework::Streams {
|
||||
this->owns=true;
|
||||
this->success=false;
|
||||
std::string portStr = std::to_string((uint32_t)port);
|
||||
|
||||
struct addrinfo hint;
|
||||
memset(&hint, 0, sizeof(hint));
|
||||
#if defined(AF_INET6)
|
||||
|
||||
Reference in New Issue
Block a user