server.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import json
  2. import logging
  3. from collections import deque
  4. from io import SEEK_END, BytesIO
  5. from typing import Callable
  6. import attr
  7. from typing_extensions import Deque
  8. from zope.interface import implementer
  9. from twisted.internet import address, threads, udp
  10. from twisted.internet._resolver import SimpleResolverComplexifier
  11. from twisted.internet.defer import Deferred, fail, succeed
  12. from twisted.internet.error import DNSLookupError
  13. from twisted.internet.interfaces import (
  14. IReactorPluggableNameResolver,
  15. IReactorTCP,
  16. IResolverSimple,
  17. )
  18. from twisted.python.failure import Failure
  19. from twisted.test.proto_helpers import AccumulatingProtocol, MemoryReactorClock
  20. from twisted.web.http import unquote
  21. from twisted.web.http_headers import Headers
  22. from twisted.web.server import Site
  23. from synapse.http.site import SynapseRequest
  24. from synapse.util import Clock
  25. from tests.utils import setup_test_homeserver as _sth
  26. logger = logging.getLogger(__name__)
  27. class TimedOutException(Exception):
  28. """
  29. A web query timed out.
  30. """
  31. @attr.s
  32. class FakeChannel:
  33. """
  34. A fake Twisted Web Channel (the part that interfaces with the
  35. wire).
  36. """
  37. site = attr.ib(type=Site)
  38. _reactor = attr.ib()
  39. result = attr.ib(type=dict, default=attr.Factory(dict))
  40. _producer = None
  41. @property
  42. def json_body(self):
  43. if not self.result:
  44. raise Exception("No result yet.")
  45. return json.loads(self.result["body"].decode("utf8"))
  46. @property
  47. def code(self):
  48. if not self.result:
  49. raise Exception("No result yet.")
  50. return int(self.result["code"])
  51. @property
  52. def headers(self):
  53. if not self.result:
  54. raise Exception("No result yet.")
  55. h = Headers()
  56. for i in self.result["headers"]:
  57. h.addRawHeader(*i)
  58. return h
  59. def writeHeaders(self, version, code, reason, headers):
  60. self.result["version"] = version
  61. self.result["code"] = code
  62. self.result["reason"] = reason
  63. self.result["headers"] = headers
  64. def write(self, content):
  65. assert isinstance(content, bytes), "Should be bytes! " + repr(content)
  66. if "body" not in self.result:
  67. self.result["body"] = b""
  68. self.result["body"] += content
  69. def registerProducer(self, producer, streaming):
  70. self._producer = producer
  71. self.producerStreaming = streaming
  72. def _produce():
  73. if self._producer:
  74. self._producer.resumeProducing()
  75. self._reactor.callLater(0.1, _produce)
  76. if not streaming:
  77. self._reactor.callLater(0.0, _produce)
  78. def unregisterProducer(self):
  79. if self._producer is None:
  80. return
  81. self._producer = None
  82. def requestDone(self, _self):
  83. self.result["done"] = True
  84. def getPeer(self):
  85. # We give an address so that getClientIP returns a non null entry,
  86. # causing us to record the MAU
  87. return address.IPv4Address("TCP", "127.0.0.1", 3423)
  88. def getHost(self):
  89. return None
  90. @property
  91. def transport(self):
  92. return self
  93. class FakeSite:
  94. """
  95. A fake Twisted Web Site, with mocks of the extra things that
  96. Synapse adds.
  97. """
  98. server_version_string = b"1"
  99. site_tag = "test"
  100. access_logger = logging.getLogger("synapse.access.http.fake")
  101. def make_request(
  102. reactor,
  103. method,
  104. path,
  105. content=b"",
  106. access_token=None,
  107. request=SynapseRequest,
  108. shorthand=True,
  109. federation_auth_origin=None,
  110. content_is_form=False,
  111. ):
  112. """
  113. Make a web request using the given method and path, feed it the
  114. content, and return the Request and the Channel underneath.
  115. Args:
  116. method (bytes/unicode): The HTTP request method ("verb").
  117. path (bytes/unicode): The HTTP path, suitably URL encoded (e.g.
  118. escaped UTF-8 & spaces and such).
  119. content (bytes or dict): The body of the request. JSON-encoded, if
  120. a dict.
  121. shorthand: Whether to try and be helpful and prefix the given URL
  122. with the usual REST API path, if it doesn't contain it.
  123. federation_auth_origin (bytes|None): if set to not-None, we will add a fake
  124. Authorization header pretenting to be the given server name.
  125. content_is_form: Whether the content is URL encoded form data. Adds the
  126. 'Content-Type': 'application/x-www-form-urlencoded' header.
  127. Returns:
  128. Tuple[synapse.http.site.SynapseRequest, channel]
  129. """
  130. if not isinstance(method, bytes):
  131. method = method.encode("ascii")
  132. if not isinstance(path, bytes):
  133. path = path.encode("ascii")
  134. # Decorate it to be the full path, if we're using shorthand
  135. if (
  136. shorthand
  137. and not path.startswith(b"/_matrix")
  138. and not path.startswith(b"/_synapse")
  139. ):
  140. path = b"/_matrix/client/r0/" + path
  141. path = path.replace(b"//", b"/")
  142. if not path.startswith(b"/"):
  143. path = b"/" + path
  144. if isinstance(content, str):
  145. content = content.encode("utf8")
  146. site = FakeSite()
  147. channel = FakeChannel(site, reactor)
  148. req = request(channel)
  149. req.process = lambda: b""
  150. req.content = BytesIO(content)
  151. # Twisted expects to be at the end of the content when parsing the request.
  152. req.content.seek(SEEK_END)
  153. req.postpath = list(map(unquote, path[1:].split(b"/")))
  154. if access_token:
  155. req.requestHeaders.addRawHeader(
  156. b"Authorization", b"Bearer " + access_token.encode("ascii")
  157. )
  158. if federation_auth_origin is not None:
  159. req.requestHeaders.addRawHeader(
  160. b"Authorization",
  161. b"X-Matrix origin=%s,key=,sig=" % (federation_auth_origin,),
  162. )
  163. if content:
  164. if content_is_form:
  165. req.requestHeaders.addRawHeader(
  166. b"Content-Type", b"application/x-www-form-urlencoded"
  167. )
  168. else:
  169. # Assume the body is JSON
  170. req.requestHeaders.addRawHeader(b"Content-Type", b"application/json")
  171. req.requestReceived(method, path, b"1.1")
  172. return req, channel
  173. def wait_until_result(clock, request, timeout=100):
  174. """
  175. Wait until the request is finished.
  176. """
  177. clock.run()
  178. x = 0
  179. while not request.finished:
  180. # If there's a producer, tell it to resume producing so we get content
  181. if request._channel._producer:
  182. request._channel._producer.resumeProducing()
  183. x += 1
  184. if x > timeout:
  185. raise TimedOutException("Timed out waiting for request to finish.")
  186. clock.advance(0.1)
  187. def render(request, resource, clock):
  188. request.render(resource)
  189. wait_until_result(clock, request)
  190. @implementer(IReactorPluggableNameResolver)
  191. class ThreadedMemoryReactorClock(MemoryReactorClock):
  192. """
  193. A MemoryReactorClock that supports callFromThread.
  194. """
  195. def __init__(self):
  196. self.threadpool = ThreadPool(self)
  197. self._tcp_callbacks = {}
  198. self._udp = []
  199. lookups = self.lookups = {}
  200. self._thread_callbacks = deque() # type: Deque[Callable[[], None]]()
  201. @implementer(IResolverSimple)
  202. class FakeResolver:
  203. def getHostByName(self, name, timeout=None):
  204. if name not in lookups:
  205. return fail(DNSLookupError("OH NO: unknown %s" % (name,)))
  206. return succeed(lookups[name])
  207. self.nameResolver = SimpleResolverComplexifier(FakeResolver())
  208. super().__init__()
  209. def listenUDP(self, port, protocol, interface="", maxPacketSize=8196):
  210. p = udp.Port(port, protocol, interface, maxPacketSize, self)
  211. p.startListening()
  212. self._udp.append(p)
  213. return p
  214. def callFromThread(self, callback, *args, **kwargs):
  215. """
  216. Make the callback fire in the next reactor iteration.
  217. """
  218. cb = lambda: callback(*args, **kwargs)
  219. # it's not safe to call callLater() here, so we append the callback to a
  220. # separate queue.
  221. self._thread_callbacks.append(cb)
  222. def getThreadPool(self):
  223. return self.threadpool
  224. def add_tcp_client_callback(self, host, port, callback):
  225. """Add a callback that will be invoked when we receive a connection
  226. attempt to the given IP/port using `connectTCP`.
  227. Note that the callback gets run before we return the connection to the
  228. client, which means callbacks cannot block while waiting for writes.
  229. """
  230. self._tcp_callbacks[(host, port)] = callback
  231. def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
  232. """Fake L{IReactorTCP.connectTCP}.
  233. """
  234. conn = super().connectTCP(
  235. host, port, factory, timeout=timeout, bindAddress=None
  236. )
  237. callback = self._tcp_callbacks.get((host, port))
  238. if callback:
  239. callback()
  240. return conn
  241. def advance(self, amount):
  242. # first advance our reactor's time, and run any "callLater" callbacks that
  243. # makes ready
  244. super().advance(amount)
  245. # now run any "callFromThread" callbacks
  246. while True:
  247. try:
  248. callback = self._thread_callbacks.popleft()
  249. except IndexError:
  250. break
  251. callback()
  252. # check for more "callLater" callbacks added by the thread callback
  253. # This isn't required in a regular reactor, but it ends up meaning that
  254. # our database queries can complete in a single call to `advance` [1] which
  255. # simplifies tests.
  256. #
  257. # [1]: we replace the threadpool backing the db connection pool with a
  258. # mock ThreadPool which doesn't really use threads; but we still use
  259. # reactor.callFromThread to feed results back from the db functions to the
  260. # main thread.
  261. super().advance(0)
  262. class ThreadPool:
  263. """
  264. Threadless thread pool.
  265. """
  266. def __init__(self, reactor):
  267. self._reactor = reactor
  268. def start(self):
  269. pass
  270. def stop(self):
  271. pass
  272. def callInThreadWithCallback(self, onResult, function, *args, **kwargs):
  273. def _(res):
  274. if isinstance(res, Failure):
  275. onResult(False, res)
  276. else:
  277. onResult(True, res)
  278. d = Deferred()
  279. d.addCallback(lambda x: function(*args, **kwargs))
  280. d.addBoth(_)
  281. self._reactor.callLater(0, d.callback, True)
  282. return d
  283. def setup_test_homeserver(cleanup_func, *args, **kwargs):
  284. """
  285. Set up a synchronous test server, driven by the reactor used by
  286. the homeserver.
  287. """
  288. server = _sth(cleanup_func, *args, **kwargs)
  289. # Make the thread pool synchronous.
  290. clock = server.get_clock()
  291. for database in server.get_datastores().databases:
  292. pool = database._db_pool
  293. def runWithConnection(func, *args, **kwargs):
  294. return threads.deferToThreadPool(
  295. pool._reactor,
  296. pool.threadpool,
  297. pool._runWithConnection,
  298. func,
  299. *args,
  300. **kwargs,
  301. )
  302. def runInteraction(interaction, *args, **kwargs):
  303. return threads.deferToThreadPool(
  304. pool._reactor,
  305. pool.threadpool,
  306. pool._runInteraction,
  307. interaction,
  308. *args,
  309. **kwargs,
  310. )
  311. pool.runWithConnection = runWithConnection
  312. pool.runInteraction = runInteraction
  313. pool.threadpool = ThreadPool(clock._reactor)
  314. pool.running = True
  315. # We've just changed the Databases to run DB transactions on the same
  316. # thread, so we need to disable the dedicated thread behaviour.
  317. server.get_datastores().main.USE_DEDICATED_DB_THREADS_FOR_EVENT_FETCHING = False
  318. return server
  319. def get_clock():
  320. clock = ThreadedMemoryReactorClock()
  321. hs_clock = Clock(clock)
  322. return clock, hs_clock
  323. @attr.s(cmp=False)
  324. class FakeTransport:
  325. """
  326. A twisted.internet.interfaces.ITransport implementation which sends all its data
  327. straight into an IProtocol object: it exists to connect two IProtocols together.
  328. To use it, instantiate it with the receiving IProtocol, and then pass it to the
  329. sending IProtocol's makeConnection method:
  330. server = HTTPChannel()
  331. client.makeConnection(FakeTransport(server, self.reactor))
  332. If you want bidirectional communication, you'll need two instances.
  333. """
  334. other = attr.ib()
  335. """The Protocol object which will receive any data written to this transport.
  336. :type: twisted.internet.interfaces.IProtocol
  337. """
  338. _reactor = attr.ib()
  339. """Test reactor
  340. :type: twisted.internet.interfaces.IReactorTime
  341. """
  342. _protocol = attr.ib(default=None)
  343. """The Protocol which is producing data for this transport. Optional, but if set
  344. will get called back for connectionLost() notifications etc.
  345. """
  346. disconnecting = False
  347. disconnected = False
  348. connected = True
  349. buffer = attr.ib(default=b"")
  350. producer = attr.ib(default=None)
  351. autoflush = attr.ib(default=True)
  352. def getPeer(self):
  353. return None
  354. def getHost(self):
  355. return None
  356. def loseConnection(self, reason=None):
  357. if not self.disconnecting:
  358. logger.info("FakeTransport: loseConnection(%s)", reason)
  359. self.disconnecting = True
  360. if self._protocol:
  361. self._protocol.connectionLost(reason)
  362. # if we still have data to write, delay until that is done
  363. if self.buffer:
  364. logger.info(
  365. "FakeTransport: Delaying disconnect until buffer is flushed"
  366. )
  367. else:
  368. self.connected = False
  369. self.disconnected = True
  370. def abortConnection(self):
  371. logger.info("FakeTransport: abortConnection()")
  372. if not self.disconnecting:
  373. self.disconnecting = True
  374. if self._protocol:
  375. self._protocol.connectionLost(None)
  376. self.disconnected = True
  377. def pauseProducing(self):
  378. if not self.producer:
  379. return
  380. self.producer.pauseProducing()
  381. def resumeProducing(self):
  382. if not self.producer:
  383. return
  384. self.producer.resumeProducing()
  385. def unregisterProducer(self):
  386. if not self.producer:
  387. return
  388. self.producer = None
  389. def registerProducer(self, producer, streaming):
  390. self.producer = producer
  391. self.producerStreaming = streaming
  392. def _produce():
  393. d = self.producer.resumeProducing()
  394. d.addCallback(lambda x: self._reactor.callLater(0.1, _produce))
  395. if not streaming:
  396. self._reactor.callLater(0.0, _produce)
  397. def write(self, byt):
  398. if self.disconnecting:
  399. raise Exception("Writing to disconnecting FakeTransport")
  400. self.buffer = self.buffer + byt
  401. # always actually do the write asynchronously. Some protocols (notably the
  402. # TLSMemoryBIOProtocol) get very confused if a read comes back while they are
  403. # still doing a write. Doing a callLater here breaks the cycle.
  404. if self.autoflush:
  405. self._reactor.callLater(0.0, self.flush)
  406. def writeSequence(self, seq):
  407. for x in seq:
  408. self.write(x)
  409. def flush(self, maxbytes=None):
  410. if not self.buffer:
  411. # nothing to do. Don't write empty buffers: it upsets the
  412. # TLSMemoryBIOProtocol
  413. return
  414. if self.disconnected:
  415. return
  416. if getattr(self.other, "transport") is None:
  417. # the other has no transport yet; reschedule
  418. if self.autoflush:
  419. self._reactor.callLater(0.0, self.flush)
  420. return
  421. if maxbytes is not None:
  422. to_write = self.buffer[:maxbytes]
  423. else:
  424. to_write = self.buffer
  425. logger.info("%s->%s: %s", self._protocol, self.other, to_write)
  426. try:
  427. self.other.dataReceived(to_write)
  428. except Exception as e:
  429. logger.exception("Exception writing to protocol: %s", e)
  430. return
  431. self.buffer = self.buffer[len(to_write) :]
  432. if self.buffer and self.autoflush:
  433. self._reactor.callLater(0.0, self.flush)
  434. if not self.buffer and self.disconnecting:
  435. logger.info("FakeTransport: Buffer now empty, completing disconnect")
  436. self.disconnected = True
  437. def connect_client(reactor: IReactorTCP, client_id: int) -> AccumulatingProtocol:
  438. """
  439. Connect a client to a fake TCP transport.
  440. Args:
  441. reactor
  442. factory: The connecting factory to build.
  443. """
  444. factory = reactor.tcpClients.pop(client_id)[2]
  445. client = factory.buildProtocol(None)
  446. server = AccumulatingProtocol()
  447. server.makeConnection(FakeTransport(client, reactor))
  448. client.makeConnection(FakeTransport(server, reactor))
  449. return client, server