server.py 12 KB

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