Files
Archipelago/WebHostLib/__init__.py
T
Uriel e6e0bc3042 WebHost: Config option for custom port ranges v2 (#6009)
* Added ability to define custom port ranges the WebHost will use for game servers, instead of pure random.

* - Added better fallback to default port range when a custom range fails
- Updated config to be clearer

* Added ability to define custom port ranges the WebHost will use for game servers, instead of pure random.

* - Added better fallback to default port range when a custom range fails
- Updated config to be clearer

* Updated soft-fail message

* Removed dead import from customserver.py

* Update requirements.txt

Settings requirements to main core branch

* fix what reviewers said and add some improvements

* remove unused argument

* try fixing test with try

* use yaml lists instead of string for config

* fix value type bug on ephemeral type

* reuse sockets with websockets api instead of opening and closing them

* add used ports cache and filter used ports when looking for ports

* fix port randomizer

* Apply suggestions from code review

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* fix some reviews

* use weights for random port and remove more-itertools

* fix net_connections not working on macOS

* rename variables and functions

* lazy init `get_used_ports`

* change `game_ports` to be `tuple`

* fix last_used_ports not being updated locally

* fix random choices and move game_port conversion into tuple

* Apply suggestions from code review

Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>

* use a named tuple on parse_game_ports

* only use ranges

* do it the duck way

* this should check all usable ports before failing

* fix while loop

* add return type to weighted random

* Update WebHostLib/customserver.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* simplify tuple conversion check

* add tests

* reformat file and change `create_random_port_socket` test

* add more test cases for parse_game_ports

* try to prevent busy-looping on create random port socket when doing test

* simplify parse game port tests to one assertListEqual

* make the range lesser for port test

* reduce range on macOS

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Update WebHostLib/customserver.py

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* remove unused import

* Update WebHostLib/customserver.py

Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>

* use generator expressions

* check for 0-tuple

* use some kind of shuffled queue

* update tests

* refactor new port handling into a class (#1)

* change time to monotonic

* Update docs/webhost configuration sample.yaml

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

* add psutil 7.2.2 as requirement

* Update WebHostLib/requirements.txt

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: Lexipherous <jasonnlelong@gmail.com>
Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com>
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
2026-06-20 17:36:18 +02:00

119 lines
4.1 KiB
Python

import base64
import os
import socket
import typing
import uuid
from flask import Flask
from flask_caching import Cache
from flask_compress import Compress
from pony.flask import Pony
from werkzeug.routing import BaseConverter
from Utils import title_sorted, get_file_safe_name
from .cli import CLI
UPLOAD_FOLDER = os.path.relpath('uploads')
LOGS_FOLDER = os.path.relpath('logs')
os.makedirs(LOGS_FOLDER, exist_ok=True)
app = Flask(__name__)
Pony(app)
app.jinja_env.filters['any'] = any
app.jinja_env.filters['all'] = all
app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name
# overwrites of flask default config
app.config["DEBUG"] = False
app.config["PORT"] = 80
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024 # 64 megabyte limit
# if you want to deploy, make sure you have a non-guessable secret key
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
app.config["SESSION_PERMANENT"] = True
app.config["MAX_FORM_MEMORY_SIZE"] = 2 * 1024 * 1024 # 2 MB, needed for large option pages such as SC2
# custom config
app.config["SELFHOST"] = True # application process is in charge of running the websites
app.config["GENERATORS"] = 8 # maximum concurrent world gens
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
app.config["GAME_PORTS"] = ["49152-65535", 0]
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
app.config["JOB_THRESHOLD"] = 1
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
app.config["JOB_TIME"] = 600
# maximum time in seconds since last activity for a room to be hosted
app.config["MAX_ROOM_TIMEOUT"] = 259200
# minimum time in days since last activity for a room to be deleted. 0 to disable.
app.config["ROOM_AUTO_DELETE"] = 0
# memory limit for generator processes in bytes
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
# waitress uses one thread for I/O, these are for processing of views that then get sent
# archipelago.gg uses gunicorn + nginx; ignoring this option
app.config["WAITRESS_THREADS"] = 10
# a default that just works. archipelago.gg runs on mariadb
app.config["PONY"] = {
'provider': 'sqlite',
'filename': os.path.abspath('ap.db3'),
'create_db': True
}
app.config["MAX_ROLL"] = 20
app.config["CACHE_TYPE"] = "SimpleCache"
app.config["HOST_ADDRESS"] = ""
app.config["ASSET_RIGHTS"] = False
cache = Cache()
Compress(app)
CLI(app)
def to_python(value: str) -> uuid.UUID:
if "=" in value or any(c.isspace() for c in value):
raise ValueError("Invalid UUID format")
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=' * (-len(value) % 4)))
def to_url(value: uuid.UUID) -> str:
return base64.urlsafe_b64encode(value.bytes).rstrip(b'=').decode('ascii')
class B64UUIDConverter(BaseConverter):
def to_python(self, value: str) -> uuid.UUID:
return to_python(value)
def to_url(self, value: typing.Any) -> str:
assert isinstance(value, uuid.UUID)
return to_url(value)
# short UUID
app.url_map.converters["suuid"] = B64UUIDConverter
app.jinja_env.filters["suuid"] = to_url
app.jinja_env.filters["title_sorted"] = title_sorted
def register() -> None:
"""Import submodules, triggering their registering on flask routing.
Note: initializes worlds subsystem."""
import importlib
from werkzeug.utils import find_modules
# has automatic patch integration
import worlds.Files
app.jinja_env.filters['is_applayercontainer'] = worlds.Files.is_ap_player_container
from WebHostLib.customserver import run_server_process
for module in find_modules("WebHostLib", include_packages=True):
importlib.import_module(module)
from . import api
app.register_blueprint(api.api_endpoints)