⏺ Synapse is running and responding to HTTP requests! The curl to /_matrix/client/versions returned a full JSON response with all the Matrix versions and unstable features. The response was 200 OK, processed in 0.001 seconds.

{"versions":["r0.0.1","r0.1.0",...,"v1.12"],"unstable_features":{...}}

  And the access log shows:
  Processed request: 0.001sec 1250B 200 "GET /_matrix/client/versions HTTP/1.1" "curl/8.7.1"

  Synapse is running on pure asyncio + aiohttp, without Twisted, responding to Matrix client API requests.
This commit is contained in:
Matthew Hodgson
2026-03-22 23:39:55 +00:00
parent abb68d849d
commit 8f20720f60
4 changed files with 57 additions and 42 deletions
+7
View File
@@ -475,6 +475,13 @@ def start_reactor(
def main() -> None:
homeserver_config = load_or_generate_config(sys.argv[1:])
# Create an asyncio event loop early so that NativeClock.call_later()
# and other asyncio primitives work during homeserver setup (before
# the loop is actually running).
import asyncio
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Create a logging context as soon as possible so we can start associating
# everything with this homeserver.
with LoggingContext(name="main", server_name=homeserver_config.server.server_name):
+40 -10
View File
@@ -632,21 +632,51 @@ except ImportError:
from synapse.http.resource import Resource as _ResourceBase
class StaticResource(_StaticBase):
"""
A resource that represents a plain non-interpreted file or directory.
class StaticResource(_ResourceBase):
"""Serve static files from a directory.
Differs from the File resource by adding clickjacking protection.
Pure-Python replacement for Twisted's ``twisted.web.static.File``.
Adds clickjacking protection headers.
"""
def render_GET(self, request: Any) -> bytes:
isLeaf = True
def __init__(self, path: str) -> None:
super().__init__()
self._path = path
def render_GET(self, request: "SynapseRequest") -> None:
import mimetypes
import os
set_clickjacking_protection_headers(request)
return super().render_GET(request)
def directoryListing(self) -> Any:
if notFound is not None:
return notFound()
return None
# Build the file path from the request URI
uri = request.path
if isinstance(uri, bytes):
uri = uri.decode("utf-8")
# Strip the prefix to get the relative file path
# The URI will be like /prefix/file.css — we need just the filename
parts = uri.rstrip("/").split("/")
filename = parts[-1] if parts else "index.html"
filepath = os.path.join(self._path, filename)
if not os.path.isfile(filepath):
request.setResponseCode(404)
request.write(b"Not Found")
request.finish()
return
content_type, _ = mimetypes.guess_type(filepath)
if content_type:
request.setHeader(b"Content-Type", content_type.encode("ascii"))
with open(filepath, "rb") as f:
data = f.read()
request.setHeader(b"Content-Length", str(len(data)).encode("ascii"))
request.write(data)
request.finish()
class UnrecognizedRequestResource(_ResourceBase):
+6 -29
View File
@@ -3,7 +3,7 @@ import time
from logging import Handler, LogRecord
from logging.handlers import MemoryHandler
from threading import Thread
from typing import Optional, cast
from typing import Any, Optional
class PeriodicallyFlushingMemoryHandler(MemoryHandler):
@@ -24,21 +24,20 @@ class PeriodicallyFlushingMemoryHandler(MemoryHandler):
target: Handler | None = None,
flushOnClose: bool = True,
period: float = 5.0,
reactor: Optional[IReactorCore] = None,
reactor: Any = None,
) -> None:
"""
period: the period between automatic flushes
reactor: if specified, a custom reactor to use. If not specifies,
defaults to the globally-installed reactor.
Log entries will be flushed immediately until this reactor has
started.
reactor: legacy parameter, ignored. Kept for config compat.
"""
super().__init__(capacity, flushLevel, target, flushOnClose)
self._flush_period: float = period
self._active: bool = True
self._reactor_started = False
# In the asyncio world there is no delayed reactor start —
# the event loop is running by the time log messages are emitted.
self._reactor_started = True
self._flushing_thread: Thread = Thread(
name="PeriodicallyFlushingMemoryHandler flushing thread",
@@ -47,28 +46,6 @@ class PeriodicallyFlushingMemoryHandler(MemoryHandler):
)
self._flushing_thread.start()
def on_reactor_running() -> None:
self._reactor_started = True
reactor_to_use: IReactorCore
if reactor is None:
reactor_to_use = cast(IReactorCore, global_reactor)
else:
reactor_to_use = reactor
# Call our hook when the reactor start up
#
# type-ignore: Ideally, we'd use `Clock.call_when_running(...)`, but
# `PeriodicallyFlushingMemoryHandler` is instantiated via Python logging
# configuration, so it's not straightforward to pass in the homeserver's clock
# (and we don't want to burden other peoples logging config with the details).
#
# The important reason why we want to use `Clock.call_when_running` is so that
# the callback runs with a logcontext as we want to know which server the logs
# came from. But since we don't log anything in the callback, it's safe to
# ignore the lint here.
reactor_to_use.callWhenRunning(on_reactor_running) # type: ignore[prefer-synapse-clock-call-when-running]
def shouldFlush(self, record: LogRecord) -> bool:
"""
Before reactor start-up, log everything immediately.
+4 -3
View File
@@ -326,9 +326,10 @@ class HomeServer(metaclass=abc.ABCMeta):
config: The full config for the homeserver.
"""
if not reactor:
reactor = cast(ISynapseReactor, _reactor)
# The "reactor" is a legacy concept from Twisted. In the asyncio
# world it's just a handle that some code passes around. We accept
# None and store it; code that actually needs an event loop should
# use asyncio.get_running_loop() at call time.
self._reactor = reactor
self.hostname = hostname
# the key we use to sign events and requests