Made almost the entire networking guts replaceable.

This commit is contained in:
Andrew Bastien 2020-04-12 10:24:31 -07:00
parent 21d62e651a
commit 948d14a0fb
11 changed files with 106 additions and 42 deletions

View file

@ -10,6 +10,7 @@
- New `utils.format_grid` for easily displaying long lists of items in a block. - New `utils.format_grid` for easily displaying long lists of items in a block.
- Using `lunr` search indexing for better `help` matching and suggestions. Also improve - Using `lunr` search indexing for better `help` matching and suggestions. Also improve
the main help command's default listing output. the main help command's default listing output.
- Made most of the networking classes such as Protocols and the SessionHandlers replaceable via `settings.py` for modding enthusiasts.
### Already in master ### Already in master

View file

@ -15,7 +15,7 @@ import zlib # Used in Compressed class
import pickle import pickle
from twisted.internet.defer import DeferredList, Deferred from twisted.internet.defer import DeferredList, Deferred
from evennia.utils.utils import to_str, variable_from_module from evennia.utils.utils import variable_from_module
# delayed import # delayed import
_LOGGER = None _LOGGER = None

View file

@ -25,7 +25,7 @@ import evennia
evennia._init() evennia._init()
from evennia.utils.utils import get_evennia_version, mod_import, make_iter from evennia.utils.utils import get_evennia_version, mod_import, make_iter, class_from_module
from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
from evennia.utils import logger from evennia.utils import logger
from evennia.server.webserver import EvenniaReverseProxyResource from evennia.server.webserver import EvenniaReverseProxyResource
@ -261,6 +261,7 @@ if TELNET_ENABLED:
# Start telnet game connections # Start telnet game connections
from evennia.server.portal import telnet from evennia.server.portal import telnet
_telnet_protocol = class_from_module(settings.TELNET_PROTOCOL_CLASS)
for interface in TELNET_INTERFACES: for interface in TELNET_INTERFACES:
ifacestr = "" ifacestr = ""
@ -270,7 +271,7 @@ if TELNET_ENABLED:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = telnet.TelnetServerFactory() factory = telnet.TelnetServerFactory()
factory.noisy = False factory.noisy = False
factory.protocol = telnet.TelnetProtocol factory.protocol = _telnet_protocol
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
telnet_service = internet.TCPServer(port, factory, interface=interface) telnet_service = internet.TCPServer(port, factory, interface=interface)
telnet_service.setName("EvenniaTelnet%s" % pstring) telnet_service.setName("EvenniaTelnet%s" % pstring)
@ -284,6 +285,7 @@ if SSL_ENABLED:
# Start Telnet+SSL game connection (requires PyOpenSSL). # Start Telnet+SSL game connection (requires PyOpenSSL).
from evennia.server.portal import telnet_ssl from evennia.server.portal import telnet_ssl
_ssl_protocol = class_from_module(settings.SSL_PROTOCOL_CLASS)
for interface in SSL_INTERFACES: for interface in SSL_INTERFACES:
ifacestr = "" ifacestr = ""
@ -294,7 +296,7 @@ if SSL_ENABLED:
factory = protocol.ServerFactory() factory = protocol.ServerFactory()
factory.noisy = False factory.noisy = False
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
factory.protocol = telnet_ssl.SSLProtocol factory.protocol = _ssl_protocol
ssl_context = telnet_ssl.getSSLContext() ssl_context = telnet_ssl.getSSLContext()
if ssl_context: if ssl_context:
@ -317,6 +319,7 @@ if SSH_ENABLED:
# evennia/game if necessary. # evennia/game if necessary.
from evennia.server.portal import ssh from evennia.server.portal import ssh
_ssh_protocol = class_from_module(settings.SSH_PROTOCOL_CLASS)
for interface in SSH_INTERFACES: for interface in SSH_INTERFACES:
ifacestr = "" ifacestr = ""
@ -326,7 +329,7 @@ if SSH_ENABLED:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = ssh.makeFactory( factory = ssh.makeFactory(
{ {
"protocolFactory": ssh.SshProtocol, "protocolFactory": _ssh_protocol,
"protocolArgs": (), "protocolArgs": (),
"sessions": PORTAL_SESSIONS, "sessions": PORTAL_SESSIONS,
} }
@ -345,6 +348,7 @@ if WEBSERVER_ENABLED:
# Start a reverse proxy to relay data to the Server-side webserver # Start a reverse proxy to relay data to the Server-side webserver
websocket_started = False websocket_started = False
_websocket_protocol = class_from_module(settings.WEBSOCKET_PROTOCOL_CLASS)
for interface in WEBSERVER_INTERFACES: for interface in WEBSERVER_INTERFACES:
ifacestr = "" ifacestr = ""
if interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1: if interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1:
@ -379,7 +383,7 @@ if WEBSERVER_ENABLED:
factory = Websocket() factory = Websocket()
factory.noisy = False factory.noisy = False
factory.protocol = webclient.WebSocketClient factory.protocol = _websocket_protocol
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
websocket_service = internet.TCPServer(port, factory, interface=w_interface) websocket_service = internet.TCPServer(port, factory, interface=w_interface)
websocket_service.setName("EvenniaWebSocket%s:%s" % (w_ifacestr, port)) websocket_service.setName("EvenniaWebSocket%s:%s" % (w_ifacestr, port))

View file

@ -9,6 +9,7 @@ from twisted.internet import reactor
from django.conf import settings from django.conf import settings
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC, PDISCONNALL from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC, PDISCONNALL
from evennia.utils.logger import log_trace from evennia.utils.logger import log_trace
from evennia.utils.utils import class_from_module
# module import # module import
_MOD_IMPORT = None _MOD_IMPORT = None
@ -32,9 +33,10 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
# ------------------------------------------------------------- # -------------------------------------------------------------
# Portal-SessionHandler class # Portal-SessionHandler class
# ------------------------------------------------------------- # -------------------------------------------------------------
_BASE_HANDLER_CLASS = class_from_module(settings.SERVER_SESSION_HANDLER_CLASS)
class PortalSessionHandler(SessionHandler): class PortalSessionHandler(_BASE_HANDLER_CLASS):
""" """
This object holds the sessions connected to the portal at any time. This object holds the sessions connected to the portal at any time.
It is synced with the server's equivalent SessionHandler over the AMP It is synced with the server's equivalent SessionHandler over the AMP
@ -463,4 +465,6 @@ class PortalSessionHandler(SessionHandler):
log_trace() log_trace()
PORTAL_SESSIONS = PortalSessionHandler() _portal_sessionhandler_class = class_from_module(settings.PORTAL_SESSION_HANDLER_CLASS)
PORTAL_SESSIONS = _portal_sessionhandler_class()

View file

@ -43,10 +43,9 @@ from twisted.conch import interfaces as iconch
from twisted.python import components from twisted.python import components
from django.conf import settings from django.conf import settings
from evennia.server import session
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.utils import ansi from evennia.utils import ansi
from evennia.utils.utils import to_str from evennia.utils.utils import to_str, class_from_module
_RE_N = re.compile(r"\|n$") _RE_N = re.compile(r"\|n$")
_RE_SCREENREADER_REGEX = re.compile( _RE_SCREENREADER_REGEX = re.compile(
@ -74,6 +73,8 @@ and put them here:
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE _PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
) )
_BASE_SESSION = class_from_module(settings.BASE_SESSION_CLASS)
# not used atm # not used atm
class SSHServerFactory(protocol.ServerFactory): class SSHServerFactory(protocol.ServerFactory):
@ -84,7 +85,7 @@ class SSHServerFactory(protocol.ServerFactory):
return "SSH" return "SSH"
class SshProtocol(Manhole, session.Session): class SshProtocol(Manhole, _BASE_SESSION):
""" """
Each account connecting over ssh gets this protocol assigned to Each account connecting over ssh gets this protocol assigned to
them. All communication between game and account goes through them. All communication between game and account goes through

View file

@ -18,7 +18,7 @@ except ImportError as error:
raise ImportError(errstr.format(err=error)) raise ImportError(errstr.format(err=error))
from django.conf import settings from django.conf import settings
from evennia.server.portal.telnet import TelnetProtocol from evennia.utils.utils import class_from_module
_GAME_DIR = settings.GAME_DIR _GAME_DIR = settings.GAME_DIR
@ -43,8 +43,10 @@ example (linux, using the openssl program):
{exestring} {exestring}
""" """
_TELNET_PROTOCOL = class_from_module(settings.TELNET_PROTOCOL_CLASS)
class SSLProtocol(TelnetProtocol):
class SSLProtocol(_TELNET_PROTOCOL):
""" """
Communication is the same as telnet, except data transfer Communication is the same as telnet, except data transfer
is done with encryption. is done with encryption.

View file

@ -25,12 +25,11 @@ from twisted.conch.telnet import (
LINEMODE_TRAPSIG, LINEMODE_TRAPSIG,
) )
from django.conf import settings from django.conf import settings
from evennia.server.session import Session
from evennia.server.portal import ttype, mssp, telnet_oob, naws, suppress_ga from evennia.server.portal import ttype, mssp, telnet_oob, naws, suppress_ga
from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi from evennia.utils import ansi
from evennia.utils.utils import to_bytes from evennia.utils.utils import to_bytes, class_from_module
_RE_N = re.compile(r"\|n$") _RE_N = re.compile(r"\|n$")
_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
@ -56,6 +55,10 @@ _HTTP_WARNING = bytes(
) )
_BASE_SESSION = class_from_module(settings.BASE_SESSION_CLASS)
class TelnetServerFactory(protocol.ServerFactory): class TelnetServerFactory(protocol.ServerFactory):
"This is only to name this better in logs" "This is only to name this better in logs"
noisy = False noisy = False
@ -64,7 +67,7 @@ class TelnetServerFactory(protocol.ServerFactory):
return "Telnet" return "Telnet"
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION):
""" """
Each player connecting over telnet (ie using most traditional mud Each player connecting over telnet (ie using most traditional mud
clients) gets a telnet protocol instance assigned to them. All clients) gets a telnet protocol instance assigned to them. All

View file

@ -17,10 +17,8 @@ from the command line and interprets it as an Evennia Command: `["text", ["look"
import re import re
import json import json
import html import html
from twisted.internet.protocol import Protocol
from django.conf import settings from django.conf import settings
from evennia.server.session import Session from evennia.utils.utils import mod_import, class_from_module
from evennia.utils.utils import to_str, mod_import
from evennia.utils.ansi import parse_ansi from evennia.utils.ansi import parse_ansi
from evennia.utils.text2html import parse_html from evennia.utils.text2html import parse_html
from autobahn.twisted.websocket import WebSocketServerProtocol from autobahn.twisted.websocket import WebSocketServerProtocol
@ -39,8 +37,10 @@ CLOSE_NORMAL = WebSocketServerProtocol.CLOSE_STATUS_CODE_NORMAL
# called when the browser is navigating away from the page # called when the browser is navigating away from the page
GOING_AWAY = WebSocketServerProtocol.CLOSE_STATUS_CODE_GOING_AWAY GOING_AWAY = WebSocketServerProtocol.CLOSE_STATUS_CODE_GOING_AWAY
_BASE_SESSION = class_from_module(settings.BASE_SESSION_CLASS)
class WebSocketClient(WebSocketServerProtocol, Session):
class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION):
""" """
Implements the server-side of the Websocket connection. Implements the server-side of the Websocket connection.
""" """

View file

@ -36,24 +36,7 @@ class Session(object):
""" """
# names of attributes that should be affected by syncing. # names of attributes that should be affected by syncing.
_attrs_to_sync = ( _attrs_to_sync = settings.SESSION_SYNC_ATTRS
"protocol_key",
"address",
"suid",
"sessid",
"uid",
"csessid",
"uname",
"logged_in",
"puid",
"conn_time",
"cmd_last",
"cmd_last_visible",
"cmd_total",
"protocol_flags",
"server_data",
"cmdset_storage_string",
)
def init_session(self, protocol_key, address, sessionhandler): def init_session(self, protocol_key, address, sessionhandler):
""" """

View file

@ -23,6 +23,7 @@ from evennia.utils.utils import (
make_iter, make_iter,
delay, delay,
callables_from_module, callables_from_module,
class_from_module
) )
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
@ -857,5 +858,9 @@ class ServerSessionHandler(SessionHandler):
log_trace() log_trace()
SESSION_HANDLER = ServerSessionHandler() # import class from settings
_session_handler_class = class_from_module(settings.SERVER_SESSION_HANDLER_CLASS)
# Instantiate class. These globals are used to provide singleton-like behavior.
SESSION_HANDLER = _session_handler_class()
SESSIONS = SESSION_HANDLER # legacy SESSIONS = SESSION_HANDLER # legacy

View file

@ -465,9 +465,6 @@ CHANNEL_COMMAND_CLASS = "evennia.comms.channelhandler.ChannelCommand"
# Typeclasses and other paths # Typeclasses and other paths
###################################################################### ######################################################################
# Server-side session class used.
SERVER_SESSION_CLASS = "evennia.server.serversession.ServerSession"
# These are paths that will be prefixed to the paths given if the # These are paths that will be prefixed to the paths given if the
# immediately entered path fail to find a typeclass. It allows for # immediately entered path fail to find a typeclass. It allows for
# shorter input strings. They must either base off the game directory # shorter input strings. They must either base off the game directory
@ -511,6 +508,70 @@ START_LOCATION = "#2"
# issues. # issues.
TYPECLASS_AGGRESSIVE_CACHE = True TYPECLASS_AGGRESSIVE_CACHE = True
######################################################################
# Networking Replaceable Guts
######################################################################
# Modify the things below at your own risk. This is here be dragons territory.
# The Base Session Class is used as a parent class for all Protocols such as
# Telnet and SSH.) Changing this could be really dangerous. It will cascade
# to tons of classes. You generally shouldn't need to touch protocols.
BASE_SESSION_CLASS = "evennia.server.session.Session"
# Telnet Protocol inherits from whatever above BASE_SESSION_CLASS is specified.
# It is used for all telnet connections, and is also inherited by the SSL Protocol
# (which is just TLS + Telnet).
TELNET_PROTOCOL_CLASS = "evennia.server.portal.telnet.TelnetProtocol"
SSL_PROTOCOL_CLASS = "evennia.server.portal.ssl.SSLProtocol"
# Websocket Client Protocol. This inherits from BASE_SESSION_CLASS. It is used
# for all webclient connections.
WEBSOCKET_PROTOCOL_CLASS = "evennia.server.portal.webclient.WebSocketClient"
# Protocol for the SSH interface. This inherits from BASE_SESSION_CLASS.
SSH_PROTOCOL_CLASS = "evennia.server.portal.ssh.SshProtocol"
# Server-side session class used. This will inherit from BASE_SESSION_CLASS.
# This one isn't as dangerous to replace.
SERVER_SESSION_CLASS = "evennia.server.serversession.ServerSession"
# The Server SessionHandler manages all ServerSessions, handling logins,
# ensuring the login process happens smoothly, handling expected and
# unexpected disconnects. You shouldn't need to touch it, but you can.
# Replace it to implement altered game logic.
SERVER_SESSION_HANDLER_CLASS = "evennia.server.sessionhandler.ServerSessionHandler"
# The Portal SessionHandler manages all incoming connections regardless of
# the protocol in use. It is responsible for keeping them going and informing
# the Server Session Handler of the connections and synchronizing them across the
# AMP connection. You shouldn't ever need to change this. But you can.
PORTAL_SESSION_HANDLER_CLASS = "evennia.server.portal.portalsessionhandler.PortalSessionHandler"
# These are members / properties / attributes kept on both Server and
# Portal Sessions. They are sync'd at various points, such as logins and
# reloads. If you add to this, you may need to adjust the class __init__
# so the additions have somewhere to go. These must be simple things that
# can be pickled - stuff you could serialize to JSON is best.
SESSION_SYNC_ATTRS = (
"protocol_key",
"address",
"suid",
"sessid",
"uid",
"csessid",
"uname",
"logged_in",
"puid",
"conn_time",
"cmd_last",
"cmd_last_visible",
"cmd_total",
"protocol_flags",
"server_data",
"cmdset_storage_string"
)
###################################################################### ######################################################################
# Options and validators # Options and validators
###################################################################### ######################################################################