#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,
"AnP_HTTPServer_end" : null,
"AnP_WebSocketServer_start" : null,
"web_socket_server_host" : "",
"web_socket_server_port" : 18765,
"AnP_WebSocketServer_end" : null,
"AnP_WebSocketsServerManager_start" : null,
"default_web_sockets_server" : {
"anp" : {
"type" : "WebSocketServerDriver",
"host" : "localhost",
"port" : 18765
}
},
"AnP_WebSocketsServerManager_end" : null,
"AnP_TitlesManager_start" : null,
"default_titles_files" : [

View File

@ -1,6 +1,7 @@
"use strict";
import {Common} from "../Utils/Common.ecma.js";
import {Event} from "../Application/Event.ecma.js";
/**
* @class WebSocketsDriver
@ -12,6 +13,12 @@ import {Common} from "../Utils/Common.ecma.js";
*/
export const WebSocketsDriver = (function(){
/**
* @callback continue_callback
* @param {boolean} ok
* @return {void}
*/
/**
* @constructs WebSocketsDriver
* @param {!(Object.<string, any|null>|Array.<any|null>)} inputs
@ -23,19 +30,39 @@ export const WebSocketsDriver = (function(){
/** @type {WebSocketsDriver} */
const self = this;
let server = null,
/** @type {WebSocket|null} */
let client = null,
/** @type {string|null} */
url = null,
/** @type {boolean} */
started = false;
/** @type {Event} */
this.on_open = new Event();
/** @type {Event} */
this.on_message = new Event();
/** @type {Event} */
this.on_error = new Event();
/** @type {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) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(started){
@ -44,27 +71,24 @@ export const WebSocketsDriver = (function(){
};
started = true;
server = new WebSocket(`ws://${host}:${port}`);
client = new WebSocket(url);
server.onopen = on_open;
server.onmessage = event => {
self.on_message.trigger(event);
};
server.onerror = event => {
self.on_error.trigger(event);
};
server.onclose = event => {
self.on_close.trigger(event);
};
client.onopen = on_open;
client.onmessage = on_message
client.onerror = on_error;
client.onclose = on_close;
return true;
};
/**
* @param {?continue_callback} callback
* @returns {boolean}
* @access public
*/
this.close = (callback = null) => {
/** @type {continue_callback} */
const end = ok => Common.execute(callback, ok);
if(!started){
@ -73,34 +97,30 @@ export const WebSocketsDriver = (function(){
};
started = false;
server.close();
server = null;
client.close();
client = null;
return true;
};
const open = event => {
const on_open = event => {
console.log(["client", event]);
};
const new_message = event => {
const on_message = event => {
console.log(["message", event]);
};
const error = event => {
const on_error = event => {
console.log(["error", event]);
};
const close = event => {
const on_close = event => {
console.log(["close", event]);
};
this.send = (ids, message) => {
Common.get_array(ids).forEach(id => {});
};
this.send_to_all = message => {
self.send(Object.keys(clients), message);
this.send = message => {
client.send(message);
};
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.IndexesManager import IndexesManager
from Managers.RoutesManager import RoutesManager
from Managers.WebSocketsServerManager import WebSocketsServerManager
from Drivers.HTTPDriver import HTTPDriver
from Utils.Common import Common
from Utils.Patterns import RE
@ -45,6 +46,7 @@ class AnP:
self.dispatches:DispatchesManager = DispatchesManager(self)
self.indexes:IndexesManager = IndexesManager(self)
self.routes:RoutesManager = RoutesManager(self)
self.web_sockets_servers:WebSocketsServerManager = WebSocketsServerManager(self)
self.http_server:HTTPDriver = HTTPDriver(self)
def update(self:Self) -> None:
@ -58,6 +60,7 @@ class AnP:
self.dispatches.update()
self.indexes.update()
self.routes.update()
self.web_sockets_servers.update()
self.http_server.update()
def reset(self:Self) -> None:
@ -71,10 +74,12 @@ class AnP:
self.dispatches.reset()
self.indexes.reset()
self.routes.reset()
self.web_sockets_servers.reset()
self.http_server.reset()
def close(self:Self) -> None:
self.__working = False
self.web_sockets_servers.close()
self.http_server.close()
def working(self:Self) -> bool:

View File

@ -34,3 +34,6 @@ class Event:
def remove(self:Self, i:int) -> None:
if i in self.__events:
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 Interfaces.Application.AnPInterface import AnPInterface
from requests import post as Post
class OllamaDriver:

View File

@ -1,29 +1,38 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from threading import Thread
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 import ServerConnection as WebSocketServer, ClientConnection as WebSocketClient
from Application.Event import Event
from websockets import Server as WebSocketServer, ClientConnection as WebSocketClient
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:
self.anp:AnPInterface = anp
self.on_new_client:Event = Event()
self.on_message:Event = Event()
self.on_close:Event = Event()
self.on_error:Event = Event()
super().__init__(inputs)
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.__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()
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:
id:str
@ -31,14 +40,14 @@ class WebSocketServerDriver:
for id in tuple(self.__clients.keys()):
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:
try:
self.__clients[id].close()
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,
"port": self.__port,
"host": self.__host
@ -47,9 +56,12 @@ class WebSocketServerDriver:
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.on_new_client.execute(client, id)
self.on_new_client.execute(id)
self.anp.print("info", "web_socket_server_client_connected", {
"client": id,
@ -64,19 +76,38 @@ class WebSocketServerDriver:
message:str = client.recv()
if message is None:
break
self.on_message.execute(client, message)
self.on_message.execute(id, message)
except Exception as exception:
self.anp.exception(exception, "web_socket_server_client_exception", {
"client": id,
"port": self.__port,
"host": self.__host
})
self.on_error.execute(client, exception)
self.on_error.execute(id, exception)
finally:
self.close_client(id, False)
self.on_close.execute(client)
self.close_client(id)
self.on_close.execute(id)
self.anp.print("info", "web_socket_server_client_disconnected", {
"client": id,
"port": self.__port,
"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.IndexesManagerInterface import IndexesManagerInterface
from Interfaces.Managers.RoutesManagerInterface import RoutesManagerInterface
from Interfaces.Managers.WebSocketsServerManagerInterface import WebSocketsServerManagerInterface
class AnPInterface(ABC):
@ -25,6 +26,7 @@ class AnPInterface(ABC):
self.dispatches:DispatchesManagerInterface = None
self.indexes:IndexesManagerInterface = None
self.routes:RoutesManagerInterface = None
self.web_sockets_servers:WebSocketsServerManagerInterface = None
@abstractmethod
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
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 None
@ -47,7 +50,7 @@ class ModelsManager:
for key, model in subinputs.items():
if Common.is_mark_key(key) and model is None:
continue
if isinstance(model, ModelAbstract) and (
if issubclass(model, ModelAbstract) and (
overwrite or key not in self.__models
):
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_headers:dict[str, Any|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:
return Common.get_value(key, (

View File

@ -8,6 +8,8 @@ from json import loads as json_decode
from io import FileIO
from mimetypes import guess_type as get_mime_by_extension
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.Patterns import RE
@ -272,3 +274,54 @@ class Common:
@staticmethod
def is_file(path:str) -> bool:
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 Application.AnP import AnP
from Controllers.AIController import AIController
from Drivers.WebSocketServerDriver import WebSocketServerDriver
inputs:dict[str, dict[str, Any|None]] = {
"default_models" : {
"AIController" : AIController
"AIController" : AIController,
"WebSocketServerDriver" : WebSocketServerDriver,
}
}