#wip: Socker HTTP Server Driver.

This commit is contained in:
KyMAN 2026-06-15 09:00:28 +02:00
parent 711e347346
commit fef6627c83
9 changed files with 399 additions and 12 deletions

View File

@ -0,0 +1,22 @@
# Usamos la imagen oficial de Dart Sass basada en Alpine
FROM dart:stable AS build
# Descargamos e instalamos la última versión de Dart Sass
RUN apt-get update && apt-get install -y curl tar
RUN curl -L https://github.com/sass/dart-sass/releases/download/1.101.0/dart-sass-1.101.0-linux-x64.tar.gz | tar -xzf - -C /opt
# Imagen final limpia y ligera
FROM alpine:latest
RUN apk add --no-cache libc6-compat libstdc++
# Copiamos los binarios desde la etapa anterior
COPY --from=build /opt/dart-sass /opt/dart-sass
# Añadimos SASS al PATH del contenedor
ENV PATH="/opt/dart-sass:${PATH}"
# Configuramos el directorio de trabajo
WORKDIR /src
# Permitimos que reciba los comandos directamente
ENTRYPOINT ["sass"]

View File

@ -67,6 +67,136 @@
"default_routes_files" : "/JSON/AnP.routes.json", "default_routes_files" : "/JSON/AnP.routes.json",
"AnP_RoutesManager_end" : null, "AnP_RoutesManager_end" : null,
"AnP_HTTPAbstracts_start" : null,
"default_http_messages" : {
"100" : "Continue",
"101" : "Switching Protocols",
"102" : "Processing",
"103" : "Early Hints",
"200" : "OK",
"201" : "Created",
"202" : "Accepted",
"203" : "Non-Authoritative Information",
"204" : "No Content",
"205" : "Reset Content",
"206" : "Partial Content",
"207" : "Multi-Status",
"208" : "Already Reported",
"226" : "IM Used",
"300" : "Multiple Choices",
"301" : "Moved Permanently",
"302" : "Found",
"303" : "See Other",
"304" : "Not Modified",
"305" : "Use Proxy",
"306" : "Switch Proxy",
"307" : "Temporary Redirect",
"308" : "Permanent Redirect",
"400" : "Bad Request",
"401" : "Unauthorized",
"402" : "Payment Required",
"403" : "Forbidden",
"404" : "Not Found",
"405" : "Method Not Allowed",
"406" : "Not Acceptable",
"407" : "Proxy Authentication Required",
"408" : "Request Timeout",
"409" : "Conflict",
"410" : "Gone",
"411" : "Length Required",
"412" : "Precondition Failed",
"413" : "Payload Too Large",
"414" : "URI Too Long",
"415" : "Unsupported Media Type",
"416" : "Range Not Satisfiable",
"417" : "Expectation Failed",
"418" : "I'm a teapot",
"421" : "Misdirected Request",
"422" : "Unprocessable Content",
"423" : "Locked",
"424" : "Failed Dependency",
"425" : "Too Early",
"426" : "Upgrade Required",
"428" : "Precondition Required",
"429" : "Too Many Requests",
"431" : "Request Header Fields Too Large",
"451" : "Unavailable For Legal Reasons",
"500" : "Internal Server Error",
"501" : "Not Implemented",
"502" : "Bad Gateway",
"503" : "Service Unavailable",
"504" : "Gateway Timeout",
"505" : "HTTP Version Not Supported",
"506" : "Variant Also Negotiates",
"507" : "Insufficient Storage",
"508" : "Loop Detected",
"510" : "Not Extended",
"511" : "Network Authentication Required",
"218" : "This is fine",
"419" : "Page Expired",
"420" : "Method Failure",
"430" : "Request Header Fields Too Large",
"450" : "Blocked by Windows Parental Controls",
"498" : "Invalid Token",
"499" : "Token Required",
"509" : "Bandwidth Limit Exceeded",
"529" : "Site is overloaded",
"530" : "Site is frozen",
"540" : "Temporarily Disabled",
"598" : "Network read timeout error",
"599" : "Network Connect Timeout Error",
"783" : "Unexpected Token",
"440" : "Login Time-out",
"449" : "Retry With",
"444" : "No Response",
"494" : "Request header too large",
"495" : "SSL Certificate Error",
"496" : "SSL Certificate Required",
"497" : "HTTP Request Sent to HTTPS Port",
"520" : "Web Server Returned an Unknown Error",
"521" : "Web Server Is Down",
"522" : "Connection Timed Out",
"523" : "Origin Is Unreachable",
"524" : "A Timeout Occurred",
"525" : "SSL Handshake Failed",
"526" : "Invalid SSL Certificate",
"527" : "Railgun Error",
"561" : "Unauthorized",
"110" : "Response is Stale",
"111" : "Revalidation Failed",
"112" : "Disconnected Operation",
"113" : "Heuristic Expiration",
"199" : "Miscellaneous Warning",
"214" : "Transformation Applied",
"299" : "Miscellaneous Persistent Warning"
},
"protocol" : "HTTP",
"protocol_version" : "1.1",
"default_protocol_code" : 200,
"http_server_name" : "AnP",
"http_server_version" : "0.0.1",
"default_cors" : "*",
"http_access_control_maximum_age" : 84600,
"http_keep_alive_timeout" : 5,
"http_keep_alive_maximum" : 100,
"default_http_charset" : "UTF-8",
"default_http_mime" : "text/html",
"http_accept_range" : "bytes",
"http_response_header" : [
"{protocol}/{protocol_version} {protocol_code} {protocol_message}",
"Date: {date}",
"Server: {server}/{server_version}",
"Last-Modified: {last_modified} ",
"Accept-Ranges: {accept_range}",
"Content-Length: {response_length}",
"Access-Control-Max-Age: {access_control_max_age}",
"Keep-Alive: timeout={keep_alive_timeout}, max={keep_alive_maximum}",
"Access-Control-Allow-Origin: {cors}",
"Connection: Keep-Alive",
"Content-type: {mime}; charset={charset}"
],
"AnP_HTTPAbstracts_end" : null,
"AnP_WebSocketsServersManager_start" : null, "AnP_WebSocketsServersManager_start" : null,
"default_web_sockets_servers" : { "default_web_sockets_servers" : {
"anp" : { "anp" : {
@ -80,7 +210,8 @@
"AnP_HTTPServersManager_start" : null, "AnP_HTTPServersManager_start" : null,
"default_http_servers" : { "default_http_servers" : {
"anp" : { "anp" : {
"type" : "HTTPDriver", "type2" : "HTTPServerDriver",
"type" : "HTTPSocketServerDriver",
"host" : "", "host" : "",
"port" : 18000 "port" : 18000
} }

View File

@ -4,6 +4,7 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Any, Self, Optional, Sequence from typing import Any, Self, Optional, Sequence
from re import Match as REMath from re import Match as REMath
import datetime
from Interfaces.Application.AnPInterface import AnPInterface from Interfaces.Application.AnPInterface import AnPInterface
from Utils.Common import Common from Utils.Common import Common
@ -14,18 +15,37 @@ class HTTPServersAbstract(ABC):
def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None: def __init__(self:Self, anp:AnPInterface, key:str, inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None) -> None:
self.anp:AnPInterface = anp self.anp:AnPInterface = anp
self.__inputs:dict[str, Any|None] = Common.get_dictionary(inputs) self._inputs:dict[str, Any|None] = Common.get_dictionary(inputs)
self.host:str = self.DEFAULT_PORT self.host:str = self.DEFAULT_HOST
self.port:int = self.DEFAULT_HOST self.port:int = self.DEFAULT_PORT
self._print_data:dict[str, Any|None] = { self._print_data:dict[str, Any|None] = {
"port": self.port, "port": self.port,
"host": self.host "host": self.host
} }
self.http_messages:dict[int, str] = {int(code): message for code, message in anp.settings.get((
"default_http_messages", "http_messages"
), inputs).items()}
self.protocol:str = anp.settings.get(("http_protocol", "protocol"), inputs, "HTTP")
self.protocol_version:str = anp.settings.get(("http_protocol_version", "protocol_version"), inputs, "1.1")
self.accept_range:str = anp.settings.get(("http_accept_range", "accept_range"), inputs, "bytes")
self.access_control_max_age:int = anp.settings.get(("http_access_control_max_age", "access_control_max_age"), inputs, 84600)
self.keep_alive_maximum:int = anp.settings.get(("http_keep_alive_maximum", "keep_alive_maximum"), inputs, 100)
self.keep_alive_timeout:int = anp.settings.get(("http_keep_alive_timeout", "keep_alive_timeout"), inputs, 5)
self.cors:str = anp.settings.get(("http_cors", "cors"), inputs, "*")
self.mime:str = anp.settings.get(("default_http_mime", "http_mime", "mime"), inputs, "text/html")
self.charset:str = anp.settings.get(("default_http_charset", "http_charset", "charset"), inputs, "UTF-8")
self._session_timeout:int = anp.settings.get(("sessions_timeout", "timeout"), inputs, 3600) self._session_timeout:int = anp.settings.get(("sessions_timeout", "timeout"), inputs, 3600)
self.key:str = key self.key:str = key
self.update() self.update()
@staticmethod
def format_datetime(datetime:datetime.datetime) -> str:
return datetime.strftime("%a, %d %b %Y %H:%M:%S GMT")
def get_http_message(self:Self, code:int) -> str:
return self.http_messages.get(code, "Unknown Status Code")
def __update_print_data(self:Self) -> None: def __update_print_data(self:Self) -> None:
self._print_data["port"] = self.port self._print_data["port"] = self.port
self._print_data["host"] = self.host self._print_data["host"] = self.host
@ -40,8 +60,8 @@ class HTTPServersAbstract(ABC):
self.close() self.close()
self.port = self.anp.settings.get(("http_server_port", "http_port", "port"), self.__inputs, self.DEFAULT_PORT) self.port = self.anp.settings.get(("http_server_port", "http_port", "port"), self._inputs, self.DEFAULT_PORT)
self.host = self.anp.settings.get(("http_server_host", "http_host", "host"), self.__inputs, self.DEFAULT_HOST) self.host = self.anp.settings.get(("http_server_host", "http_host", "host"), self._inputs, self.DEFAULT_HOST)
self.__update_print_data() self.__update_print_data()
self.anp.settings.get(("http_server_autostart", "http_autostart", "autostart")) and self.start() self.anp.settings.get(("http_server_autostart", "http_autostart", "autostart")) and self.start()

View File

@ -10,7 +10,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from Abstracts.HTTPServersAbstract import HTTPServersAbstract from Abstracts.HTTPServersAbstract import HTTPServersAbstract
class HTTPDriver(HTTPServersAbstract, ModelAbstract): class HTTPServerDriver(HTTPServersAbstract, ModelAbstract):
class HTTPRequestHandler(BaseHTTPRequestHandler): class HTTPRequestHandler(BaseHTTPRequestHandler):

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Optional, Self, Sequence
from threading import Thread
from socket import (
socket as Socket,
AF_INET as ADDRESS_FAMILY_IPV4,
SOCK_STREAM as SOCKET_STREAM,
SOL_SOCKET as SOCKET_LAYER,
SO_REUSEADDR as SOCKET_REUSE_ADDRESS
)
import datetime
from Interfaces.Application.AnPInterface import AnPInterface
from Abstracts.HTTPServersAbstract import HTTPServersAbstract
from Models.RequestModel import RequestModel
from Utils.Common import Common
from Utils.Checks import Check
from Utils.Patterns import RE
class HTTPSocketServerDriver(HTTPServersAbstract):
def __get(self:Self, keys:str|Sequence[str], default:Any|None = None) -> Any|None:
real_keys:list[str] = []
key:str
for key in Common.get_keys(keys):
header:str
for header in (
"http_socket_server_driver",
"http_server_driver",
"http_server",
"http",
""
):
final_key:str = (header + "_" if header else "") + key
if final_key not in keys:
real_keys.append(final_key)
return self.anp.settings.get(real_keys, self._inputs, default)
def __init__(self:Self,
anp:AnPInterface,
key:str,
inputs:Optional[dict[str, Any|None]|Sequence[Any|None]] = None
) -> None:
self.__server:Socket|None = None
self.__thread:Thread|None = None
self.__working:bool = False
super().__init__(anp, key, inputs)
self.__maximum_connections:int = self.__get("maximum_connections", 5)
self.__cache_size:int = self.__get("cache_size", 1024)
self.__response_header:str = self.__get("response_header")
def __listen(self:Self) -> None:
while self.__working:
try:
client:Socket
address:str
port:int
data:bytes = b""
header:str
body:str
header_lines:list[str]
line:str
request:RequestModel = RequestModel()
get_block:str
hash_block:str
response:bytes
response_body:bytes
client, (address, port) = self.__server.accept()
while True:
buffer:bytes = client.recv(self.__cache_size)
data += buffer
if len(buffer) < self.__cache_size:
break
header, body = RE.DOUBLE_NEW_LINE.split(data.decode("utf-8", errors = "ignore"), 1)
header_lines = RE.NEW_LINE.split(header)
(
request.method,
request.path,
get_block,
hash_block,
request.protocol,
request.protocol_version
) = RE.HTTP_REQUEST.match(header_lines[0]).groups()
for line in header_lines[1:]:
if RE.HTTP_HEADER_PARAMETER.match(line):
key:str
value:str
key, value = RE.HTTP_HEADER_PARAMETER.match(line).groups()
key = key.strip().lower()
value = value.strip()
if key in request.request_headers:
if Check.is_array(request.request_headers[key]):
request.request_headers[key].append(value)
else:
request.request_headers[key] = [request.request_headers[key], value]
else:
request.request_headers[key] = value
request.get_variables = self.get_variables_from(get_block)
request.post_variables = self.get_variables_from(body)
request.cookies = self.load_cookies(request.request_headers.get("cookie"))
request.hash_variables = self.get_variables_from(hash_block)
self.anp.routes.go([self.key], request.method, request.path, request)
response_body = (
request.response if Check.is_binary(request.response) else
request.response.encode() if Check.is_string(request.response) else
str(request.response).encode())
response = Common.string_variables(self.__response_header, {
"content_length" : len(response_body),
"protocol" : request.get("protocol", request.protocol or self.protocol),
"protocol_version" : request.get("protocol_version", request.protocol_version or self.protocol_version),
"http_code" : request.response_code,
"http_message" : self.get_http_message(request.response_code),
"date" : self.format_datetime(datetime.datetime.now(datetime.timezone.utc)),
"last_modified" : self.format_datetime(request.last_modified or request.get("last_modified") or datetime.datetime.now(datetime.timezone.utc)),
"accept_range" : request.get("accept_range", self.accept_range),
"response_length" : len(response_body),
"access_control_max_age" : request.get("access_control_max_age", self.access_control_max_age),
"keep_alive_maximum" : request.get("keep_alive_maximum", self.keep_alive_maximum),
"keep_alive_timeout" : request.get("keep_alive_timeout", self.keep_alive_timeout),
"cors" : request.get("cors", self.cors),
"mime" : request.response_mime or request.get("mime", self.mime),
"charset" : request.response_charset or request.get("charset", self.charset)
}).encode() + response_body
client.sendall(response)
client.close()
except Exception as exception:
self.anp.exception(exception, "anp_http_socket_server_driver_run_exception", {
"key" : self.key,
"host" : self.host,
"port" : self.port
})
def start(self:Self) -> None:
self.__working = True
self.__server = Socket(ADDRESS_FAMILY_IPV4, SOCKET_STREAM)
try:
self.__server.setsockopt(SOCKET_LAYER, SOCKET_REUSE_ADDRESS, 1)
self.__server.bind((self.host, self.port))
self.__server.listen(self.__maximum_connections)
self.__thread = Thread(target = self.__listen)
self.__thread.start()
except Exception as exception:
self.anp.exception(exception, "anp_http_socket_server_driver_start_exception", {
"key" : self.key,
"host" : self.host,
"port" : self.port
})
def close(self:Self) -> None:
self.__working = False
if self.__server:
try:
self.__server.close()
except Exception as _:
pass
self.__server = None
if self.__thread:
try:
self.__thread.join()
except Exception as _:
pass
self.__thread = None

View File

@ -13,6 +13,7 @@ class RequestModel:
self.post_variables:dict[str, Any|None] = {} self.post_variables:dict[str, Any|None] = {}
self.get_variables:dict[str, Any|None] = {} self.get_variables:dict[str, Any|None] = {}
self.url_variables:dict[str, Any|None] = {} self.url_variables:dict[str, Any|None] = {}
self.hash_variables:dict[str, Any|None] = {}
self.cookies:dict[str, Any|None] = {} self.cookies:dict[str, Any|None] = {}
self.variables:dict[str, Any|None] = {} self.variables:dict[str, Any|None] = {}
self.request_headers:dict[str, Any|None] = {} self.request_headers:dict[str, Any|None] = {}
@ -20,11 +21,15 @@ class RequestModel:
self.route:RouteAbstract|None = None self.route:RouteAbstract|None = None
self.response:str|bytes|None = None self.response:str|bytes|None = None
self.response_mime:str|None = None self.response_mime:str|None = None
self.response_charset:str|None = None
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 self.data:Any|None = None
self.session:SessionModel|None = session self.session:SessionModel|None = session
self.protocol:str|None = None
self.protocol_version:str|None = None
self.last_modified:str|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:
if self.session is not None: if self.session is not None:
@ -34,7 +39,13 @@ class RequestModel:
if results is not None: if results is not None:
return results return results
return Common.get_value(key, ( return Common.get_value(key, (
self.cookies, self.url_variables, self.get_variables, self.post_variables, self.variables self.cookies,
self.url_variables,
self.get_variables,
self.post_variables,
self.variables,
self.request_headers,
self.hash_variables,
), default) ), default)
def set_variables(self:Self, inputs:dict[str, Any|None], on:Optional[str] = None) -> None: def set_variables(self:Self, inputs:dict[str, Any|None], on:Optional[str] = None) -> None:
@ -43,6 +54,7 @@ class RequestModel:
self.url_variables if on == "url" else self.url_variables if on == "url" else
self.get_variables if on == "get" else self.get_variables if on == "get" else
self.post_variables if on == "post" else self.post_variables if on == "post" else
self.hash_variables if on == "hash" else
self.variables).update(Common.get_dictionary(Common.load_json(inputs))) self.variables).update(Common.get_dictionary(Common.load_json(inputs)))
def set_response(self:Self, data:Any|None) -> None: def set_response(self:Self, data:Any|None) -> None:

View File

@ -15,4 +15,7 @@ class RE:
EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$') EXCEPTION:REPattern = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in ([^\n]+)(.*|[\r\n]*)*$')
NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]') NEW_LINE:REPattern = re_compile(r'\r\n|[\r\n]')
HTTP_VARIABLE:REPattern = re_compile(r'([^=&]+)=([^&]*)') HTTP_VARIABLE:REPattern = re_compile(r'([^=&]+)=([^&]*)')
PARENT_PATH:REPattern = re_compile(r'^(.*)[\/\\][^\/\\]+[\/\\]?$') PARENT_PATH:REPattern = re_compile(r'^(.*)[\/\\][^\/\\]+[\/\\]?$')
DOUBLE_NEW_LINE:REPattern = re_compile(r'(?:\r\n){2}|\r{2}|\n{2}')
HTTP_HEADER_PARAMETER:REPattern = re_compile(r'([^:]+):\s*(.+)')
HTTP_REQUEST:REPattern = re_compile(r'^([^\s]+)\s([^\s\?\#]+)(?:\?([^#]+))?(?:\#([^\s]+))?\s([^\/]+)\/([0-9\.]+)$')

View File

@ -5,7 +5,8 @@ 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 from Drivers.WebSocketServerDriver import WebSocketServerDriver
from Drivers.HTTPDriver import HTTPDriver from Drivers.HTTPSocketServerDriver import HTTPSocketServerDriver
from Drivers.HTTPServerDriver import HTTPServerDriver
from Drivers.OllamaDriver import OllamaDriver from Drivers.OllamaDriver import OllamaDriver
from Drivers.FilesDriver import FilesDriver from Drivers.FilesDriver import FilesDriver
@ -13,7 +14,8 @@ inputs:dict[str, dict[str, Any|None]] = {
"default_models" : { "default_models" : {
"AIController" : AIController, "AIController" : AIController,
"WebSocketServerDriver" : WebSocketServerDriver, "WebSocketServerDriver" : WebSocketServerDriver,
"HTTPDriver" : HTTPDriver, "HTTPSocketServerDriver" : HTTPSocketServerDriver,
"HTTPServerDriver" : HTTPServerDriver,
"OllamaDriver" : OllamaDriver, "OllamaDriver" : OllamaDriver,
"FilesDriver" : FilesDriver "FilesDriver" : FilesDriver
} }

View File

@ -1,3 +1,7 @@
#!/bin/bash #!/bin/bash
directory=`dirname $(readlink -f "$0")` directory=`dirname $(readlink -f "$0")`
sass $directory/../Public/scss/AnP.scss ../Public/scss/AnP.css; # sass $directory/../Public/scss/AnP.scss $directory/../Public/scss/AnP.css
docker build -t dirt-sass -f $directory/../Dockers/Dockerfile.sass.gemini $directory
cd $directory/../Public/scss
docker run -it --rm -v $(pwd):$(pwd) -w $(pwd) dirt-sass AnP.scss AnP.css;
cd $directory