Add websocket example

This commit is contained in:
Edward Emelianov 2020-10-09 12:04:25 +03:00
parent 6cccee95ca
commit cf158b6dac
9 changed files with 227 additions and 19 deletions

View File

@ -30,6 +30,9 @@ else()
set(CMAKE_BUILD_TYPE RELEASE) set(CMAKE_BUILD_TYPE RELEASE)
endif() endif()
#pthreads
find_package(Threads REQUIRED)
###### pkgconfig ###### ###### pkgconfig ######
# pkg-config modules (for pkg-check-modules) # pkg-config modules (for pkg-check-modules)
set(MODULES usefull_macros sqlite3) set(MODULES usefull_macros sqlite3)
@ -62,7 +65,7 @@ add_definitions(${CFLAGS} -DLOCALEDIR=\"${LOCALEDIR}\"
-DMAJOR_VERSION=\"${MAJOR_VESION}\") -DMAJOR_VERSION=\"${MAJOR_VESION}\")
# -l # -l
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm) target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} -lm)
# Installation of the program # Installation of the program
INSTALL(TARGETS ${PROJ} DESTINATION "bin") INSTALL(TARGETS ${PROJ} DESTINATION "bin")

2
Readme Normal file
View File

@ -0,0 +1,2 @@
cleaning:
sqlite3 users.db "vacuum;"

View File

@ -31,6 +31,7 @@ static int help;
// global parameters (init with default): // global parameters (init with default):
glob_pars G = { glob_pars G = {
.port = "8080", .port = "8080",
.wsport = "8081",
.certfile = "cert.pem", .certfile = "cert.pem",
.keyfile = "cert.key", .keyfile = "cert.key",
}; };
@ -46,7 +47,8 @@ static myoption cmdlnopts[] = {
{"dumpusers", NO_ARGS, NULL, 'U', arg_int, APTR(&G.dumpUserDB),_("dump users database")}, {"dumpusers", NO_ARGS, NULL, 'U', arg_int, APTR(&G.dumpUserDB),_("dump users database")},
{"dumpsess", NO_ARGS, NULL, 'S', arg_int, APTR(&G.dumpSessDB),_("dump session database")}, {"dumpsess", NO_ARGS, NULL, 'S', arg_int, APTR(&G.dumpSessDB),_("dump session database")},
{"server", NO_ARGS, NULL, 'r', arg_int, APTR(&G.runServer), _("run server process")}, {"server", NO_ARGS, NULL, 'r', arg_int, APTR(&G.runServer), _("run server process")},
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to listen")}, {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("server port to listen")},
{"wsport", NEED_ARG, NULL, 'P', arg_string, APTR(&G.wsport), _("websocket port to listen (!= server port!)")},
{"certfile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.certfile), _("file with SSL certificate")}, {"certfile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.certfile), _("file with SSL certificate")},
{"keyfile", NEED_ARG, NULL, 'k', arg_string, APTR(&G.keyfile), _("file with SSL key")}, {"keyfile", NEED_ARG, NULL, 'k', arg_string, APTR(&G.keyfile), _("file with SSL key")},
{"usersdb", NEED_ARG, NULL, 'u', arg_string, APTR(&G.usersdb), _("users database filename")}, {"usersdb", NEED_ARG, NULL, 'u', arg_string, APTR(&G.usersdb), _("users database filename")},
@ -55,6 +57,7 @@ static myoption cmdlnopts[] = {
{"useradd", NO_ARGS, NULL, 'a', arg_int, APTR(&G.useradd), _("add user[s] interactively")}, {"useradd", NO_ARGS, NULL, 'a', arg_int, APTR(&G.useradd), _("add user[s] interactively")},
{"sdatime", NEED_ARG, NULL, 'A', arg_longlong,APTR(&G.delatime), _("minimal atime to delete sessions from DB (-1 for >year)")}, {"sdatime", NEED_ARG, NULL, 'A', arg_longlong,APTR(&G.delatime), _("minimal atime to delete sessions from DB (-1 for >year)")},
{"sessdel", NEED_ARG, NULL, 'l', arg_string, APTR(&G.delsession),_("delete session by sessID or sockID")}, {"sessdel", NEED_ARG, NULL, 'l', arg_string, APTR(&G.delsession),_("delete session by sessID or sockID")},
{"logfile", NEED_ARG, NULL, 'L', arg_string, APTR(&G.logfilename),_("log file name")},
end_option end_option
}; };

View File

@ -31,7 +31,8 @@ typedef struct{
int dumpSessDB; // dump session database int dumpSessDB; // dump session database
int runServer; // run as server int runServer; // run as server
int useradd; // add user[s] int useradd; // add user[s]
char *port; // port to listen char *port; // server port to listen
char *wsport; // websocket port to listen (!= port !!!)
char *certfile; // file with SSL certificate char *certfile; // file with SSL certificate
char *keyfile; // file with SSL key char *keyfile; // file with SSL key
char *usersdb; // users database name char *usersdb; // users database name
@ -39,6 +40,7 @@ typedef struct{
char **userdel; // user names to delete char **userdel; // user names to delete
long long delatime; // minimal atime to delete sessions from DB long long delatime; // minimal atime to delete sessions from DB
char *delsession; // delete session by sessID or sockID char *delsession; // delete session by sessID or sockID
char *logfilename; // name of log file
int rest_pars_num; // number of rest parameters int rest_pars_num; // number of rest parameters
char** rest_pars; // the rest parameters: array of char* char** rest_pars; // the rest parameters: array of char*
} glob_pars; } glob_pars;

112
main.c
View File

@ -32,6 +32,10 @@
#include "auth.h" #include "auth.h"
#include "cmdlnopts.h" #include "cmdlnopts.h"
#include "websockets.h"
// temporary
#define putlog(...)
onion_connection_status get(_U_ onion_handler *h, onion_request *req, onion_response *res){ onion_connection_status get(_U_ onion_handler *h, onion_request *req, onion_response *res){
sessinfo *session = qookieSession(req); sessinfo *session = qookieSession(req);
@ -52,36 +56,113 @@ onion_connection_status get(_U_ onion_handler *h, onion_request *req, onion_resp
return OCS_CLOSE_CONNECTION; return OCS_CLOSE_CONNECTION;
} }
static onion *o = NULL; static onion *os = NULL, *ow = NULL;
void signals(int signo){ void signals(int signo){
if(o) onion_free(o);
closeSQLite(); closeSQLite();
if(os) onion_free(os);
if(ow) onion_free(ow);
exit(signo); exit(signo);
} }
static void runServer(){ // POST/GET server
o = onion_new(O_POOL); static void *runPostGet(_U_ void *data){
if(!(onion_flags(o) & O_SSL_AVAILABLE)){ os = onion_new(O_THREADED);
if(!(onion_flags(os) & O_SSL_AVAILABLE)){
ONION_ERROR("SSL support is not available"); ONION_ERROR("SSL support is not available");
signals(1); signals(1);
} }
int error = onion_set_certificate(o, O_SSL_CERTIFICATE_KEY, G.certfile, G.keyfile); int error = onion_set_certificate(os, O_SSL_CERTIFICATE_KEY, G.certfile, G.keyfile);
if(error){ if(error){
ONION_ERROR("Cant set certificate and key files (%s, %s)", G.certfile, G.keyfile); ONION_ERROR("Cant set certificate and key files (%s, %s)", G.certfile, G.keyfile);
signals(1); signals(1);
} }
onion_set_port(o, G.port); onion_set_port(os, G.port);
onion_url *url = onion_root_url(o); onion_url *url = onion_root_url(os);
onion_url_add_handler(url, "^static/", onion_handler_export_local_new("static")); onion_url_add_handler(url, "^static/", onion_handler_export_local_new("static"));
onion_url_add_with_data(url, "", onion_shortcut_internal_redirect, "static/index.html", NULL); onion_url_add_with_data(url, "", onion_shortcut_internal_redirect, "static/index.html", NULL);
onion_url_add(url, "^auth/", auth); onion_url_add(url, "^auth/", auth);
onion_url_add(url, "^get/", get); onion_url_add(url, "^get/", get);
signal(SIGTERM, signals); error = onion_listen(os);
error = onion_listen(o); if(error) ONION_ERROR("Cant create POST/GET server: %s", strerror(errno));
if(error){ onion_free(os);
ONION_ERROR("Cant create the server: %s", strerror(errno)); return NULL;
}
// Websocket server
static void *runWS(_U_ void *data){
ow = onion_new(O_THREADED);
if(!(onion_flags(ow) & O_SSL_AVAILABLE)){
ONION_ERROR("SSL support is not available");
signals(1);
} }
onion_free(o); int error = onion_set_certificate(ow, O_SSL_CERTIFICATE_KEY, G.certfile, G.keyfile);
if(error){
ONION_ERROR("Cant set certificate and key files (%s, %s)", G.certfile, G.keyfile);
signals(1);
}
onion_set_port(ow, G.wsport);
onion_url *url = onion_root_url(ow);
onion_url_add(url, "", websocket_run);
DBG("Listen websocket");
error = onion_listen(ow);
if(error) ONION_ERROR("Cant create POST/GET server: %s", strerror(errno));
onion_free(ow);
return NULL;
}
static void runServer(){
// if(G.logfilename) Cl_createlog();
signal(SIGTERM, signals);
signal(SIGINT, signals);
signal(SIGQUIT, signals);
signal(SIGTSTP, SIG_IGN);
signal(SIGHUP, SIG_IGN);
pthread_t pg_thread, ws_thread;
if(pthread_create(&pg_thread, NULL, runPostGet, NULL)){
ERR("pthread_create()");
}
if(pthread_create(&ws_thread, NULL, runWS, NULL)){
ERR("pthread_create()");
}
do{
if(pthread_kill(pg_thread, 0) == ESRCH){ // POST/GET died
WARNX("POST/GET server thread died");
putlog("POST/GET server thread died");
pthread_join(pg_thread, NULL);
if(pthread_create(&pg_thread, NULL, runPostGet, NULL)){
putlog("pthread_create() failed");
ERR("pthread_create()");
}
}
if((pthread_kill(pg_thread, 0) == ESRCH) || (pthread_kill(ws_thread, 0) == ESRCH)){ // died
WARNX("Websocket server thread died");
putlog("Websocket server thread died");
pthread_join(ws_thread, NULL);
if(pthread_create(&ws_thread, NULL, runWS, NULL)){
putlog("pthread_create() failed");
ERR("pthread_create()");
}
}
#if 0
usleep(1000); // sleep a little or thread's won't be able to lock mutex
if(dtime() - tgot < T_INTERVAL) continue;
tgot = dtime();
/*
* INSERT CODE HERE
* Gather data (poll_device)
*/
// copy temporary buffers to main
pthread_mutex_lock(&mutex);
/*
* INSERT CODE HERE
* fill global data buffers
*/
pthread_mutex_unlock(&mutex);
#endif
}while(1);
putlog("Unreaceable code reached!");
ERRX("Unreaceable code reached!");
} }
static char *getl(char *msg, int noempty){ static char *getl(char *msg, int noempty){
@ -178,7 +259,10 @@ int main(int argc, char **argv){
if(!deleteOldSessions((int64_t)G.delatime)) if(!deleteOldSessions((int64_t)G.delatime))
green("All sessions with atime<%lld deleted\n", G.delatime); green("All sessions with atime<%lld deleted\n", G.delatime);
} }
if(G.runServer) runServer(); if(G.runServer){
if(strcmp(G.port, G.wsport) == 0) ERRX("Server port ans websocket port should be different!");
runServer();
}
closeSQLite(); closeSQLite();
return 0; return 0;
} }

View File

@ -10,8 +10,9 @@ auth = function(){
$("inout").onclick = auth.logout; $("inout").onclick = auth.logout;
} }
function _wsk(request){ function _wsk(request){
var wsKey = request.responseText; wsKey = request.responseText;
if(wsKey) console.log("Web key received: " + wsKey); if(wsKey) console.log("Web key received: " + wsKey);
wsinit();
} }
function reqAuth(request){ function reqAuth(request){
var txt = request.responseText; var txt = request.responseText;
@ -62,10 +63,34 @@ auth = function(){
var str = "auth/?login=" + l + "&passwd=" + p; var str = "auth/?login=" + l + "&passwd=" + p;
sendrequest(str, reqAuth); sendrequest(str, reqAuth);
} }
// websockets
var ws;
function wsinit(){
delete(ws);
ws = new WebSocket('wss://localhost:8081');
ws.onopen = function(){ws.send("Akey="+wsKey);}; // send key after init
ws.onclose = function(evt){
var text = "WebSocket closed: ";
if(evt.wasClean) text += "by remote side";
else text += "connection lost"
$('wsmsgs').innerHTML = text;
};
ws.onmessage = function(evt){
$('wsmsgs').innerHTML = evt.data;
}
ws.onerror = function(err){
parseErr("WebSocket error " + err.message);
}
}
function wssend(txt){
ws.send(txt);
}
return{ return{
init: init1, init: init1,
login: login1, login: login1,
logout: logout1, logout: logout1,
send: sendlogpass send: sendlogpass,
wssend: wssend,
wsinit: wsinit
}; };
}(); }();

View File

@ -7,7 +7,11 @@
<body onload="auth.init();"> <body onload="auth.init();">
<p>Text <p>Text
<p>More text <p>More text
<button onclick="auth.wsinit();">Push me</button>
<p> <p>
<div id="wsmsgs"></div>
<div id="errmsg" style='background-color: red;'></div> <div id="errmsg" style='background-color: red;'></div>
<p>
<input type="text" id="wssnd" onchange="auth.wssend($('wssnd').value);">
</body> </body>
</html> </html>

59
websockets.c Normal file
View File

@ -0,0 +1,59 @@
/*
* This file is part of the Onion_test project.
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "websockets.h"
#include <errno.h>
#include <onion/log.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#define BUFLEN 255
static onion_connection_status websocket_cont(_U_ void *data, onion_websocket *ws, ssize_t dlen){
FNAME();
char tmp[BUFLEN+1];
if(dlen > BUFLEN) dlen = BUFLEN;
int len = onion_websocket_read(ws, tmp, dlen);
if(len <= 0){
ONION_ERROR("Error reading data: %d: %s (%d)", errno, strerror(errno), dlen);
return OCS_NEED_MORE_DATA;
}
tmp[len] = 0;
DBG("WS: got %s", tmp);
onion_websocket_printf(ws, "Echo: %s", tmp);
ONION_INFO("Read from websocket: %d: %s", len, tmp);
return OCS_NEED_MORE_DATA;
}
onion_connection_status websocket_run(_U_ void *data, onion_request *req, onion_response *res){
FNAME();
onion_websocket *ws = onion_websocket_new(req, res);
if (!ws){
green("PROC\n");
DBG("Processed");
return OCS_PROCESSED;
}
DBG("WS ready");
green("RDY\n");
onion_websocket_printf(ws, "Hello from server. Write something to echo it");
onion_websocket_set_callback(ws, websocket_cont);
return OCS_WEBSOCKET;
}

26
websockets.h Normal file
View File

@ -0,0 +1,26 @@
/*
* This file is part of the Onion_test project.
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef WEBSOCKETS_H__
#define WEBSOCKETS_H__
#include <onion/onion.h>
#include <onion/websocket.h>
onion_connection_status websocket_run(void *data, onion_request *req, onion_response *res);
#endif // WEBSOCKETS_H__