@@ -731,7 +731,7 @@ CARLA_EXPORT float carla_get_internal_parameter_value(uint pluginId, int32_t par | |||||
* Get a plugin's peak values. | * Get a plugin's peak values. | ||||
* @param pluginId Plugin | * @param pluginId Plugin | ||||
*/ | */ | ||||
float* carla_get_peak_values(uint pluginId); | |||||
CARLA_EXPORT float* carla_get_peak_values(uint pluginId); | |||||
/*! | /*! | ||||
* Get a plugin's input peak value. | * Get a plugin's input peak value. | ||||
@@ -20,6 +20,7 @@ | |||||
# Imports (Global) | # Imports (Global) | ||||
import requests | import requests | ||||
from websocket import WebSocket, WebSocketConnectionClosedException | |||||
# --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
# Imports (Custom) | # Imports (Custom) | ||||
@@ -29,49 +30,6 @@ from carla_backend_qt import * | |||||
import os | import os | ||||
from time import sleep | from time import sleep | ||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
# Iterates over the content of a file-like object line-by-line. | |||||
# Based on code by Lars Kellogg-Stedman, see https://github.com/requests/requests/issues/2433 | |||||
def iterate_stream_nonblock(stream, chunk_size=1024): | |||||
pending = None | |||||
while True: | |||||
try: | |||||
chunk = os.read(stream.raw.fileno(), chunk_size) | |||||
except BlockingIOError: | |||||
break | |||||
if not chunk: | |||||
break | |||||
if pending is not None: | |||||
chunk = pending + chunk | |||||
pending = None | |||||
lines = chunk.splitlines() | |||||
if lines and lines[-1]: | |||||
pending = lines.pop() | |||||
for line in lines: | |||||
yield line | |||||
if not pending: | |||||
break | |||||
if pending: | |||||
yield pending | |||||
# --------------------------------------------------------------------------------------------------------------------- | |||||
def create_stream(baseurl): | |||||
stream = requests.get("{}/stream".format(baseurl), stream=True, timeout=0.1) | |||||
if stream.encoding is None: | |||||
stream.encoding = 'utf-8' | |||||
return stream | |||||
# --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
# Carla Host object for connecting to the REST API backend | # Carla Host object for connecting to the REST API backend | ||||
@@ -79,14 +37,23 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
def __init__(self): | def __init__(self): | ||||
CarlaHostQtNull.__init__(self) | CarlaHostQtNull.__init__(self) | ||||
self.baseurl = "http://localhost:2228" | |||||
self.stream = create_stream(self.baseurl) | |||||
self.host = "localhost" | |||||
self.port = 2228 | |||||
self.baseurl = "http://{}:{}".format(self.host, self.port) | |||||
self.socket = WebSocket() | |||||
self.socket.connect("ws://{}:{}/ws".format(self.host, self.port), timeout=1) | |||||
self.isRemote = True | self.isRemote = True | ||||
self.isRunning = True | |||||
self.peaks = [] | |||||
for i in range(99): | |||||
self.peaks.append((0.0, 0.0, 0.0, 0.0)) | |||||
def get_engine_driver_count(self): | def get_engine_driver_count(self): | ||||
# FIXME | |||||
return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) - 1 | |||||
return int(requests.get("{}/get_engine_driver_count".format(self.baseurl)).text) | |||||
def get_engine_driver_name(self, index): | def get_engine_driver_name(self, index): | ||||
return requests.get("{}/get_engine_driver_name".format(self.baseurl), params={ | return requests.get("{}/get_engine_driver_name".format(self.baseurl), params={ | ||||
@@ -114,13 +81,22 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
return bool(int(requests.get("{}/engine_close".format(self.baseurl)).text)) | return bool(int(requests.get("{}/engine_close".format(self.baseurl)).text)) | ||||
def engine_idle(self): | def engine_idle(self): | ||||
closed = False | |||||
stream = self.stream | |||||
if not self.isRunning: | |||||
return | |||||
while True: | |||||
try: | |||||
line = self.socket.recv().strip() | |||||
except WebSocketConnectionClosedException: | |||||
self.isRunning = False | |||||
if self.fEngineCallback is None: | |||||
self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
return | |||||
for line in iterate_stream_nonblock(stream): | |||||
line = line.decode('utf-8', errors='ignore') | |||||
if line == "Keep-Alive": | |||||
return | |||||
if line.startswith("Carla: "): | |||||
elif line.startswith("Carla: "): | |||||
if self.fEngineCallback is None: | if self.fEngineCallback is None: | ||||
continue | continue | ||||
@@ -137,15 +113,24 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
# pass to callback | # pass to callback | ||||
self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr) | self.fEngineCallback(None, action, pluginId, value1, value2, value3, valueStr) | ||||
elif line == "Connection: close": | |||||
if not closed: | |||||
self.stream = create_stream(self.baseurl) | |||||
closed = True | |||||
elif line.startswith("Peaks: "): | |||||
# split values from line | |||||
pluginId, value1, value2, value3, value4 = line[7:].split(" ",5) | |||||
if closed: | |||||
stream.close() | |||||
# convert to proper types | |||||
pluginId = int(pluginId) | |||||
value1 = float(value1) | |||||
value2 = float(value2) | |||||
value3 = float(value3) | |||||
value4 = float(value4) | |||||
# store peaks | |||||
self.peaks[pluginId] = (value1, value2, value3, value4) | |||||
def is_engine_running(self): | def is_engine_running(self): | ||||
if not self.isRunning: | |||||
return False | |||||
try: | try: | ||||
return bool(int(requests.get("{}/is_engine_running".format(self.baseurl)).text)) | return bool(int(requests.get("{}/is_engine_running".format(self.baseurl)).text)) | ||||
except requests.exceptions.ConnectionError: | except requests.exceptions.ConnectionError: | ||||
@@ -192,7 +177,7 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
def patchbay_refresh(self, external): | def patchbay_refresh(self, external): | ||||
return bool(int(requests.get("{}/patchbay_refresh".format(self.baseurl), params={ | return bool(int(requests.get("{}/patchbay_refresh".format(self.baseurl), params={ | ||||
'external': external, | |||||
'external': int(external), | |||||
}).text)) | }).text)) | ||||
def transport_play(self): | def transport_play(self): | ||||
@@ -215,7 +200,13 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text) | return int(requests.get("{}/get_current_transport_frame".format(self.baseurl)).text) | ||||
def get_transport_info(self): | def get_transport_info(self): | ||||
return requests.get("{}/get_transport_info".format(self.baseurl)).json() | |||||
if self.isRunning: | |||||
try: | |||||
return requests.get("{}/get_transport_info".format(self.baseurl)).json() | |||||
except requests.exceptions.ConnectionError: | |||||
if self.fEngineCallback is None: | |||||
self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
return PyCarlaTransportInfo() | |||||
def get_current_plugin_count(self): | def get_current_plugin_count(self): | ||||
return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text) | return int(requests.get("{}/get_current_plugin_count".format(self.baseurl)).text) | ||||
@@ -411,10 +402,16 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
}).text) | }).text) | ||||
def get_current_parameter_value(self, pluginId, parameterId): | def get_current_parameter_value(self, pluginId, parameterId): | ||||
return float(requests.get("{}/get_current_parameter_value".format(self.baseurl), params={ | |||||
'pluginId': pluginId, | |||||
'parameterId': parameterId, | |||||
}).text) | |||||
if self.isRunning: | |||||
try: | |||||
return float(requests.get("{}/get_current_parameter_value".format(self.baseurl), params={ | |||||
'pluginId': pluginId, | |||||
'parameterId': parameterId, | |||||
}).text) | |||||
except requests.exceptions.ConnectionError: | |||||
if self.fEngineCallback is None: | |||||
self.fEngineCallback(None, ENGINE_CALLBACK_QUIT, 0, 0, 0, 0.0, "") | |||||
return 0.0 | |||||
def get_internal_parameter_value(self, pluginId, parameterId): | def get_internal_parameter_value(self, pluginId, parameterId): | ||||
return float(requests.get("{}/get_internal_parameter_value".format(self.baseurl), params={ | return float(requests.get("{}/get_internal_parameter_value".format(self.baseurl), params={ | ||||
@@ -423,28 +420,22 @@ class CarlaHostQtWeb(CarlaHostQtNull): | |||||
}).text) | }).text) | ||||
def get_input_peak_value(self, pluginId, isLeft): | def get_input_peak_value(self, pluginId, isLeft): | ||||
return float(requests.get("{}/get_input_peak_value".format(self.baseurl), params={ | |||||
'pluginId': pluginId, | |||||
'isLeft': isLeft, | |||||
}).text) | |||||
return self.peaks[pluginId][0 if isLeft else 1] | |||||
def get_output_peak_value(self, pluginId, isLeft): | def get_output_peak_value(self, pluginId, isLeft): | ||||
return float(requests.get("{}/get_output_peak_value".format(self.baseurl), params={ | |||||
'pluginId': pluginId, | |||||
'isLeft': isLeft, | |||||
}).text) | |||||
return self.peaks[pluginId][2 if isLeft else 3] | |||||
def set_option(self, pluginId, option, yesNo): | def set_option(self, pluginId, option, yesNo): | ||||
requests.get("{}/set_option".format(self.baseurl), params={ | requests.get("{}/set_option".format(self.baseurl), params={ | ||||
'pluginId': pluginId, | 'pluginId': pluginId, | ||||
'option': option, | 'option': option, | ||||
'yesNo': yesNo, | |||||
'yesNo': int(yesNo), | |||||
}) | }) | ||||
def set_active(self, pluginId, onOff): | def set_active(self, pluginId, onOff): | ||||
requests.get("{}/set_active".format(self.baseurl), params={ | requests.get("{}/set_active".format(self.baseurl), params={ | ||||
'pluginId': pluginId, | 'pluginId': pluginId, | ||||
'onOff': onOff, | |||||
'onOff': int(onOff), | |||||
}) | }) | ||||
def set_drywet(self, pluginId, value): | def set_drywet(self, pluginId, value): | ||||
@@ -23,7 +23,10 @@ endif | |||||
BUILD_CXX_FLAGS += -I$(CWD) -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils | BUILD_CXX_FLAGS += -I$(CWD) -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils | ||||
LINK_FLAGS += -L$(BINDIR) -lcarla_standalone2 -lcarla_utils -lrestbed -lpthread -Wl,-rpath=$(shell realpath $(CWD)/../bin) | |||||
LINK_FLAGS += -Wl,-rpath=$(shell realpath $(CWD)/../bin) | |||||
LINK_FLAGS += -L$(BINDIR) -lcarla_standalone2 -lcarla_utils | |||||
LINK_FLAGS += -lrestbed -lssl -lcrypto | |||||
LINK_FLAGS += -lpthread | |||||
# ---------------------------------------------------------------------------------------------------------------------- | # ---------------------------------------------------------------------------------------------------------------------- | ||||
@@ -24,23 +24,15 @@ | |||||
static bool gEngineRunning = false; | static bool gEngineRunning = false; | ||||
void engine_idle_handler() | |||||
{ | |||||
if (gEngineRunning) | |||||
carla_engine_idle(); | |||||
} | |||||
// ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId, int value1, int value2, float value3, const char* valueStr) | static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId, int value1, int value2, float value3, const char* valueStr) | ||||
{ | { | ||||
#if 0 | |||||
carla_stdout("EngineCallback(%p, %u:%s, %u, %i, %i, %f, %s)", | |||||
ptr, (uint)action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); | |||||
#endif | |||||
carla_debug("EngineCallback(%p, %u:%s, %u, %i, %i, %f, %s)", | |||||
ptr, (uint)action, EngineCallbackOpcode2Str(action), pluginId, value1, value2, value3, valueStr); | |||||
char msgBuf[1024]; | char msgBuf[1024]; | ||||
std::snprintf(msgBuf, 1023, "Carla: %u %u %i %i %f %s\n", action, pluginId, value1, value2, value3, valueStr); | |||||
std::snprintf(msgBuf, 1023, "Carla: %u %u %i %i %f %s", action, pluginId, value1, value2, value3, valueStr); | |||||
msgBuf[1023] = '\0'; | msgBuf[1023] = '\0'; | ||||
switch (action) | switch (action) | ||||
@@ -56,7 +48,10 @@ static void EngineCallback(void* ptr, EngineCallbackOpcode action, uint pluginId | |||||
break; | break; | ||||
} | } | ||||
send_server_side_message(msgBuf); | |||||
return send_server_side_message(msgBuf); | |||||
// maybe unused | |||||
(void)ptr; | |||||
} | } | ||||
static const char* FileCallback(void* ptr, FileCallbackOpcode action, bool isDir, const char* title, const char* filter) | static const char* FileCallback(void* ptr, FileCallbackOpcode action, bool isDir, const char* title, const char* filter) | ||||
@@ -274,7 +269,7 @@ void handle_carla_transport_bpm(const std::shared_ptr<Session> session) | |||||
const std::shared_ptr<const Request> request = session->get_request(); | const std::shared_ptr<const Request> request = session->get_request(); | ||||
const double bpm = std::atof(request->get_query_parameter("bpm").c_str()); | const double bpm = std::atof(request->get_query_parameter("bpm").c_str()); | ||||
CARLA_SAFE_ASSERT_RETURN(bpm > 0.0,) // FIXME | |||||
CARLA_SAFE_ASSERT_RETURN(bpm > 0.0, session->close(OK)) // FIXME | |||||
carla_transport_bpm(bpm); | carla_transport_bpm(bpm); | ||||
session->close(OK); | session->close(OK); | ||||
@@ -31,11 +31,26 @@ | |||||
// ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
std::vector<std::shared_ptr<Session>> gSessions; | |||||
#include <map> | |||||
#include <restbed> | |||||
#include <system_error> | |||||
#include <openssl/sha.h> | |||||
#include <openssl/hmac.h> | |||||
#include <openssl/evp.h> | |||||
#include <openssl/bio.h> | |||||
#include <openssl/buffer.h> | |||||
using namespace std; | |||||
using namespace restbed; | |||||
using namespace std::chrono; | |||||
// std::vector<std::shared_ptr<Session>> gSessions; | |||||
CarlaStringList gSessionMessages; | CarlaStringList gSessionMessages; | ||||
CarlaMutex gSessionMessagesMutex; | CarlaMutex gSessionMessagesMutex; | ||||
std::map< string, shared_ptr< WebSocket > > sockets = { }; | |||||
// ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
void send_server_side_message(const char* const message) | void send_server_side_message(const char* const message) | ||||
@@ -47,20 +62,6 @@ void send_server_side_message(const char* const message) | |||||
// ------------------------------------------------------------------------------------------------------------------- | // ------------------------------------------------------------------------------------------------------------------- | ||||
static void register_server_side_handler(const std::shared_ptr<Session> session) | |||||
{ | |||||
const auto headers = std::multimap<std::string, std::string> { | |||||
{ "Connection", "keep-alive" }, | |||||
{ "Cache-Control", "no-cache" }, | |||||
{ "Content-Type", "text/event-stream" }, | |||||
{ "Access-Control-Allow-Origin", "*" } //Only required for demo purposes. | |||||
}; | |||||
session->yield(OK, headers, [](const std::shared_ptr<Session> rsession) { | |||||
gSessions.push_back(rsession); | |||||
}); | |||||
} | |||||
static void event_stream_handler(void) | static void event_stream_handler(void) | ||||
{ | { | ||||
static bool firstInit = true; | static bool firstInit = true; | ||||
@@ -71,12 +72,10 @@ static void event_stream_handler(void) | |||||
carla_stdout("Carla REST-API Server started"); | carla_stdout("Carla REST-API Server started"); | ||||
} | } | ||||
gSessions.erase( | |||||
std::remove_if(gSessions.begin(), gSessions.end(), | |||||
[](const std::shared_ptr<Session> &a) { | |||||
return a->is_closed(); | |||||
}), | |||||
gSessions.end()); | |||||
const bool running = carla_is_engine_running(); | |||||
if (running) | |||||
carla_engine_idle(); | |||||
CarlaStringList messages; | CarlaStringList messages; | ||||
@@ -89,27 +88,214 @@ static void event_stream_handler(void) | |||||
for (auto message : messages) | for (auto message : messages) | ||||
{ | { | ||||
// std::puts(message); | |||||
for (auto entry : sockets) | |||||
{ | |||||
auto socket = entry.second; | |||||
if (socket->is_open()) | |||||
socket->send(message); | |||||
} | |||||
} | |||||
if (running) | |||||
{ | |||||
if (const uint count = carla_get_current_plugin_count()) | |||||
{ | |||||
char msgBuf[1024]; | |||||
float* peaks; | |||||
for (uint i=0; i<count; ++i) | |||||
{ | |||||
peaks = carla_get_peak_values(i); | |||||
CARLA_SAFE_ASSERT_BREAK(peaks != nullptr); | |||||
std::snprintf(msgBuf, 1023, "Peaks: %u %f %f %f %f", i, peaks[0], peaks[1], peaks[2], peaks[3]); | |||||
msgBuf[1023] = '\0'; | |||||
for (auto entry : sockets) | |||||
{ | |||||
auto socket = entry.second; | |||||
if (socket->is_open()) | |||||
socket->send(msgBuf); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
for (auto entry : sockets) | |||||
{ | |||||
auto socket = entry.second; | |||||
if (socket->is_open()) | |||||
socket->send("Keep-Alive"); | |||||
} | |||||
} | |||||
// ------------------------------------------------------------------------------------------------------------------- | |||||
string base64_encode( const unsigned char* input, int length ) | |||||
{ | |||||
BIO* bmem, *b64; | |||||
BUF_MEM* bptr; | |||||
b64 = BIO_new( BIO_f_base64( ) ); | |||||
bmem = BIO_new( BIO_s_mem( ) ); | |||||
b64 = BIO_push( b64, bmem ); | |||||
BIO_write( b64, input, length ); | |||||
( void ) BIO_flush( b64 ); | |||||
BIO_get_mem_ptr( b64, &bptr ); | |||||
char* buff = ( char* )malloc( bptr->length ); | |||||
memcpy( buff, bptr->data, bptr->length - 1 ); | |||||
buff[ bptr->length - 1 ] = 0; | |||||
BIO_free_all( b64 ); | |||||
return buff; | |||||
} | |||||
multimap< string, string > build_websocket_handshake_response_headers( const shared_ptr< const Request >& request ) | |||||
{ | |||||
auto key = request->get_header( "Sec-WebSocket-Key" ); | |||||
key.append( "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ); | |||||
Byte hash[ SHA_DIGEST_LENGTH ]; | |||||
SHA1( reinterpret_cast< const unsigned char* >( key.data( ) ), key.length( ), hash ); | |||||
multimap< string, string > headers; | |||||
headers.insert( make_pair( "Upgrade", "websocket" ) ); | |||||
headers.insert( make_pair( "Connection", "Upgrade" ) ); | |||||
headers.insert( make_pair( "Sec-WebSocket-Accept", base64_encode( hash, SHA_DIGEST_LENGTH ) ) ); | |||||
for (auto session : gSessions) | |||||
session->yield(OK, message); | |||||
return headers; | |||||
} | |||||
void close_handler( const shared_ptr< WebSocket > socket ) | |||||
{ | |||||
carla_stdout("CLOSE %i", __LINE__); | |||||
if ( socket->is_open( ) ) | |||||
{ | |||||
auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 00 } ) ); | |||||
socket->send( response ); | |||||
} | } | ||||
carla_stdout("CLOSE %i", __LINE__); | |||||
const auto key = socket->get_key( ); | |||||
sockets.erase( key ); | |||||
fprintf( stderr, "Closed connection to %s.\n", key.data( ) ); | |||||
} | |||||
void error_handler( const shared_ptr< WebSocket > socket, const error_code error ) | |||||
{ | |||||
const auto key = socket->get_key( ); | |||||
fprintf( stderr, "WebSocket Errored '%s' for %s.\n", error.message( ).data( ), key.data( ) ); | |||||
} | |||||
void message_handler( const shared_ptr< WebSocket > source, const shared_ptr< WebSocketMessage > message ) | |||||
{ | |||||
const auto opcode = message->get_opcode( ); | |||||
if (const uint count = carla_get_current_plugin_count()) | |||||
if ( opcode == WebSocketMessage::PING_FRAME ) | |||||
{ | |||||
auto response = make_shared< WebSocketMessage >( WebSocketMessage::PONG_FRAME, message->get_data( ) ); | |||||
source->send( response ); | |||||
} | |||||
else if ( opcode == WebSocketMessage::PONG_FRAME ) | |||||
{ | { | ||||
char msgBuf[1024]; | |||||
float* peaks; | |||||
//Ignore PONG_FRAME. | |||||
// | |||||
//Every time the ping_handler is scheduled to run, it fires off a PING_FRAME to each | |||||
//WebSocket. The client, if behaving correctly, will respond with a PONG_FRAME. | |||||
// | |||||
//On each occasion the underlying TCP socket sees any packet data transfer, whether | |||||
//a PING, PONG, TEXT, or BINARY... frame. It will automatically reset the timeout counter | |||||
//leaving the connection active; see also Settings::set_connection_timeout. | |||||
return; | |||||
} | |||||
else if ( opcode == WebSocketMessage::CONNECTION_CLOSE_FRAME ) | |||||
{ | |||||
source->close( ); | |||||
} | |||||
else if ( opcode == WebSocketMessage::BINARY_FRAME ) | |||||
{ | |||||
//We don't support binary data. | |||||
auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 03 } ) ); | |||||
source->send( response ); | |||||
} | |||||
else if ( opcode == WebSocketMessage::TEXT_FRAME ) | |||||
{ | |||||
auto response = make_shared< WebSocketMessage >( *message ); | |||||
response->set_mask( 0 ); | |||||
for (uint i=0; i<count; ++i) | |||||
for ( auto socket : sockets ) | |||||
{ | { | ||||
peaks = carla_get_peak_values(i); | |||||
CARLA_SAFE_ASSERT_BREAK(peaks != nullptr); | |||||
auto destination = socket.second; | |||||
destination->send( response ); | |||||
} | |||||
const auto key = source->get_key( ); | |||||
const auto data = String::format( "Received message '%.*s' from %s\n", message->get_data( ).size( ), message->get_data( ).data( ), key.data( ) ); | |||||
fprintf( stderr, "%s", data.data( ) ); | |||||
} | |||||
} | |||||
std::snprintf(msgBuf, 1023, "Peaks: %u %f %f %f %f\n", i, peaks[0], peaks[1], peaks[2], peaks[3]); | |||||
msgBuf[1023] = '\0'; | |||||
void get_method_handler(const shared_ptr<Session> session) | |||||
{ | |||||
carla_stdout("HERE %i", __LINE__); | |||||
const auto request = session->get_request(); | |||||
const auto connection_header = request->get_header("connection", String::lowercase); | |||||
carla_stdout("HERE %i", __LINE__); | |||||
for (auto session : gSessions) | |||||
session->yield(OK, msgBuf); | |||||
if ( connection_header.find( "upgrade" ) not_eq string::npos ) | |||||
{ | |||||
if ( request->get_header( "upgrade", String::lowercase ) == "websocket" ) | |||||
{ | |||||
const auto headers = build_websocket_handshake_response_headers( request ); | |||||
session->upgrade( SWITCHING_PROTOCOLS, headers, [ ]( const shared_ptr< WebSocket > socket ) | |||||
{ | |||||
if ( socket->is_open( ) ) | |||||
{ | |||||
socket->set_close_handler( close_handler ); | |||||
socket->set_error_handler( error_handler ); | |||||
socket->set_message_handler( message_handler ); | |||||
socket->send("Welcome to Corvusoft Chat!"); | |||||
auto key = socket->get_key( ); | |||||
sockets[key] = socket; | |||||
} | |||||
else | |||||
{ | |||||
fprintf( stderr, "WebSocket Negotiation Failed: Client closed connection.\n" ); | |||||
} | |||||
} ); | |||||
return; | |||||
} | |||||
} | |||||
session->close( BAD_REQUEST ); | |||||
} | |||||
void ping_handler( void ) | |||||
{ | |||||
for ( auto entry : sockets ) | |||||
{ | |||||
auto key = entry.first; | |||||
auto socket = entry.second; | |||||
if ( socket->is_open( ) ) | |||||
{ | |||||
socket->send( WebSocketMessage::PING_FRAME ); | |||||
} | |||||
else | |||||
{ | |||||
socket->close( ); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -132,11 +318,11 @@ int main(int, const char**) | |||||
{ | { | ||||
Service service; | Service service; | ||||
// server-side messages | |||||
// websocket | |||||
{ | { | ||||
std::shared_ptr<Resource> resource = std::make_shared<Resource>(); | std::shared_ptr<Resource> resource = std::make_shared<Resource>(); | ||||
resource->set_path("/stream"); | |||||
resource->set_method_handler("GET", register_server_side_handler); | |||||
resource->set_path("/ws"); | |||||
resource->set_method_handler("GET", get_method_handler); | |||||
service.publish(resource); | service.publish(resource); | ||||
} | } | ||||
@@ -251,8 +437,8 @@ int main(int, const char**) | |||||
make_resource(service, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info); | make_resource(service, "/get_cached_plugin_info", handle_carla_get_cached_plugin_info); | ||||
// schedule events | // schedule events | ||||
service.schedule(engine_idle_handler); // FIXME, crashes on fast times, but we need ~30Hz for OSC.. | |||||
service.schedule(event_stream_handler, std::chrono::milliseconds(500)); | |||||
service.schedule(event_stream_handler, std::chrono::milliseconds(33)); | |||||
service.schedule(ping_handler, milliseconds(5000)); | |||||
std::shared_ptr<Settings> settings = std::make_shared<Settings>(); | std::shared_ptr<Settings> settings = std::make_shared<Settings>(); | ||||
settings->set_port(2228); | settings->set_port(2228); | ||||