Multi-user gravity beats for Sound of Science 2013 (server)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 

173 lines
5.8 KiB

#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <stdexcept>
#include <syslog.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#include <lib/libwebsockets.h>
namespace websockets {
// Will be caught in the callback and program will continue to run (but connection will be closed)
struct runtime_error : public std::runtime_error {
int return_value;
runtime_error(const char * message, int return_value = -1)
: std::runtime_error(message)
, return_value(return_value)
{}
};
// Will never be caught (by us), and hence things will terminate
struct fatal_error : public std::runtime_error {
fatal_error(const char * message)
: std::runtime_error(message)
{}
};
struct Log {
Log(std::string const & name, int syslog_options, int debug_level){
setlogmask(LOG_UPTO (LOG_DEBUG));
openlog(name.c_str(), syslog_options, LOG_DAEMON);
lws_set_log_level(debug_level, lwsl_emit_syslog);
}
#define log_function(nice_name, lwsl_name)\
template <typename... T>\
void nice_name(std::string const & format, T... args){\
lwsl_name(format.c_str(), args...);\
}
log_function(notice, lwsl_notice)
log_function(error, lwsl_err)
log_function(warning, lwsl_warn)
log_function(debug, lwsl_debug)
#undef log_function
~Log(){
closelog();
}
};
// Basic wrapper for the context pointer
struct Context {
std::unique_ptr<libwebsocket_context, decltype(libwebsocket_context_destroy)*> context;
Context(lws_context_creation_info& info)
: context(libwebsocket_create_context(&info), &libwebsocket_context_destroy)
{
if(context == nullptr){
lwsl_err("libwebsocket init failed\n");
throw std::runtime_error("libwebsocket init failed");
}
}
libwebsocket_context const * get_raw() const {
return context.get();
}
libwebsocket_context * get_raw() {
return context.get();
}
};
// Basic protocol to extend, already casts the void* to the actual type
template <typename T>
struct Protocol {
typedef T user_type;
int callback(libwebsocket_context *context, libwebsocket *wsi, libwebsocket_callback_reasons reason, void *user, void *in, size_t len){
return call(*context, *wsi, reason, *static_cast<T*>(user), in, len);
}
virtual int call(libwebsocket_context& context, libwebsocket& wsi, libwebsocket_callback_reasons reason, T& user, void *in, size_t len){
return 0;
};
};
struct basic_websocket_info{
libwebsocket_context* context{nullptr};
libwebsocket* wsi{nullptr};
basic_websocket_info(libwebsocket_context* context, libwebsocket* wsi)
: context(context)
, wsi(wsi)
{}
};
// 4-function Protocol, provide functions to handle establishment, closing, receiving and writing (follows receiving)
// To let the callback return something non-zero (in case of error), use websockets::runtime_error
// Only types with no-arg ctor are allowed (uses placement new, because libwebsockets allocates for us)
template <typename T>
struct TestProtocol {
typedef T user_type;
std::function<void(user_type&, basic_websocket_info)> establish_func;
std::function<void(user_type&, basic_websocket_info)> close_func;
std::function<std::string(user_type&, basic_websocket_info)> write_func;
std::function<void(user_type&, std::string, basic_websocket_info)> receive_func;
bool verbose{false};
TestProtocol(decltype(establish_func) establish_func, decltype(close_func) close_func, decltype(write_func) write_func, decltype(receive_func) receive_func)
: establish_func(establish_func)
, close_func(close_func)
, write_func(write_func)
, receive_func(receive_func)
{}
int callback(libwebsocket_context* context, libwebsocket* wsi, libwebsocket_callback_reasons reason, void* user_ptr, void *in, size_t len){
user_type& user = *static_cast<user_type*>(user_ptr);
basic_websocket_info binfo{context, wsi};
try{
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
lwsl_notice("Connection established (%p, %p)\n", this, user_ptr);
new (user_ptr) user_type();
establish_func(user, binfo);
break;
case LWS_CALLBACK_CLOSED:
lwsl_notice("Connection closed (%p, %p)\n", this, user_ptr);
user.~user_type();
close_func(user, binfo);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:{
std::string string_to_send = write_func(user, binfo);
if(verbose) std::cout << string_to_send << std::endl;
// we need the extra bytes padding on both sides :(
unsigned char * buf = new unsigned char [LWS_SEND_BUFFER_PRE_PADDING + string_to_send.size() + LWS_SEND_BUFFER_POST_PADDING];
memcpy(&buf[LWS_SEND_BUFFER_PRE_PADDING], string_to_send.c_str(), string_to_send.size());
int n = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], string_to_send.size(), LWS_WRITE_TEXT);
if (n < 0) throw runtime_error("Could not write", 1);
if (n < string_to_send.size()) throw runtime_error("Partial write", -1);
break;
}
case LWS_CALLBACK_RECEIVE:
receive_func(user, std::string((char*)in, len), binfo);
libwebsocket_callback_on_writable(context, wsi);
break;
default:
break;
}
} catch(runtime_error& e){
lwsl_err("Exception thrown: %s", e.what());
return e.return_value;
}
return 0;
}
};
// Some wrappers to easily create the list of protocols from structs of above kind.
#define WSprotocol_callback(protocol)\
[](libwebsocket_context *context, libwebsocket *wsi, libwebsocket_callback_reasons reason, void *user, void *in, size_t len)\
{return protocol.callback(context, wsi, reason, user, in, len);}
#define WSstandard_protocol(name, protocol)\
{ name, WSprotocol_callback(protocol), sizeof(decltype(protocol)::user_type) }
int default_main(int argc, char **argv, libwebsocket_protocols* protocols, std::function<void()> runloop_callback = nullptr);
} // namespace websockets