#wip: WebSockets building.

This commit is contained in:
KyMAN 2026-05-28 07:24:34 +02:00
parent 45b2597ab0
commit 4a97794e2c
14 changed files with 380 additions and 58 deletions

View File

@ -63,10 +63,15 @@
"http_server_port" : 18000, "http_server_port" : 18000,
"AnP_HTTPServer_end" : null, "AnP_HTTPServer_end" : null,
"AnP_WebSocketServer_start" : null, "AnP_WebSocketsServerManager_start" : null,
"web_socket_server_host" : "", "default_web_sockets_server" : {
"web_socket_server_port" : 18765, "anp" : {
"AnP_WebSocketServer_end" : null, "type" : "WebSocketServerDriver",
"host" : "localhost",
"port" : 18765
}
},
"AnP_WebSocketsServerManager_end" : null,
"AnP_TitlesManager_start" : null, "AnP_TitlesManager_start" : null,
"default_titles_files" : [ "default_titles_files" : [

View File

@ -1,6 +1,7 @@
"use strict"; "use strict";
import {Common} from "../Utils/Common.ecma.js"; import {Common} from "../Utils/Common.ecma.js";
import {Event} from "../Application/Event.ecma.js";
/** /**
* @class WebSocketsDriver * @class WebSocketsDriver
@ -12,6 +13,12 @@ import {Common} from "../Utils/Common.ecma.js";
*/ */
export const WebSocketsDriver = (function(){ export const WebSocketsDriver = (function(){
/**
* @callback continue_callback
* @param {boolean} ok
* @return {void}
*/
/** /**
* @constructs WebSocketsDriver * @constructs WebSocketsDriver
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs * @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
@ -23,19 +30,39 @@ export const WebSocketsDriver = (function(){
/** @type {WebSocketsDriver} */ /** @type {WebSocketsDriver} */
const self = this; const self = this;
let server = null, /** @type {WebSocket|null} */
let client = null,
/** @type {string|null} */
url = null, url = null,
/** @type {boolean} */
started = false; started = false;
/** @type {Event} */
this.on_open = new Event(); this.on_open = new Event();
/** @type {Event} */
this.on_message = new Event(); this.on_message = new Event();
/** @type {Event} */
this.on_error = new Event(); this.on_error = new Event();
/** @type {Event} */
this.on_close = new Event(); this.on_close = new Event();
const constructor = () => {}; /**
* @returns {void}
* @access private
*/
const constructor = () => {
url = Common.get_string(inputs.url, "ws://localhost:8080");
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.start = (callback = null) => { this.start = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok); const end = ok => Common.execute(callback, ok);
if(started){ if(started){
@ -44,27 +71,24 @@ export const WebSocketsDriver = (function(){
}; };
started = true; started = true;
server = new WebSocket(`ws://${host}:${port}`); client = new WebSocket(url);
server.onopen = on_open; client.onopen = on_open;
client.onmessage = on_message
server.onmessage = event => { client.onerror = on_error;
self.on_message.trigger(event); client.onclose = on_close;
};
server.onerror = event => {
self.on_error.trigger(event);
};
server.onclose = event => {
self.on_close.trigger(event);
};
return true; return true;
}; };
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.close = (callback = null) => { this.close = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok); const end = ok => Common.execute(callback, ok);
if(!started){ if(!started){
@ -73,34 +97,30 @@ export const WebSocketsDriver = (function(){
}; };
started = false; started = false;
server.close(); client.close();
server = null; client = null;
return true; return true;
}; };
const open = event => { const on_open = event => {
console.log(["client", event]); console.log(["client", event]);
}; };
const new_message = event => { const on_message = event => {
console.log(["message", event]); console.log(["message", event]);
}; };
const error = event => { const on_error = event => {
console.log(["error", event]); console.log(["error", event]);
}; };
const close = event => { const on_close = event => {
console.log(["close", event]); console.log(["close", event]);
}; };
this.send = (ids, message) => { this.send = message => {
Common.get_array(ids).forEach(id => {}); client.send(message);
};
this.send_to_all = message => {
self.send(Object.keys(clients), message);
}; };
constructor(); constructor();

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Optional, Sequence
from abc import ABC, abstractmethod
from Application.Event import Event
class WebSocketsServerAbstract(ABC):
def __init__(self:Self, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.on_new_client:Event = Event()
self.on_message:Event = Event()
self.on_close:Event = Event()
self.on_error:Event = Event()
@abstractmethod
def start(self:Self) -> None:pass
@abstractmethod
def close(self:Self) -> None:pass
@abstractmethod
def close_client(self:Self, id:int) -> None:pass
@abstractmethod
def send(self:Self, data:Any|None, ids:Optional[int|Sequence[int]] = None) -> None:pass

View File

@ -14,6 +14,7 @@ from Managers.ControllersManager import ControllersManager
from Managers.DispatchesManager import DispatchesManager from Managers.DispatchesManager import DispatchesManager
from Managers.IndexesManager import IndexesManager from Managers.IndexesManager import IndexesManager
from Managers.RoutesManager import RoutesManager from Managers.RoutesManager import RoutesManager
from Managers.WebSocketsServerManager import WebSocketsServerManager
from Drivers.HTTPDriver import HTTPDriver from Drivers.HTTPDriver import HTTPDriver
from Utils.Common import Common from Utils.Common import Common
from Utils.Patterns import RE from Utils.Patterns import RE
@ -45,6 +46,7 @@ class AnP:
self.dispatches:DispatchesManager = DispatchesManager(self) self.dispatches:DispatchesManager = DispatchesManager(self)
self.indexes:IndexesManager = IndexesManager(self) self.indexes:IndexesManager = IndexesManager(self)
self.routes:RoutesManager = RoutesManager(self) self.routes:RoutesManager = RoutesManager(self)
self.web_sockets_servers:WebSocketsServerManager = WebSocketsServerManager(self)
self.http_server:HTTPDriver = HTTPDriver(self) self.http_server:HTTPDriver = HTTPDriver(self)
def update(self:Self) -> None: def update(self:Self) -> None:
@ -58,6 +60,7 @@ class AnP:
self.dispatches.update() self.dispatches.update()
self.indexes.update() self.indexes.update()
self.routes.update() self.routes.update()
self.web_sockets_servers.update()
self.http_server.update() self.http_server.update()
def reset(self:Self) -> None: def reset(self:Self) -> None:
@ -71,10 +74,12 @@ class AnP:
self.dispatches.reset() self.dispatches.reset()
self.indexes.reset() self.indexes.reset()
self.routes.reset() self.routes.reset()
self.web_sockets_servers.reset()
self.http_server.reset() self.http_server.reset()
def close(self:Self) -> None: def close(self:Self) -> None:
self.__working = False self.__working = False
self.web_sockets_servers.close()
self.http_server.close() self.http_server.close()
def working(self:Self) -> bool: def working(self:Self) -> bool:

View File

@ -34,3 +34,6 @@ class Event:
def remove(self:Self, i:int) -> None: def remove(self:Self, i:int) -> None:
if i in self.__events: if i in self.__events:
del self.__events[i] del self.__events[i]
def clean(self:Self) -> None:
self.__events = {}

View File

@ -3,7 +3,7 @@
from typing import Any, Optional, Self, Sequence from typing import Any, Optional, Self, Sequence
from Interfaces.Application.AnPInterface import AnPInterface from Interfaces.Application.AnPInterface import AnPInterface
from requests import post as Post
class OllamaDriver: class OllamaDriver:

View File

@ -1,28 +1,37 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from threading import Thread
from typing import Any, Self, Sequence, Optional from typing import Any, Self, Sequence, Optional
from Abstracts.WebSocketsServerAbstract import WebSocketsServerAbstract
from Abstracts.ModelAbstract import ModelAbstract
from websockets.sync.server import serve as server_serve from websockets.sync.server import serve as server_serve
from websockets import ServerConnection as WebSocketServer, ClientConnection as WebSocketClient from websockets import Server as WebSocketServer, ClientConnection as WebSocketClient
from Application.Event import Event
from Interfaces.Application.AnPInterface import AnPInterface from Interfaces.Application.AnPInterface import AnPInterface
from Utils.Checks import Check
class WebSocketServerDriver: class WebSocketServerDriver(WebSocketsServerAbstract, ModelAbstract):
def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.anp:AnPInterface = anp self.anp:AnPInterface = anp
self.on_new_client:Event = Event() super().__init__(inputs)
self.on_message:Event = Event()
self.on_close:Event = Event()
self.on_error:Event = Event()
self.__server:WebSocketServer self.__server:WebSocketServer
self.__clients:dict[str, WebSocketClient] = {} self.__clients:dict[int, WebSocketClient] = {}
self.__host:str = anp.settings.get(("web_socket_server_host", "host"), inputs, "") self.__host:str = anp.settings.get(("web_socket_server_host", "host"), inputs, "")
self.__port:int = anp.settings.get(("web_socket_server_port", "port"), inputs, 8765) self.__port:int = anp.settings.get(("web_socket_server_port", "port"), inputs, 8765)
self.__client_i:int = 0
self.__thread:Thread = None
with server_serve(self.__handler, self.__host, self.__port) as self.__server: anp.settings.get(("web_socket_server_autostart", "autostart"), inputs, True) and self.start()
self.__server.serve_forever()
def __run(self:Self) -> None:
self.__server = server_serve(self.__handler, self.__host, self.__port)
self.__server.serve_forever()
def start(self:Self) -> None:
self.__thread = Thread(target = self.__run)
self.__thread.start()
def close(self:Self) -> None: def close(self:Self) -> None:
@ -31,14 +40,14 @@ class WebSocketServerDriver:
for id in tuple(self.__clients.keys()): for id in tuple(self.__clients.keys()):
self.close_client(id) self.close_client(id)
self.__server.close() self.__server.shutdown()
def close_client(self:Self, id:str, show_exception:bool = True) -> None: def close_client(self:Self, id:int) -> None:
if id in self.__clients: if id in self.__clients:
try: try:
self.__clients[id].close() self.__clients[id].close()
except Exception as exception: except Exception as exception:
show_exception and self.anp.exception(exception, "web_socket_server_client_close_exception", { self.anp.exception(exception, "web_socket_server_client_close_exception", {
"client": id, "client": id,
"port": self.__port, "port": self.__port,
"host": self.__host "host": self.__host
@ -47,9 +56,12 @@ class WebSocketServerDriver:
def __handler(self:Self, client:WebSocketClient) -> None: def __handler(self:Self, client:WebSocketClient) -> None:
id:str = str(id(client)) id:int = self.__client_i
self.__client_i += 1
self.__clients[id] = client self.__clients[id] = client
self.on_new_client.execute(client, id) self.on_new_client.execute(id)
self.anp.print("info", "web_socket_server_client_connected", { self.anp.print("info", "web_socket_server_client_connected", {
"client": id, "client": id,
@ -64,19 +76,38 @@ class WebSocketServerDriver:
message:str = client.recv() message:str = client.recv()
if message is None: if message is None:
break break
self.on_message.execute(client, message) self.on_message.execute(id, message)
except Exception as exception: except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_exception", { self.anp.exception(exception, "web_socket_server_client_exception", {
"client": id, "client": id,
"port": self.__port, "port": self.__port,
"host": self.__host "host": self.__host
}) })
self.on_error.execute(client, exception) self.on_error.execute(id, exception)
finally: finally:
self.close_client(id, False) self.close_client(id)
self.on_close.execute(client) self.on_close.execute(id)
self.anp.print("info", "web_socket_server_client_disconnected", { self.anp.print("info", "web_socket_server_client_disconnected", {
"client": id, "client": id,
"port": self.__port, "port": self.__port,
"host": self.__host "host": self.__host
}) })
def send(self:Self, data:str, ids:int|Sequence[int]) -> bool:
success:bool = True
id:int
for id in ids if Check.is_array(ids) else [ids]:
if id in self.__clients:
try:
self.__clients[id].send(data)
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_send_exception", {
"client": id,
"port": self.__port,
"host": self.__host
})
success = False
return success

View File

@ -12,6 +12,7 @@ from Interfaces.Managers.ControllersManagerInterface import ControllersManagerIn
from Interfaces.Managers.DispatchesManagerInterface import DispatchesManagerInterface from Interfaces.Managers.DispatchesManagerInterface import DispatchesManagerInterface
from Interfaces.Managers.IndexesManagerInterface import IndexesManagerInterface from Interfaces.Managers.IndexesManagerInterface import IndexesManagerInterface
from Interfaces.Managers.RoutesManagerInterface import RoutesManagerInterface from Interfaces.Managers.RoutesManagerInterface import RoutesManagerInterface
from Interfaces.Managers.WebSocketsServerManagerInterface import WebSocketsServerManagerInterface
class AnPInterface(ABC): class AnPInterface(ABC):
@ -25,6 +26,7 @@ class AnPInterface(ABC):
self.dispatches:DispatchesManagerInterface = None self.dispatches:DispatchesManagerInterface = None
self.indexes:IndexesManagerInterface = None self.indexes:IndexesManagerInterface = None
self.routes:RoutesManagerInterface = None self.routes:RoutesManagerInterface = None
self.web_sockets_servers:WebSocketsServerManagerInterface = None
@abstractmethod @abstractmethod
def update(self:Self) -> None:pass def update(self:Self) -> None:pass

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Optional, Sequence
from abc import ABC, abstractmethod
from Abstracts.WebSocketsServerAbstract import WebSocketsServerAbstract
from Application.Event import Event
class WebSocketsServerManagerInterface(ABC):
def __init__(self:Self, anp:Any, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.on_new_client:Event = None
self.on_message:Event = None
self.on_close:Event = None
self.on_error:Event = None
@abstractmethod
def update(self:Self) -> None:pass
@abstractmethod
def reset(self:Self) -> None:pass
@abstractmethod
def close(self:Self) -> None:pass
@abstractmethod
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:pass
@abstractmethod
def remove(self:Self, names:str|Sequence[str]) -> None:pass
@abstractmethod
def get(self:Self, name:str) -> WebSocketsServerAbstract|None:pass
@abstractmethod
def send(self:Self,
name:str,
controller:str,
method:str,
data:Optional[Any] = None,
clients:Optional[int|Sequence[int]] = None,
code:int = 200
) -> None:pass

View File

@ -35,7 +35,10 @@ class ModelsManager:
key:str key:str
for key in Common.get_keys(keys): for key in Common.get_keys(keys):
if key in self.__models and isinstance(self.__models[key], Type): if key in self.__models and (
isinstance(self.__models[key], Type) or
issubclass(self.__models[key], Type)
):
return self.__models[key] return self.__models[key]
return None return None
@ -47,7 +50,7 @@ class ModelsManager:
for key, model in subinputs.items(): for key, model in subinputs.items():
if Common.is_mark_key(key) and model is None: if Common.is_mark_key(key) and model is None:
continue continue
if isinstance(model, ModelAbstract) and ( if issubclass(model, ModelAbstract) and (
overwrite or key not in self.__models overwrite or key not in self.__models
): ):
self.__models[key] = model self.__models[key] = model

View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Self, Optional, Sequence
from Interfaces.Application.AnPInterface import AnPInterface
from Abstracts.WebSocketsServerAbstract import WebSocketsServerAbstract
from Application.Event import Event
from Models.RequestModel import RequestModel
from Utils.Checks import Check
from Utils.Common import Common
class WebSocketsServerManager:
def __init__(self:Self, anp:AnPInterface, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.anp:AnPInterface = anp
self.__web_sockets:dict[str, WebSocketsServerAbstract] = {}
self.on_new_client:Event = Event()
self.on_message:Event = Event()
self.on_close:Event = Event()
self.on_error:Event = Event()
self.on_message.add(self.__receive)
self.update()
def update(self:Self) -> None:
key:str
for key in ("default_web_sockets_server_files", "web_sockets_server_files", "default_web_sockets_server", "web_sockets_server"):
self.add(self.anp.settings.get(key), True)
def reset(self:Self) -> None:
self.__web_sockets = {}
self.update()
def close(self:Self) -> None:
for web_socket in self.__web_sockets.values():
web_socket.close()
self.__web_sockets = {}
def __set(self:Self, name:str, web_socket:WebSocketsServerAbstract) -> None:
self.__web_sockets[name] = web_socket
web_socket.on_new_client.add(lambda id:self.on_new_client(web_socket, id, name))
web_socket.on_message.add(lambda id, message:self.on_message(web_socket, id, message, name))
web_socket.on_close.add(lambda id:self.on_close(web_socket, id, name))
web_socket.on_error.add(lambda id, exception:self.on_error(web_socket, id, exception, name))
def add(self:Self, inputs:Any|None, overwrite:bool = False) -> None:
subinputs:dict[str, Any|None]
for subinputs in Common.load_json(inputs, True):
key:str
value:Any|None
for key, value in subinputs.items():
if isinstance(value, WebSocketsServerAbstract):
if overwrite or key not in self.__web_sockets:
self.__set(key, value)
elif Check.is_dictionary(value):
if overwrite or key not in self.__web_sockets:
_type:str|None = Common.get_value("type", value)
if _type is None:
continue
Class:type[WebSocketsServerAbstract] = self.anp.models.get(WebSocketsServerAbstract, _type)
Class and issubclass(Class, WebSocketsServerAbstract) and self.__set(key, Class(self.anp, value))
def remove(self:Self, names:str|Sequence[str]) -> None:
for name in names if Check.is_array(names) else [names]:
if name in self.__web_sockets:
try:
self.__web_sockets[name].close()
del self.__web_sockets[name]
except Exception as exception:
self.anp.exception(exception, "web_socket_server_close_exception", {"name": name})
def get(self:Self, name:str) -> WebSocketsServerAbstract|None:
return self.__web_sockets.get(name)
def send(self:Self,
name:str,
controller:str,
method:str,
data:Optional[Any] = None,
clients:Optional[int|Sequence[int]] = None,
code:int = 200
) -> None:
if name in self.__web_sockets:
try:
self.__web_sockets[name].send(Common.data_encode({
"ok" : code >= 200 and code < 300,
"code" : code,
"controller" : controller,
"method" : method,
"data" : data
}), clients)
except Exception as exception:
self.anp.exception(exception, "web_socket_server_send_exception", {"name": name})
def __receive(self:Self, web_socket:WebSocketsServerAbstract, client:int, raw_data:str, name:str) -> None:
data:dict[str, Any|None] = Common.data_decode(raw_data)
if "controller" in data and "method" in data:
request:RequestModel = RequestModel()
request.data = data.get("data", None)
request.variables["web_socket"] = web_socket
request.variables["client_id"] = client
request.variables["web_socket_name"] = name
self.anp.controllers.execute(
data["controller"],
data["method"],
request
)

View File

@ -21,6 +21,7 @@ class RequestModel:
self.response_code:int = 0 self.response_code:int = 0
self.response_headers:dict[str, Any|None] = {} self.response_headers:dict[str, Any|None] = {}
self.callback:Callable[[RequestModel, str|bytes|None], None]|None = None self.callback:Callable[[RequestModel, str|bytes|None], None]|None = None
self.data:Any|None = None
def get(self:Self, key:str|Sequence[str], default:Optional[Any] = None) -> Any|None: def get(self:Self, key:str|Sequence[str], default:Optional[Any] = None) -> Any|None:
return Common.get_value(key, ( return Common.get_value(key, (

View File

@ -8,6 +8,8 @@ from json import loads as json_decode
from io import FileIO from io import FileIO
from mimetypes import guess_type as get_mime_by_extension from mimetypes import guess_type as get_mime_by_extension
from inspect import FrameInfo, stack as get_stack from inspect import FrameInfo, stack as get_stack
from json import dumps as json_encode, loads as json_decode
from base64 import b64encode as base64_encode, b64decode as base64_decode
from Utils.Checks import Check from Utils.Checks import Check
from Utils.Patterns import RE from Utils.Patterns import RE
@ -272,3 +274,54 @@ class Common:
@staticmethod @staticmethod
def is_file(path:str) -> bool: def is_file(path:str) -> bool:
return is_file(path) return is_file(path)
@staticmethod
def json_encode(data:dict[str, Any|None]|Sequence[Any|None]) -> str|None:
try:
return json_encode(data)
except Exception as exception:
pass
return None
@staticmethod
def json_decode(data:str) -> dict[str, Any|None]|Sequence[Any|None]|None:
try:
return json_decode(data)
except Exception as exception:
pass
return None
@staticmethod
def base64_encode(data:bytes) -> str|None:
try:
return base64_encode(data).decode("utf-8")
except Exception as exception:
pass
return None
@staticmethod
def base64_decode(data:str) -> bytes|None:
try:
return base64_decode(data.encode("utf-8"))
except Exception as exception:
pass
return None
@classmethod
def data_encode(cls:type[Self], data:Any|None) -> str|None:
if Check.is_json_data(data):
data = cls.json_encode(data)
elif not Check.is_string(data):
data = str(data)
return cls.base64_encode(data.encode("utf-8"))
@classmethod
def data_decode(cls:type[Self], data:str) -> Any|None:
if Check.is_string(data):
data = cls.base64_decode(data)
if Check.is_string(data):
try:
return cls.json_decode(data)
except Exception as exception:
return data
return None

View File

@ -4,10 +4,12 @@
from typing import Any from typing import Any
from Application.AnP import AnP from Application.AnP import AnP
from Controllers.AIController import AIController from Controllers.AIController import AIController
from Drivers.WebSocketServerDriver import WebSocketServerDriver
inputs:dict[str, dict[str, Any|None]] = { inputs:dict[str, dict[str, Any|None]] = {
"default_models" : { "default_models" : {
"AIController" : AIController "AIController" : AIController,
"WebSocketServerDriver" : WebSocketServerDriver,
} }
} }