server.py 14 KB

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