server.py 20 KB

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