mirror of
https://github.com/outbackdingo/sysadm.git
synced 2026-01-27 02:20:17 +00:00
Convert the REST/JSON input/output structures quite a bit so the backend usage is now agnostic as to the type of input used. It will then convert the output format to match what was input (REST -> REST, JSON -> JSON).
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QDateTime>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#define CurHttpVersion QString("HTTP/1.1")
|
||||
|
||||
@@ -22,12 +25,19 @@
|
||||
*/
|
||||
class RestInputStruct{
|
||||
public:
|
||||
//REST Variables
|
||||
QString VERB, URI, HTTPVERSION;
|
||||
QStringList Header;
|
||||
QString Body;
|
||||
//JSON input variables
|
||||
QString name, namesp, id;
|
||||
QJsonValue args;
|
||||
//Raw Text
|
||||
QStringList Header; //REST Headers
|
||||
QString Body; //Everything else
|
||||
|
||||
RestInputStruct(QString message){
|
||||
HTTPVERSION = CurHttpVersion;
|
||||
RestInputStruct(QString message = ""){
|
||||
HTTPVERSION = CurHttpVersion; //default value
|
||||
if(message.isEmpty()){ return; }
|
||||
//Pull out any REST headers
|
||||
if(!message.startsWith("{")){
|
||||
Header = message.section("\n{",0,0).split("\n");
|
||||
}
|
||||
@@ -37,11 +47,28 @@ public:
|
||||
URI = line.section(" ",1,1);
|
||||
HTTPVERSION = line.section(" ",2,2);
|
||||
Body = message.remove(Header.join("\n")+"\n"); //chop the headers off the front
|
||||
}else{
|
||||
//simple bypass for any non-REST inputs - just have it go straight to JSON parsing
|
||||
VERB = "GET";
|
||||
URI = "/syscache";
|
||||
Body = message;
|
||||
}
|
||||
//Now Parse out the Body into the JSON fields and/or arguments structure
|
||||
Body = Body.simplified(); //remove any extra whitespace on the beginning/end
|
||||
if(Body.startsWith("{") && Body.endsWith("}") ){
|
||||
QJsonDocument doc = QJsonDocument::fromJson(Body.toUtf8());
|
||||
if(!doc.isNull() && doc.isObject() ){
|
||||
//Valid JSON found
|
||||
if(doc.object().contains("namespace") ){ namesp = doc.object().value("namespace").toString(); }
|
||||
if(doc.object().contains("name") ){ name = doc.object().value("name").toString(); }
|
||||
if(doc.object().contains("id") ){ namesp = doc.object().value("id").toString(); }
|
||||
if(doc.object().contains("args") ){ args = doc.object().value("args"); }
|
||||
else{
|
||||
//no args structure - treat the entire body as the arguments struct
|
||||
args = doc.object();
|
||||
}
|
||||
}
|
||||
}
|
||||
//Now do any REST -> JSON conversions if necessary
|
||||
if(!URI.isEmpty()){
|
||||
//TO-DO
|
||||
name = URI.section("/",-1); //last entry
|
||||
namesp = URI.section("/",0,-2); //URI excluding name
|
||||
}
|
||||
}
|
||||
~RestInputStruct(){}
|
||||
@@ -51,54 +78,114 @@ public:
|
||||
class RestOutputStruct{
|
||||
public:
|
||||
enum ExitCode{OK, CREATED, ACCEPTED, NOCONTENT, RESETCONTENT, PARTIALCONTENT, PROCESSING, BADREQUEST, UNAUTHORIZED, FORBIDDEN, NOTFOUND };
|
||||
QString HTTPVERSION;
|
||||
QStringList Header;
|
||||
RestInputStruct in_struct;
|
||||
ExitCode CODE;
|
||||
QString Body;
|
||||
QStringList Header; //REST output header lines
|
||||
QJsonValue out_args;
|
||||
|
||||
RestOutputStruct(){
|
||||
HTTPVERSION = CurHttpVersion;
|
||||
CODE = BADREQUEST; //default exit code
|
||||
}
|
||||
~RestOutputStruct(){}
|
||||
|
||||
QString assembleMessage(){
|
||||
/* JUST OUTPUT RAW JSON - DISABLE REST FOR THE MOMENT
|
||||
QStringList headers;
|
||||
QString firstline = HTTPVERSION;
|
||||
switch(CODE){
|
||||
case PROCESSING:
|
||||
firstline.append(" 102 Processing"); break;
|
||||
case OK:
|
||||
firstline.append(" 200 OK"); break;
|
||||
case CREATED:
|
||||
firstline.append(" 201 Created"); break;
|
||||
case ACCEPTED:
|
||||
firstline.append(" 202 Accepted"); break;
|
||||
case NOCONTENT:
|
||||
firstline.append(" 204 No Content"); break;
|
||||
case RESETCONTENT:
|
||||
firstline.append(" 205 Reset Content"); break;
|
||||
case PARTIALCONTENT:
|
||||
firstline.append(" 206 Partial Content"); break;
|
||||
case BADREQUEST:
|
||||
firstline.append(" 400 Bad Request"); break;
|
||||
case UNAUTHORIZED:
|
||||
firstline.append(" 401 Unauthorized"); break;
|
||||
case FORBIDDEN:
|
||||
firstline.append(" 403 Forbidden"); break;
|
||||
case NOTFOUND:
|
||||
firstline.append(" 404 Not Found"); break;
|
||||
if( !in_struct.VERB.isEmpty() ){
|
||||
//REST output syntax
|
||||
QStringList headers;
|
||||
QString firstline = in_struct.HTTPVERSION;
|
||||
if(firstline.isEmpty()){ firstline = CurHttpVersion; }//default value
|
||||
QString Body;
|
||||
if(!out_args.isNull()){
|
||||
QJsonObject obj; obj.insert("args", out_args);
|
||||
Body = QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
switch(CODE){
|
||||
case PROCESSING:
|
||||
firstline.append(" 102 Processing"); break;
|
||||
case OK:
|
||||
firstline.append(" 200 OK"); break;
|
||||
case CREATED:
|
||||
firstline.append(" 201 Created"); break;
|
||||
case ACCEPTED:
|
||||
firstline.append(" 202 Accepted"); break;
|
||||
case NOCONTENT:
|
||||
firstline.append(" 204 No Content"); break;
|
||||
case RESETCONTENT:
|
||||
firstline.append(" 205 Reset Content"); break;
|
||||
case PARTIALCONTENT:
|
||||
firstline.append(" 206 Partial Content"); break;
|
||||
case BADREQUEST:
|
||||
firstline.append(" 400 Bad Request"); break;
|
||||
case UNAUTHORIZED:
|
||||
firstline.append(" 401 Unauthorized"); break;
|
||||
case FORBIDDEN:
|
||||
firstline.append(" 403 Forbidden"); break;
|
||||
case NOTFOUND:
|
||||
firstline.append(" 404 Not Found"); break;
|
||||
}
|
||||
headers << firstline;
|
||||
headers << "Date: "+QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
if(!Header.isEmpty()){ headers << Header; }
|
||||
//Now add the body of the return
|
||||
if(!Body.isEmpty()){ headers << "Content-Length: "+QString::number(Body.length()); }
|
||||
headers << Body;
|
||||
return headers.join("\n");
|
||||
|
||||
}else{
|
||||
//JSON output (load all the input fields for the moment)
|
||||
QString oname = "response"; //use this for valid responses (input ID found)
|
||||
if(in_struct.id.isEmpty()){ oname = in_struct.name; }
|
||||
QString onamesp = in_struct.namesp;
|
||||
QString oid = in_struct.id;
|
||||
if(CODE!=OK){
|
||||
//Format the output based on the type of error
|
||||
QJsonObject out_err;
|
||||
switch(CODE){
|
||||
case PROCESSING:
|
||||
//oname = onamesp = "error";
|
||||
out_err.insert("code","102"); out_err.insert("message", "Processing"); break;
|
||||
case CREATED:
|
||||
//oname = onamesp = "error";
|
||||
out_err.insert("code","201"); out_err.insert("message", "Created"); break;
|
||||
case ACCEPTED:
|
||||
//oname = onamesp = "error";
|
||||
out_err.insert("code","202"); out_err.insert("message", "Accepted"); break;
|
||||
case NOCONTENT:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","204"); out_err.insert("message", "No Content"); break;
|
||||
case RESETCONTENT:
|
||||
//oname = onamesp = "error";
|
||||
out_err.insert("code","205"); out_err.insert("message", "Reset Content"); break;
|
||||
case PARTIALCONTENT:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","206"); out_err.insert("message", "Partial Content"); break;
|
||||
case BADREQUEST:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","400"); out_err.insert("message", "Bad Request"); break;
|
||||
case UNAUTHORIZED:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","401"); out_err.insert("message", "Unauthorized"); break;
|
||||
case FORBIDDEN:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","403"); out_err.insert("message", "Forbidden"); break;
|
||||
case NOTFOUND:
|
||||
oname = onamesp = "error";
|
||||
out_err.insert("code","404"); out_err.insert("message", "Not Found"); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
out_args = out_err;
|
||||
}
|
||||
//Now assemple the JSON output
|
||||
QJsonObject obj;
|
||||
obj.insert("namespace",onamesp);
|
||||
obj.insert("name",oname);
|
||||
obj.insert("id",oid);
|
||||
obj.insert("args", out_args);
|
||||
//Convert the JSON to string form
|
||||
QJsonDocument doc(obj);
|
||||
return doc.toJson(QJsonDocument::Compact);
|
||||
}
|
||||
headers << firstline;
|
||||
headers << "Date: "+QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
//Add other headers here as necessary
|
||||
if(!Header.isEmpty()){ headers << Header; }
|
||||
//Now add the body of the return
|
||||
if(!Body.isEmpty()){ headers << "Content-Length: "+QString::number(Body.length()); }
|
||||
headers << Body;
|
||||
return headers.join("\n");*/
|
||||
return Body;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ void WebSocket::EvaluateREST(QString msg){
|
||||
//Now check for the REST-specific verbs/actions
|
||||
if(IN.VERB == "OPTIONS" || IN.VERB == "HEAD"){
|
||||
RestOutputStruct out;
|
||||
out.in_struct = IN;
|
||||
out.CODE = RestOutputStruct::OK;
|
||||
if(IN.VERB=="HEAD"){
|
||||
|
||||
@@ -88,21 +89,26 @@ void WebSocket::EvaluateREST(QString msg){
|
||||
}
|
||||
out.Header << "Accept: text/json";
|
||||
out.Header << "Content-Type: text/json; charset=utf-8";
|
||||
SOCKET->sendTextMessage(out.assembleMessage());
|
||||
}else{
|
||||
if(SOCKET!=0){ SOCKET->sendTextMessage(out.assembleMessage()); }
|
||||
else if(TSOCKET!=0){ TSOCKET->write(out.assembleMessage().toUtf8().data()); }
|
||||
}else{
|
||||
EvaluateRequest(IN);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocket::EvaluateRequest(const RestInputStruct &REQ){
|
||||
RestOutputStruct out;
|
||||
if(REQ.VERB != "GET"){
|
||||
out.in_struct = REQ;
|
||||
if(!REQ.VERB.isEmpty() && REQ.VERB != "GET"){
|
||||
//Non-supported request (at the moment) - return an error message
|
||||
out.CODE = RestOutputStruct::BADREQUEST;
|
||||
}else if(out.in_struct.name.isEmpty() || out.in_struct.namesp.isEmpty() ){
|
||||
//Invalid JSON structure validity
|
||||
//Note: id and args are optional at this stage - let the subsystems handle those inputs
|
||||
out.CODE = RestOutputStruct::BADREQUEST;
|
||||
}else{
|
||||
//GET request
|
||||
//Now check the body of the message and do what it needs
|
||||
QJsonDocument doc = QJsonDocument::fromJson(REQ.Body.toUtf8());
|
||||
/*QJsonDocument doc = QJsonDocument::fromJson(REQ.Body.toUtf8());
|
||||
if(doc.isNull()){ qWarning() << "Empty JSON Message Body!!" << REQ.Body.toUtf8(); }
|
||||
//Define the output structures
|
||||
QJsonObject ret; //return message
|
||||
@@ -115,81 +121,85 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){
|
||||
&& doc.object().contains("id") \
|
||||
&& doc.object().contains("args");
|
||||
//Can add some fallbacks for missing fields here - but not implemented yet
|
||||
|
||||
*/
|
||||
//parse the message and do something
|
||||
if(good && (JsonValueToString(doc.object().value("namespace"))=="rpc") ){
|
||||
//if(good && (JsonValueToString(doc.object().value("namespace"))=="rpc") ){
|
||||
if(out.in_struct.namesp.toLower() == "rpc"){
|
||||
//Now fetch the outputs from the appropriate subsection
|
||||
//Note: Each subsection needs to set the "name", "namespace", and "args" output objects
|
||||
QString name = JsonValueToString(doc.object().value("name")).toLower();
|
||||
QJsonValue args = doc.object().value("args");
|
||||
if(name.startsWith("auth")){
|
||||
//QString name = JsonValueToString(doc.object().value("name")).toLower();
|
||||
//QJsonValue args = doc.object().value("args");
|
||||
if(out.in_struct.name.startsWith("auth")){
|
||||
//Now perform authentication based on type of auth given
|
||||
//Note: This sets/changes the current SockAuthToken
|
||||
AUTHSYSTEM->clearAuth(SockAuthToken); //new auth requested - clear any old token
|
||||
if(DEBUG){ qDebug() << "Authenticate Peer:" << SOCKET->peerAddress().toString(); }
|
||||
bool localhost = (SOCKET->peerAddress() == QHostAddress::LocalHost) || (SOCKET->peerAddress() == QHostAddress::LocalHostIPv6);
|
||||
//Now do the auth
|
||||
if(name=="auth" && args.isObject() ){
|
||||
if(out.in_struct.name=="auth" && out.in_struct.args.isObject() ){
|
||||
//username/password authentication
|
||||
QString user, pass;
|
||||
if(args.toObject().contains("username")){ user = JsonValueToString(args.toObject().value("username")); }
|
||||
if(args.toObject().contains("password")){ pass = JsonValueToString(args.toObject().value("password")); }
|
||||
if(out.in_struct.args.toObject().contains("username")){ user = JsonValueToString(out.in_struct.args.toObject().value("username")); }
|
||||
if(out.in_struct.args.toObject().contains("password")){ pass = JsonValueToString(out.in_struct.args.toObject().value("password")); }
|
||||
SockAuthToken = AUTHSYSTEM->LoginUP(localhost, user, pass);
|
||||
}else if(name == "auth_token" && args.isObject()){
|
||||
SockAuthToken = JsonValueToString(args.toObject().value("token"));
|
||||
}else if(name == "auth_clear"){
|
||||
}else if(out.in_struct.name == "auth_token" && out.in_struct.args.isObject()){
|
||||
SockAuthToken = JsonValueToString(out.in_struct.args.toObject().value("token"));
|
||||
}else if(out.in_struct.name == "auth_clear"){
|
||||
return; //don't send a return message after clearing an auth (already done)
|
||||
}
|
||||
|
||||
//Now check the auth and respond appropriately
|
||||
if(AUTHSYSTEM->checkAuth(SockAuthToken)){
|
||||
//Good Authentication - return the new token
|
||||
ret.insert("namespace", QJsonValue("rpc"));
|
||||
ret.insert("name", QJsonValue("response"));
|
||||
ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
//ret.insert("namespace", QJsonValue("rpc"));
|
||||
//ret.insert("name", QJsonValue("response"));
|
||||
//ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
QJsonArray array;
|
||||
array.append(SockAuthToken);
|
||||
array.append(AUTHSYSTEM->checkAuthTimeoutSecs(SockAuthToken));
|
||||
ret.insert("args", array);
|
||||
out.out_args = array;
|
||||
}else{
|
||||
SockAuthToken.clear(); //invalid token
|
||||
//Bad Authentication - return error
|
||||
SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized");
|
||||
out.CODE = RestOutputStruct::UNAUTHORIZED;
|
||||
//SetOutputError(&ret, JsonValueToString(out.in_struct.id), 401, "Unauthorized");
|
||||
}
|
||||
|
||||
}else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token
|
||||
//Now provide access to the various subsystems
|
||||
//Pre-set any output fields
|
||||
QJsonObject outargs;
|
||||
ret.insert("namespace", QJsonValue("rpc"));
|
||||
ret.insert("name", QJsonValue("response"));
|
||||
ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
EvaluateBackendRequest(name, doc.object().value("args"), &outargs);
|
||||
ret.insert("args",outargs);
|
||||
//ret.insert("namespace", QJsonValue("rpc"));
|
||||
//ret.insert("name", QJsonValue("response"));
|
||||
//ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
out.CODE = EvaluateBackendRequest(out.in_struct.name, out.in_struct.args, &outargs);
|
||||
out.out_args = outargs; //ret.insert("args",outargs);
|
||||
}else{
|
||||
out.CODE = RestOutputStruct::UNAUTHORIZED;
|
||||
//Bad/No authentication
|
||||
SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized");
|
||||
//SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized");
|
||||
}
|
||||
|
||||
}else if(good && (JsonValueToString(doc.object().value("namespace"))=="events") ){
|
||||
if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token
|
||||
|
||||
//}else if(good && (JsonValueToString(doc.object().value("namespace"))=="events") ){
|
||||
}else if(out.in_struct.namesp.toLower() == "events"){
|
||||
if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token
|
||||
//Pre-set any output fields
|
||||
QJsonObject outargs;
|
||||
ret.insert("namespace", QJsonValue("events"));
|
||||
ret.insert("name", QJsonValue("response"));
|
||||
ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
//ret.insert("namespace", QJsonValue("events"));
|
||||
//ret.insert("name", QJsonValue("response"));
|
||||
//ret.insert("id", doc.object().value("id")); //use the same ID for the return message
|
||||
//Assemble the list of input events
|
||||
QStringList evlist;
|
||||
if(doc.object().value("args").isObject()){ evlist << JsonValueToString(doc.object().value("args")); }
|
||||
else if(doc.object().value("args").isArray()){ evlist = JsonArrayToStringList(doc.object().value("args").toArray()); }
|
||||
if(out.in_struct.args.isObject()){ evlist << JsonValueToString(out.in_struct.args); }
|
||||
else if(out.in_struct.args.isArray()){ evlist = JsonArrayToStringList(out.in_struct.args.toArray()); }
|
||||
//Now subscribe/unsubscribe to these events
|
||||
if(JsonValueToString(doc.object().value("name"))=="subscribe"){
|
||||
if(out.in_struct.name=="subscribe"){
|
||||
if(evlist.contains("dispatcher")){
|
||||
SendAppCafeEvents = true;
|
||||
outargs.insert("subscribe",QJsonValue("dispatcher"));
|
||||
QTimer::singleShot(100, this, SLOT(AppCafeStatusUpdate()) );
|
||||
}
|
||||
}else if(JsonValueToString(doc.object().value("name"))=="unsubscribe"){
|
||||
}else if(out.in_struct.name=="unsubscribe"){
|
||||
if(evlist.contains("dispatcher")){
|
||||
SendAppCafeEvents = false;
|
||||
outargs.insert("unsubscribe",QJsonValue("dispatcher"));
|
||||
@@ -197,26 +207,26 @@ void WebSocket::EvaluateRequest(const RestInputStruct &REQ){
|
||||
}else{
|
||||
outargs.insert("unknown",QJsonValue("unknown"));
|
||||
}
|
||||
ret.insert("args",outargs);
|
||||
//ret.insert("args",outargs);
|
||||
out.out_args = outargs;
|
||||
}else{
|
||||
//Bad/No authentication
|
||||
SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized");
|
||||
out.CODE = RestOutputStruct::UNAUTHORIZED;
|
||||
//SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized");
|
||||
}
|
||||
}else{
|
||||
//Error in inputs - assemble the return error message
|
||||
QString id = "error";
|
||||
}else{
|
||||
//Error in inputs - assemble the return error message
|
||||
out.CODE = RestOutputStruct::BADREQUEST;
|
||||
/*QString id = "error";
|
||||
if(doc.object().contains("id")){ id = JsonValueToString(doc.object().value("id")); } //use the same ID
|
||||
SetOutputError(&ret, id, 400, "Bad Request");
|
||||
}
|
||||
}else{
|
||||
//Unknown type of JSON input - nothing to do
|
||||
}
|
||||
SetOutputError(&ret, id, 400, "Bad Request");*/
|
||||
}
|
||||
//Assemble the outputs for this "GET" request
|
||||
out.CODE = RestOutputStruct::OK;
|
||||
//Assemble the output JSON document/text
|
||||
QJsonDocument retdoc;
|
||||
retdoc.setObject(ret);
|
||||
out.Body = retdoc.toJson();
|
||||
//QJsonDocument retdoc;
|
||||
//retdoc.setObject(ret);
|
||||
//out.Body = retdoc.toJson();
|
||||
out.Header << "Content-Type: text/json; charset=utf-8";
|
||||
}
|
||||
//Return any information
|
||||
@@ -380,21 +390,25 @@ void WebSocket::AppCafeStatusUpdate(QString msg){
|
||||
//qDebug() << "Socket Status Update:" << msg;
|
||||
if(!SendAppCafeEvents){ return; } //don't report events on this socket
|
||||
RestOutputStruct out;
|
||||
out.CODE = RestOutputStruct::OK;
|
||||
out.in_struct.name = "event";
|
||||
out.in_struct.namesp = "events";
|
||||
//Define the output structures
|
||||
QJsonObject ret; //return message
|
||||
//QJsonObject ret; //return message
|
||||
//Pre-set any output fields
|
||||
QJsonObject outargs;
|
||||
ret.insert("namespace", QJsonValue("events"));
|
||||
ret.insert("name", QJsonValue("event"));
|
||||
ret.insert("id", QJsonValue(""));
|
||||
//ret.insert("namespace", QJsonValue("events"));
|
||||
//ret.insert("name", QJsonValue("event"));
|
||||
//ret.insert("id", QJsonValue(""));
|
||||
outargs.insert("name", "dispatcher");
|
||||
outargs.insert("args",QJsonValue(msg));
|
||||
ret.insert("args",outargs);
|
||||
out.CODE = RestOutputStruct::OK;
|
||||
out.out_args = outargs;
|
||||
//ret.insert("args",outargs);
|
||||
|
||||
//Assemble the output JSON document/text
|
||||
QJsonDocument retdoc;
|
||||
retdoc.setObject(ret);
|
||||
out.Body = retdoc.toJson();
|
||||
//QJsonDocument retdoc;
|
||||
//retdoc.setObject(ret);
|
||||
//out.Body = retdoc.toJson();
|
||||
out.Header << "Content-Type: text/json; charset=utf-8";
|
||||
//Now send the message back through the socket
|
||||
if(SOCKET!=0){ SOCKET->sendTextMessage(out.assembleMessage()); }
|
||||
|
||||
Reference in New Issue
Block a user