test_wsgi.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.wsgi}.
  5. """
  6. __metaclass__ = type
  7. from sys import exc_info
  8. import tempfile
  9. import traceback
  10. import warnings
  11. from zope.interface.verify import verifyObject
  12. from twisted.python.compat import intToBytes, urlquote, _PY3
  13. from twisted.python.log import addObserver, removeObserver, err
  14. from twisted.python.failure import Failure
  15. from twisted.python.threadable import getThreadID
  16. from twisted.python.threadpool import ThreadPool
  17. from twisted.internet.defer import Deferred, gatherResults
  18. from twisted.internet import reactor
  19. from twisted.internet.error import ConnectionLost
  20. from twisted.trial.unittest import TestCase, SkipTest
  21. from twisted.web import http
  22. from twisted.web.resource import IResource, Resource
  23. from twisted.web.server import Request, Site, version
  24. from twisted.web.wsgi import WSGIResource
  25. from twisted.web.test.test_web import DummyChannel
  26. class SynchronousThreadPool:
  27. """
  28. A single-threaded implementation of part of the L{ThreadPool} interface.
  29. This implementation calls functions synchronously rather than running
  30. them in a thread pool. It is used to make the tests which are not
  31. directly for thread-related behavior deterministic.
  32. """
  33. def callInThread(self, f, *a, **kw):
  34. """
  35. Call C{f(*a, **kw)} in this thread rather than scheduling it to be
  36. called in a thread.
  37. """
  38. try:
  39. f(*a, **kw)
  40. except:
  41. # callInThread doesn't let exceptions propagate to the caller.
  42. # None is always returned and any exception raised gets logged
  43. # later on.
  44. err(None, "Callable passed to SynchronousThreadPool.callInThread failed")
  45. class SynchronousReactorThreads:
  46. """
  47. A single-threaded implementation of part of the L{IReactorThreads}
  48. interface. This implementation assumes that it will only be invoked
  49. from the reactor thread, so it calls functions synchronously rather than
  50. trying to schedule them to run in the reactor thread. It is used in
  51. conjunction with L{SynchronousThreadPool} to make the tests which are
  52. not directly for thread-related behavior deterministic.
  53. """
  54. def callFromThread(self, f, *a, **kw):
  55. """
  56. Call C{f(*a, **kw)} in this thread which should also be the reactor
  57. thread.
  58. """
  59. f(*a, **kw)
  60. class WSGIResourceTests(TestCase):
  61. def setUp(self):
  62. """
  63. Create a L{WSGIResource} with synchronous threading objects and a no-op
  64. application object. This is useful for testing certain things about
  65. the resource implementation which are unrelated to WSGI.
  66. """
  67. self.resource = WSGIResource(
  68. SynchronousReactorThreads(), SynchronousThreadPool(),
  69. lambda environ, startResponse: None)
  70. def test_interfaces(self):
  71. """
  72. L{WSGIResource} implements L{IResource} and stops resource traversal.
  73. """
  74. verifyObject(IResource, self.resource)
  75. self.assertTrue(self.resource.isLeaf)
  76. def test_unsupported(self):
  77. """
  78. A L{WSGIResource} cannot have L{IResource} children. Its
  79. C{getChildWithDefault} and C{putChild} methods raise L{RuntimeError}.
  80. """
  81. self.assertRaises(
  82. RuntimeError,
  83. self.resource.getChildWithDefault,
  84. b"foo", Request(DummyChannel(), False))
  85. self.assertRaises(
  86. RuntimeError,
  87. self.resource.putChild,
  88. b"foo", Resource())
  89. class WSGITestsMixin:
  90. """
  91. @ivar channelFactory: A no-argument callable which will be invoked to
  92. create a new HTTP channel to associate with request objects.
  93. """
  94. channelFactory = DummyChannel
  95. def setUp(self):
  96. self.threadpool = SynchronousThreadPool()
  97. self.reactor = SynchronousReactorThreads()
  98. def lowLevelRender(
  99. self, requestFactory, applicationFactory, channelFactory, method,
  100. version, resourceSegments, requestSegments, query=None, headers=[],
  101. body=None, safe=''):
  102. """
  103. @param method: A C{str} giving the request method to use.
  104. @param version: A C{str} like C{'1.1'} giving the request version.
  105. @param resourceSegments: A C{list} of unencoded path segments which
  106. specifies the location in the resource hierarchy at which the
  107. L{WSGIResource} will be placed, eg C{['']} for I{/}, C{['foo',
  108. 'bar', '']} for I{/foo/bar/}, etc.
  109. @param requestSegments: A C{list} of unencoded path segments giving the
  110. request URI.
  111. @param query: A C{list} of two-tuples of C{str} giving unencoded query
  112. argument keys and values.
  113. @param headers: A C{list} of two-tuples of C{str} giving request header
  114. names and corresponding values.
  115. @param safe: A C{str} giving the bytes which are to be considered
  116. I{safe} for inclusion in the request URI and not quoted.
  117. @return: A L{Deferred} which will be called back with a two-tuple of
  118. the arguments passed which would be passed to the WSGI application
  119. object for this configuration and request (ie, the environment and
  120. start_response callable).
  121. """
  122. def _toByteString(string):
  123. # Twisted's HTTP implementation prefers byte strings. As a
  124. # convenience for tests, string arguments are encoded to an
  125. # ISO-8859-1 byte string (if not already) before being passed on.
  126. if isinstance(string, bytes):
  127. return string
  128. else:
  129. return string.encode('iso-8859-1')
  130. root = WSGIResource(
  131. self.reactor, self.threadpool, applicationFactory())
  132. resourceSegments.reverse()
  133. for seg in resourceSegments:
  134. tmp = Resource()
  135. tmp.putChild(_toByteString(seg), root)
  136. root = tmp
  137. channel = channelFactory()
  138. channel.site = Site(root)
  139. request = requestFactory(channel, False)
  140. for k, v in headers:
  141. request.requestHeaders.addRawHeader(
  142. _toByteString(k), _toByteString(v))
  143. request.gotLength(0)
  144. if body:
  145. request.content.write(body)
  146. request.content.seek(0)
  147. uri = '/' + '/'.join([urlquote(seg, safe) for seg in requestSegments])
  148. if query is not None:
  149. uri += '?' + '&'.join(['='.join([urlquote(k, safe), urlquote(v, safe)])
  150. for (k, v) in query])
  151. request.requestReceived(
  152. _toByteString(method), _toByteString(uri),
  153. b'HTTP/' + _toByteString(version))
  154. return request
  155. def render(self, *a, **kw):
  156. result = Deferred()
  157. def applicationFactory():
  158. def application(*args):
  159. environ, startResponse = args
  160. result.callback(args)
  161. startResponse('200 OK', [])
  162. return iter(())
  163. return application
  164. self.lowLevelRender(
  165. Request, applicationFactory, self.channelFactory, *a, **kw)
  166. return result
  167. def requestFactoryFactory(self, requestClass=Request):
  168. d = Deferred()
  169. def requestFactory(*a, **kw):
  170. request = requestClass(*a, **kw)
  171. # If notifyFinish is called after lowLevelRender returns, it won't
  172. # do the right thing, because the request will have already
  173. # finished. One might argue that this is a bug in
  174. # Request.notifyFinish.
  175. request.notifyFinish().chainDeferred(d)
  176. return request
  177. return d, requestFactory
  178. def getContentFromResponse(self, response):
  179. return response.split(b'\r\n\r\n', 1)[1]
  180. def prepareRequest(self, application=None):
  181. """
  182. Prepare a L{Request} which, when a request is received, captures the
  183. C{environ} and C{start_response} callable passed to a WSGI app.
  184. @param application: An optional WSGI application callable that accepts
  185. the familiar C{environ} and C{start_response} args and returns an
  186. iterable of body content. If not supplied, C{start_response} will
  187. be called with a "200 OK" status and no headers, and no content
  188. will be yielded.
  189. @return: A two-tuple of (C{request}, C{deferred}). The former is a
  190. Twisted L{Request}. The latter is a L{Deferred} which will be
  191. called back with a two-tuple of the arguments passed to a WSGI
  192. application (i.e. the C{environ} and C{start_response} callable),
  193. or will errback with any error arising within the WSGI app.
  194. """
  195. result = Deferred()
  196. def outerApplication(environ, startResponse):
  197. try:
  198. if application is None:
  199. startResponse('200 OK', [])
  200. content = iter(()) # No content.
  201. else:
  202. content = application(environ, startResponse)
  203. except:
  204. result.errback()
  205. startResponse('500 Error', [])
  206. return iter(())
  207. else:
  208. result.callback((environ, startResponse))
  209. return content
  210. resource = WSGIResource(
  211. self.reactor, self.threadpool, outerApplication)
  212. root = Resource()
  213. root.putChild(b"res", resource)
  214. channel = self.channelFactory()
  215. channel.site = Site(root)
  216. class CannedRequest(Request):
  217. """
  218. Convenient L{Request} derivative which has canned values for all
  219. of C{requestReceived}'s arguments.
  220. """
  221. def requestReceived(
  222. self, command=b"GET", path=b"/res", version=b"1.1"):
  223. return Request.requestReceived(
  224. self, command=command, path=path, version=version)
  225. request = CannedRequest(channel, queued=False)
  226. request.gotLength(0) # Initialize buffer for request body.
  227. return request, result
  228. class EnvironTests(WSGITestsMixin, TestCase):
  229. """
  230. Tests for the values in the C{environ} C{dict} passed to the application
  231. object by L{twisted.web.wsgi.WSGIResource}.
  232. """
  233. def environKeyEqual(self, key, value):
  234. def assertEnvironKeyEqual(result):
  235. environ, startResponse = result
  236. self.assertEqual(environ[key], value)
  237. return value
  238. return assertEnvironKeyEqual
  239. def test_environIsDict(self):
  240. """
  241. L{WSGIResource} calls the application object with an C{environ}
  242. parameter which is exactly of type C{dict}.
  243. """
  244. d = self.render('GET', '1.1', [], [''])
  245. def cbRendered(result):
  246. environ, startResponse = result
  247. self.assertIdentical(type(environ), dict)
  248. # Environment keys are always native strings.
  249. for name in environ:
  250. self.assertIsInstance(name, str)
  251. d.addCallback(cbRendered)
  252. return d
  253. def test_requestMethod(self):
  254. """
  255. The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
  256. application contains the HTTP method in the request (RFC 3875, section
  257. 4.1.12).
  258. """
  259. get = self.render('GET', '1.1', [], [''])
  260. get.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
  261. # Also make sure a different request method shows up as a different
  262. # value in the environ dict.
  263. post = self.render('POST', '1.1', [], [''])
  264. post.addCallback(self.environKeyEqual('REQUEST_METHOD', 'POST'))
  265. return gatherResults([get, post])
  266. def test_requestMethodIsNativeString(self):
  267. """
  268. The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
  269. application is always a native string.
  270. """
  271. for method in b"GET", u"GET":
  272. request, result = self.prepareRequest()
  273. request.requestReceived(method)
  274. result.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
  275. self.assertIsInstance(self.successResultOf(result), str)
  276. def test_scriptName(self):
  277. """
  278. The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
  279. application contains the I{abs_path} (RFC 2396, section 3) to this
  280. resource (RFC 3875, section 4.1.13).
  281. """
  282. root = self.render('GET', '1.1', [], [''])
  283. root.addCallback(self.environKeyEqual('SCRIPT_NAME', ''))
  284. emptyChild = self.render('GET', '1.1', [''], [''])
  285. emptyChild.addCallback(self.environKeyEqual('SCRIPT_NAME', '/'))
  286. leaf = self.render('GET', '1.1', ['foo'], ['foo'])
  287. leaf.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
  288. container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
  289. container.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo/'))
  290. internal = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
  291. internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
  292. unencoded = self.render(
  293. 'GET', '1.1', ['foo', '/', b'bar\xff'], ['foo', '/', b'bar\xff'])
  294. # The RFC says "(not URL-encoded)", even though that makes
  295. # interpretation of SCRIPT_NAME ambiguous.
  296. unencoded.addCallback(
  297. self.environKeyEqual('SCRIPT_NAME', '/foo///bar\xff'))
  298. return gatherResults([
  299. root, emptyChild, leaf, container, internal, unencoded])
  300. def test_scriptNameIsNativeString(self):
  301. """
  302. The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
  303. application is always a native string.
  304. """
  305. request, result = self.prepareRequest()
  306. request.requestReceived(path=b"/res")
  307. result.addCallback(self.environKeyEqual('SCRIPT_NAME', '/res'))
  308. self.assertIsInstance(self.successResultOf(result), str)
  309. if _PY3:
  310. # Native strings are rejected by Request.requestReceived() before
  311. # t.w.wsgi has any say in the matter.
  312. request, result = self.prepareRequest()
  313. self.assertRaises(TypeError, request.requestReceived, path=u"/res")
  314. else:
  315. request, result = self.prepareRequest()
  316. request.requestReceived(path=u"/res")
  317. result.addCallback(self.environKeyEqual('SCRIPT_NAME', '/res'))
  318. self.assertIsInstance(self.successResultOf(result), str)
  319. def test_pathInfo(self):
  320. """
  321. The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
  322. application contains the suffix of the request URI path which is not
  323. included in the value for the C{'SCRIPT_NAME'} key (RFC 3875, section
  324. 4.1.5).
  325. """
  326. assertKeyEmpty = self.environKeyEqual('PATH_INFO', '')
  327. root = self.render('GET', '1.1', [], [''])
  328. root.addCallback(self.environKeyEqual('PATH_INFO', '/'))
  329. emptyChild = self.render('GET', '1.1', [''], [''])
  330. emptyChild.addCallback(assertKeyEmpty)
  331. leaf = self.render('GET', '1.1', ['foo'], ['foo'])
  332. leaf.addCallback(assertKeyEmpty)
  333. container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
  334. container.addCallback(assertKeyEmpty)
  335. internalLeaf = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
  336. internalLeaf.addCallback(self.environKeyEqual('PATH_INFO', '/bar'))
  337. internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
  338. internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
  339. unencoded = self.render('GET', '1.1', [], ['foo', '/', b'bar\xff'])
  340. unencoded.addCallback(
  341. self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
  342. return gatherResults([
  343. root, leaf, container, internalLeaf,
  344. internalContainer, unencoded])
  345. def test_pathInfoIsNativeString(self):
  346. """
  347. The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
  348. application is always a native string.
  349. """
  350. request, result = self.prepareRequest()
  351. request.requestReceived(path=b"/res/foo/bar")
  352. result.addCallback(self.environKeyEqual('PATH_INFO', '/foo/bar'))
  353. self.assertIsInstance(self.successResultOf(result), str)
  354. if _PY3:
  355. # Native strings are rejected by Request.requestReceived() before
  356. # t.w.wsgi has any say in the matter.
  357. request, result = self.prepareRequest()
  358. self.assertRaises(
  359. TypeError, request.requestReceived, path=u"/res/foo/bar")
  360. else:
  361. request, result = self.prepareRequest()
  362. request.requestReceived(path=u"/res/foo/bar")
  363. result.addCallback(self.environKeyEqual('PATH_INFO', '/foo/bar'))
  364. self.assertIsInstance(self.successResultOf(result), str)
  365. def test_queryString(self):
  366. """
  367. The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
  368. application contains the portion of the request URI after the first
  369. I{?} (RFC 3875, section 4.1.7).
  370. """
  371. missing = self.render('GET', '1.1', [], [''], None)
  372. missing.addCallback(self.environKeyEqual('QUERY_STRING', ''))
  373. empty = self.render('GET', '1.1', [], [''], [])
  374. empty.addCallback(self.environKeyEqual('QUERY_STRING', ''))
  375. present = self.render('GET', '1.1', [], [''], [('foo', 'bar')])
  376. present.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
  377. unencoded = self.render('GET', '1.1', [], [''], [('/', '/')])
  378. unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '%2F=%2F'))
  379. # "?" is reserved in the <searchpart> portion of a URL. However, it
  380. # seems to be a common mistake of clients to forget to quote it. So,
  381. # make sure we handle that invalid case.
  382. doubleQuestion = self.render(
  383. 'GET', '1.1', [], [''], [('foo', '?bar')], safe='?')
  384. doubleQuestion.addCallback(
  385. self.environKeyEqual('QUERY_STRING', 'foo=?bar'))
  386. return gatherResults([
  387. missing, empty, present, unencoded, doubleQuestion])
  388. def test_queryStringIsNativeString(self):
  389. """
  390. The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
  391. application is always a native string.
  392. """
  393. request, result = self.prepareRequest()
  394. request.requestReceived(path=b"/res?foo=bar")
  395. result.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
  396. self.assertIsInstance(self.successResultOf(result), str)
  397. if _PY3:
  398. # Native strings are rejected by Request.requestReceived() before
  399. # t.w.wsgi has any say in the matter.
  400. request, result = self.prepareRequest()
  401. self.assertRaises(
  402. TypeError, request.requestReceived, path=u"/res?foo=bar")
  403. else:
  404. request, result = self.prepareRequest()
  405. request.requestReceived(path=u"/res?foo=bar")
  406. result.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
  407. self.assertIsInstance(self.successResultOf(result), str)
  408. def test_contentType(self):
  409. """
  410. The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
  411. application contains the value of the I{Content-Type} request header
  412. (RFC 3875, section 4.1.3).
  413. """
  414. missing = self.render('GET', '1.1', [], [''])
  415. missing.addCallback(self.environKeyEqual('CONTENT_TYPE', ''))
  416. present = self.render(
  417. 'GET', '1.1', [], [''], None, [('content-type', 'x-foo/bar')])
  418. present.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
  419. return gatherResults([missing, present])
  420. def test_contentTypeIsNativeString(self):
  421. """
  422. The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
  423. application is always a native string.
  424. """
  425. for contentType in b"x-foo/bar", u"x-foo/bar":
  426. request, result = self.prepareRequest()
  427. request.requestHeaders.addRawHeader(b"Content-Type", contentType)
  428. request.requestReceived()
  429. result.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
  430. self.assertIsInstance(self.successResultOf(result), str)
  431. def test_contentLength(self):
  432. """
  433. The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
  434. application contains the value of the I{Content-Length} request header
  435. (RFC 3875, section 4.1.2).
  436. """
  437. missing = self.render('GET', '1.1', [], [''])
  438. missing.addCallback(self.environKeyEqual('CONTENT_LENGTH', ''))
  439. present = self.render(
  440. 'GET', '1.1', [], [''], None, [('content-length', '1234')])
  441. present.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
  442. return gatherResults([missing, present])
  443. def test_contentLengthIsNativeString(self):
  444. """
  445. The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
  446. application is always a native string.
  447. """
  448. for contentLength in b"1234", u"1234":
  449. request, result = self.prepareRequest()
  450. request.requestHeaders.addRawHeader(b"Content-Length", contentLength)
  451. request.requestReceived()
  452. result.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
  453. self.assertIsInstance(self.successResultOf(result), str)
  454. def test_serverName(self):
  455. """
  456. The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
  457. application contains the best determination of the server hostname
  458. possible, using either the value of the I{Host} header in the request
  459. or the address the server is listening on if that header is not
  460. present (RFC 3875, section 4.1.14).
  461. """
  462. missing = self.render('GET', '1.1', [], [''])
  463. # 10.0.0.1 value comes from a bit far away -
  464. # twisted.test.test_web.DummyChannel.transport.getHost().host
  465. missing.addCallback(self.environKeyEqual('SERVER_NAME', '10.0.0.1'))
  466. present = self.render(
  467. 'GET', '1.1', [], [''], None, [('host', 'example.org')])
  468. present.addCallback(self.environKeyEqual('SERVER_NAME', 'example.org'))
  469. return gatherResults([missing, present])
  470. def test_serverNameIsNativeString(self):
  471. """
  472. The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
  473. application is always a native string.
  474. """
  475. for serverName in b"host.example.com", u"host.example.com":
  476. request, result = self.prepareRequest()
  477. # This is kind of a cheat; getRequestHostname() breaks in Python 3
  478. # when the "Host" request header is set to a native string because
  479. # it tries to split around b":", so we patch the method.
  480. request.getRequestHostname = lambda: serverName
  481. request.requestReceived()
  482. result.addCallback(self.environKeyEqual('SERVER_NAME', 'host.example.com'))
  483. self.assertIsInstance(self.successResultOf(result), str)
  484. def test_serverPort(self):
  485. """
  486. The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
  487. application contains the port number of the server which received the
  488. request (RFC 3875, section 4.1.15).
  489. """
  490. portNumber = 12354
  491. def makeChannel():
  492. channel = DummyChannel()
  493. channel.transport = DummyChannel.TCP()
  494. channel.transport.port = portNumber
  495. return channel
  496. self.channelFactory = makeChannel
  497. d = self.render('GET', '1.1', [], [''])
  498. d.addCallback(self.environKeyEqual('SERVER_PORT', str(portNumber)))
  499. return d
  500. def test_serverPortIsNativeString(self):
  501. """
  502. The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
  503. application is always a native string.
  504. """
  505. request, result = self.prepareRequest()
  506. request.requestReceived()
  507. result.addCallback(self.environKeyEqual('SERVER_PORT', '80'))
  508. self.assertIsInstance(self.successResultOf(result), str)
  509. def test_serverProtocol(self):
  510. """
  511. The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
  512. application contains the HTTP version number received in the request
  513. (RFC 3875, section 4.1.16).
  514. """
  515. old = self.render('GET', '1.0', [], [''])
  516. old.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.0'))
  517. new = self.render('GET', '1.1', [], [''])
  518. new.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.1'))
  519. return gatherResults([old, new])
  520. def test_serverProtocolIsNativeString(self):
  521. """
  522. The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
  523. application is always a native string.
  524. """
  525. for serverProtocol in b"1.1", u"1.1":
  526. request, result = self.prepareRequest()
  527. # In Python 3, native strings can be rejected by Request.write()
  528. # which will cause a crash after the bit we're trying to test, so
  529. # we patch write() out here to do nothing.
  530. request.write = lambda data: None
  531. request.requestReceived(version=b"1.1")
  532. result.addCallback(self.environKeyEqual('SERVER_PROTOCOL', '1.1'))
  533. self.assertIsInstance(self.successResultOf(result), str)
  534. def test_remoteAddr(self):
  535. """
  536. The C{'REMOTE_ADDR'} key of the C{environ} C{dict} passed to the
  537. application contains the address of the client making the request.
  538. """
  539. d = self.render('GET', '1.1', [], [''])
  540. d.addCallback(self.environKeyEqual('REMOTE_ADDR', '192.168.1.1'))
  541. return d
  542. def test_headers(self):
  543. """
  544. HTTP request headers are copied into the C{environ} C{dict} passed to
  545. the application with a C{HTTP_} prefix added to their names.
  546. """
  547. singleValue = self.render(
  548. 'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
  549. def cbRendered(result):
  550. environ, startResponse = result
  551. self.assertEqual(environ['HTTP_FOO'], 'bar')
  552. self.assertEqual(environ['HTTP_BAZ'], 'quux')
  553. singleValue.addCallback(cbRendered)
  554. multiValue = self.render(
  555. 'GET', '1.1', [], [''], None, [('foo', 'bar'), ('foo', 'baz')])
  556. multiValue.addCallback(self.environKeyEqual('HTTP_FOO', 'bar,baz'))
  557. withHyphen = self.render(
  558. 'GET', '1.1', [], [''], None, [('foo-bar', 'baz')])
  559. withHyphen.addCallback(self.environKeyEqual('HTTP_FOO_BAR', 'baz'))
  560. multiLine = self.render(
  561. 'GET', '1.1', [], [''], None, [('foo', 'bar\n\tbaz')])
  562. multiLine.addCallback(self.environKeyEqual('HTTP_FOO', 'bar \tbaz'))
  563. return gatherResults([singleValue, multiValue, withHyphen, multiLine])
  564. def test_wsgiVersion(self):
  565. """
  566. The C{'wsgi.version'} key of the C{environ} C{dict} passed to the
  567. application has the value C{(1, 0)} indicating that this is a WSGI 1.0
  568. container.
  569. """
  570. versionDeferred = self.render('GET', '1.1', [], [''])
  571. versionDeferred.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
  572. return versionDeferred
  573. def test_wsgiRunOnce(self):
  574. """
  575. The C{'wsgi.run_once'} key of the C{environ} C{dict} passed to the
  576. application is set to C{False}.
  577. """
  578. once = self.render('GET', '1.1', [], [''])
  579. once.addCallback(self.environKeyEqual('wsgi.run_once', False))
  580. return once
  581. def test_wsgiMultithread(self):
  582. """
  583. The C{'wsgi.multithread'} key of the C{environ} C{dict} passed to the
  584. application is set to C{True}.
  585. """
  586. thread = self.render('GET', '1.1', [], [''])
  587. thread.addCallback(self.environKeyEqual('wsgi.multithread', True))
  588. return thread
  589. def test_wsgiMultiprocess(self):
  590. """
  591. The C{'wsgi.multiprocess'} key of the C{environ} C{dict} passed to the
  592. application is set to C{False}.
  593. """
  594. process = self.render('GET', '1.1', [], [''])
  595. process.addCallback(self.environKeyEqual('wsgi.multiprocess', False))
  596. return process
  597. def test_wsgiURLScheme(self):
  598. """
  599. The C{'wsgi.url_scheme'} key of the C{environ} C{dict} passed to the
  600. application has the request URL scheme.
  601. """
  602. # XXX Does this need to be different if the request is for an absolute
  603. # URL?
  604. def channelFactory():
  605. channel = DummyChannel()
  606. channel.transport = DummyChannel.SSL()
  607. return channel
  608. self.channelFactory = DummyChannel
  609. httpDeferred = self.render('GET', '1.1', [], [''])
  610. httpDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
  611. self.channelFactory = channelFactory
  612. httpsDeferred = self.render('GET', '1.1', [], [''])
  613. httpsDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
  614. return gatherResults([httpDeferred, httpsDeferred])
  615. def test_wsgiErrors(self):
  616. """
  617. The C{'wsgi.errors'} key of the C{environ} C{dict} passed to the
  618. application is a file-like object (as defined in the U{Input and Errors
  619. Streams<http://www.python.org/dev/peps/pep-0333/#input-and-error-streams>}
  620. section of PEP 333) which converts bytes written to it into events for
  621. the logging system.
  622. """
  623. events = []
  624. addObserver(events.append)
  625. self.addCleanup(removeObserver, events.append)
  626. errors = self.render('GET', '1.1', [], [''])
  627. def cbErrors(result):
  628. environ, startApplication = result
  629. errors = environ['wsgi.errors']
  630. errors.write('some message\n')
  631. errors.writelines(['another\nmessage\n'])
  632. errors.flush()
  633. self.assertEqual(events[0]['message'], ('some message\n',))
  634. self.assertEqual(events[0]['system'], 'wsgi')
  635. self.assertTrue(events[0]['isError'])
  636. self.assertEqual(events[1]['message'], ('another\nmessage\n',))
  637. self.assertEqual(events[1]['system'], 'wsgi')
  638. self.assertTrue(events[1]['isError'])
  639. self.assertEqual(len(events), 2)
  640. errors.addCallback(cbErrors)
  641. return errors
  642. def test_wsgiErrorsExpectsOnlyNativeStringsInPython2(self):
  643. """
  644. The C{'wsgi.errors'} file-like object from the C{environ} C{dict}
  645. expects writes of only native strings in Python 2. Some existing WSGI
  646. applications may write non-native (i.e. C{unicode}) strings so, for
  647. compatibility, these elicit only a warning in Python 2.
  648. """
  649. if _PY3:
  650. raise SkipTest("Not relevant in Python 3")
  651. request, result = self.prepareRequest()
  652. request.requestReceived()
  653. environ, _ = self.successResultOf(result)
  654. errors = environ["wsgi.errors"]
  655. with warnings.catch_warnings(record=True) as caught:
  656. errors.write(u"fred")
  657. self.assertEqual(1, len(caught))
  658. self.assertEqual(UnicodeWarning, caught[0].category)
  659. self.assertEqual(
  660. "write() argument should be str, not u'fred' (unicode)",
  661. str(caught[0].message))
  662. def test_wsgiErrorsAcceptsOnlyNativeStringsInPython3(self):
  663. """
  664. The C{'wsgi.errors'} file-like object from the C{environ} C{dict}
  665. permits writes of only native strings in Python 3, and raises
  666. C{TypeError} for writes of non-native strings.
  667. """
  668. if not _PY3:
  669. raise SkipTest("Relevant only in Python 3")
  670. request, result = self.prepareRequest()
  671. request.requestReceived()
  672. environ, _ = self.successResultOf(result)
  673. errors = environ["wsgi.errors"]
  674. error = self.assertRaises(TypeError, errors.write, b"fred")
  675. self.assertEqual(
  676. "write() argument must be str, not b'fred' (bytes)",
  677. str(error))
  678. class InputStreamTestMixin(WSGITestsMixin):
  679. """
  680. A mixin for L{TestCase} subclasses which defines a number of tests against
  681. L{_InputStream}. The subclass is expected to create a file-like object to
  682. be wrapped by an L{_InputStream} under test.
  683. """
  684. def getFileType(self):
  685. raise NotImplementedError(
  686. "%s.getFile must be implemented" % (self.__class__.__name__,))
  687. def _renderAndReturnReaderResult(self, reader, content):
  688. contentType = self.getFileType()
  689. class CustomizedRequest(Request):
  690. def gotLength(self, length):
  691. # Always allocate a file of the specified type, instead of
  692. # using the base behavior of selecting one depending on the
  693. # length.
  694. self.content = contentType()
  695. def appFactoryFactory(reader):
  696. result = Deferred()
  697. def applicationFactory():
  698. def application(*args):
  699. environ, startResponse = args
  700. result.callback(reader(environ['wsgi.input']))
  701. startResponse('200 OK', [])
  702. return iter(())
  703. return application
  704. return result, applicationFactory
  705. d, appFactory = appFactoryFactory(reader)
  706. self.lowLevelRender(
  707. CustomizedRequest, appFactory, DummyChannel,
  708. 'PUT', '1.1', [], [''], None, [],
  709. content)
  710. return d
  711. def test_readAll(self):
  712. """
  713. Calling L{_InputStream.read} with no arguments returns the entire input
  714. stream.
  715. """
  716. bytes = b"some bytes are here"
  717. d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
  718. d.addCallback(self.assertEqual, bytes)
  719. return d
  720. def test_readSome(self):
  721. """
  722. Calling L{_InputStream.read} with an integer returns that many bytes
  723. from the input stream, as long as it is less than or equal to the total
  724. number of bytes available.
  725. """
  726. bytes = b"hello, world."
  727. d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
  728. d.addCallback(self.assertEqual, b"hel")
  729. return d
  730. def test_readMoreThan(self):
  731. """
  732. Calling L{_InputStream.read} with an integer that is greater than the
  733. total number of bytes in the input stream returns all bytes in the
  734. input stream.
  735. """
  736. bytes = b"some bytes are here"
  737. d = self._renderAndReturnReaderResult(
  738. lambda input: input.read(len(bytes) + 3), bytes)
  739. d.addCallback(self.assertEqual, bytes)
  740. return d
  741. def test_readTwice(self):
  742. """
  743. Calling L{_InputStream.read} a second time returns bytes starting from
  744. the position after the last byte returned by the previous read.
  745. """
  746. bytes = b"some bytes, hello"
  747. def read(input):
  748. input.read(3)
  749. return input.read()
  750. d = self._renderAndReturnReaderResult(read, bytes)
  751. d.addCallback(self.assertEqual, bytes[3:])
  752. return d
  753. def test_readNone(self):
  754. """
  755. Calling L{_InputStream.read} with L{None} as an argument returns all
  756. bytes in the input stream.
  757. """
  758. bytes = b"the entire stream"
  759. d = self._renderAndReturnReaderResult(
  760. lambda input: input.read(None), bytes)
  761. d.addCallback(self.assertEqual, bytes)
  762. return d
  763. def test_readNegative(self):
  764. """
  765. Calling L{_InputStream.read} with a negative integer as an argument
  766. returns all bytes in the input stream.
  767. """
  768. bytes = b"all of the input"
  769. d = self._renderAndReturnReaderResult(
  770. lambda input: input.read(-1), bytes)
  771. d.addCallback(self.assertEqual, bytes)
  772. return d
  773. def test_readline(self):
  774. """
  775. Calling L{_InputStream.readline} with no argument returns one line from
  776. the input stream.
  777. """
  778. bytes = b"hello\nworld"
  779. d = self._renderAndReturnReaderResult(
  780. lambda input: input.readline(), bytes)
  781. d.addCallback(self.assertEqual, b"hello\n")
  782. return d
  783. def test_readlineSome(self):
  784. """
  785. Calling L{_InputStream.readline} with an integer returns at most that
  786. many bytes, even if it is not enough to make up a complete line.
  787. COMPATIBILITY NOTE: the size argument is excluded from the WSGI
  788. specification, but is provided here anyhow, because useful libraries
  789. such as python stdlib's cgi.py assume their input file-like-object
  790. supports readline with a size argument. If you use it, be aware your
  791. application may not be portable to other conformant WSGI servers.
  792. """
  793. bytes = b"goodbye\nworld"
  794. d = self._renderAndReturnReaderResult(
  795. lambda input: input.readline(3), bytes)
  796. d.addCallback(self.assertEqual, b"goo")
  797. return d
  798. def test_readlineMoreThan(self):
  799. """
  800. Calling L{_InputStream.readline} with an integer which is greater than
  801. the number of bytes in the next line returns only the next line.
  802. """
  803. bytes = b"some lines\nof text"
  804. d = self._renderAndReturnReaderResult(
  805. lambda input: input.readline(20), bytes)
  806. d.addCallback(self.assertEqual, b"some lines\n")
  807. return d
  808. def test_readlineTwice(self):
  809. """
  810. Calling L{_InputStream.readline} a second time returns the line
  811. following the line returned by the first call.
  812. """
  813. bytes = b"first line\nsecond line\nlast line"
  814. def readline(input):
  815. input.readline()
  816. return input.readline()
  817. d = self._renderAndReturnReaderResult(readline, bytes)
  818. d.addCallback(self.assertEqual, b"second line\n")
  819. return d
  820. def test_readlineNone(self):
  821. """
  822. Calling L{_InputStream.readline} with L{None} as an argument returns
  823. one line from the input stream.
  824. """
  825. bytes = b"this is one line\nthis is another line"
  826. d = self._renderAndReturnReaderResult(
  827. lambda input: input.readline(None), bytes)
  828. d.addCallback(self.assertEqual, b"this is one line\n")
  829. return d
  830. def test_readlineNegative(self):
  831. """
  832. Calling L{_InputStream.readline} with a negative integer as an argument
  833. returns one line from the input stream.
  834. """
  835. bytes = b"input stream line one\nline two"
  836. d = self._renderAndReturnReaderResult(
  837. lambda input: input.readline(-1), bytes)
  838. d.addCallback(self.assertEqual, b"input stream line one\n")
  839. return d
  840. def test_readlines(self):
  841. """
  842. Calling L{_InputStream.readlines} with no arguments returns a list of
  843. all lines from the input stream.
  844. """
  845. bytes = b"alice\nbob\ncarol"
  846. d = self._renderAndReturnReaderResult(
  847. lambda input: input.readlines(), bytes)
  848. d.addCallback(self.assertEqual, [b"alice\n", b"bob\n", b"carol"])
  849. return d
  850. def test_readlinesSome(self):
  851. """
  852. Calling L{_InputStream.readlines} with an integer as an argument
  853. returns a list of lines from the input stream with the argument serving
  854. as an approximate bound on the total number of bytes to read.
  855. """
  856. bytes = b"123\n456\n789\n0"
  857. d = self._renderAndReturnReaderResult(
  858. lambda input: input.readlines(5), bytes)
  859. def cbLines(lines):
  860. # Make sure we got enough lines to make 5 bytes. Anything beyond
  861. # that is fine too.
  862. self.assertEqual(lines[:2], [b"123\n", b"456\n"])
  863. d.addCallback(cbLines)
  864. return d
  865. def test_readlinesMoreThan(self):
  866. """
  867. Calling L{_InputStream.readlines} with an integer which is greater than
  868. the total number of bytes in the input stream returns a list of all
  869. lines from the input.
  870. """
  871. bytes = b"one potato\ntwo potato\nthree potato"
  872. d = self._renderAndReturnReaderResult(
  873. lambda input: input.readlines(100), bytes)
  874. d.addCallback(
  875. self.assertEqual,
  876. [b"one potato\n", b"two potato\n", b"three potato"])
  877. return d
  878. def test_readlinesAfterRead(self):
  879. """
  880. Calling L{_InputStream.readlines} after a call to L{_InputStream.read}
  881. returns lines starting at the byte after the last byte returned by the
  882. C{read} call.
  883. """
  884. bytes = b"hello\nworld\nfoo"
  885. def readlines(input):
  886. input.read(7)
  887. return input.readlines()
  888. d = self._renderAndReturnReaderResult(readlines, bytes)
  889. d.addCallback(self.assertEqual, [b"orld\n", b"foo"])
  890. return d
  891. def test_readlinesNone(self):
  892. """
  893. Calling L{_InputStream.readlines} with L{None} as an argument returns
  894. all lines from the input.
  895. """
  896. bytes = b"one fish\ntwo fish\n"
  897. d = self._renderAndReturnReaderResult(
  898. lambda input: input.readlines(None), bytes)
  899. d.addCallback(self.assertEqual, [b"one fish\n", b"two fish\n"])
  900. return d
  901. def test_readlinesNegative(self):
  902. """
  903. Calling L{_InputStream.readlines} with a negative integer as an
  904. argument returns a list of all lines from the input.
  905. """
  906. bytes = b"red fish\nblue fish\n"
  907. d = self._renderAndReturnReaderResult(
  908. lambda input: input.readlines(-1), bytes)
  909. d.addCallback(self.assertEqual, [b"red fish\n", b"blue fish\n"])
  910. return d
  911. def test_iterable(self):
  912. """
  913. Iterating over L{_InputStream} produces lines from the input stream.
  914. """
  915. bytes = b"green eggs\nand ham\n"
  916. d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
  917. d.addCallback(self.assertEqual, [b"green eggs\n", b"and ham\n"])
  918. return d
  919. def test_iterableAfterRead(self):
  920. """
  921. Iterating over L{_InputStream} after calling L{_InputStream.read}
  922. produces lines from the input stream starting from the first byte after
  923. the last byte returned by the C{read} call.
  924. """
  925. bytes = b"green eggs\nand ham\n"
  926. def iterate(input):
  927. input.read(3)
  928. return list(input)
  929. d = self._renderAndReturnReaderResult(iterate, bytes)
  930. d.addCallback(self.assertEqual, [b"en eggs\n", b"and ham\n"])
  931. return d
  932. class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
  933. """
  934. Tests for L{_InputStream} when it is wrapped around a
  935. L{StringIO.StringIO}.
  936. This is only available in Python 2.
  937. """
  938. def getFileType(self):
  939. try:
  940. from StringIO import StringIO
  941. except ImportError:
  942. raise SkipTest("StringIO.StringIO is not available.")
  943. else:
  944. return StringIO
  945. class InputStreamCStringIOTests(InputStreamTestMixin, TestCase):
  946. """
  947. Tests for L{_InputStream} when it is wrapped around a
  948. L{cStringIO.StringIO}.
  949. This is only available in Python 2.
  950. """
  951. def getFileType(self):
  952. try:
  953. from cStringIO import StringIO
  954. except ImportError:
  955. raise SkipTest("cStringIO.StringIO is not available.")
  956. else:
  957. return StringIO
  958. class InputStreamBytesIOTests(InputStreamTestMixin, TestCase):
  959. """
  960. Tests for L{_InputStream} when it is wrapped around an L{io.BytesIO}.
  961. """
  962. def getFileType(self):
  963. from io import BytesIO
  964. return BytesIO
  965. class InputStreamTemporaryFileTests(InputStreamTestMixin, TestCase):
  966. """
  967. Tests for L{_InputStream} when it is wrapped around a L{tempfile.TemporaryFile}.
  968. """
  969. def getFileType(self):
  970. return tempfile.TemporaryFile
  971. class StartResponseTests(WSGITestsMixin, TestCase):
  972. """
  973. Tests for the I{start_response} parameter passed to the application object
  974. by L{WSGIResource}.
  975. """
  976. def test_status(self):
  977. """
  978. The response status passed to the I{start_response} callable is written
  979. as the status of the response to the request.
  980. """
  981. channel = DummyChannel()
  982. def applicationFactory():
  983. def application(environ, startResponse):
  984. startResponse('107 Strange message', [])
  985. return iter(())
  986. return application
  987. d, requestFactory = self.requestFactoryFactory()
  988. def cbRendered(ignored):
  989. self.assertTrue(
  990. channel.transport.written.getvalue().startswith(
  991. b'HTTP/1.1 107 Strange message'))
  992. d.addCallback(cbRendered)
  993. self.lowLevelRender(
  994. requestFactory, applicationFactory,
  995. lambda: channel, 'GET', '1.1', [], [''], None, [])
  996. return d
  997. def test_statusMustBeNativeString(self):
  998. """
  999. The response status passed to the I{start_response} callable MUST be a
  1000. native string in Python 2 and Python 3.
  1001. """
  1002. status = b"200 OK" if _PY3 else u"200 OK"
  1003. def application(environ, startResponse):
  1004. startResponse(status, [])
  1005. return iter(())
  1006. request, result = self.prepareRequest(application)
  1007. request.requestReceived()
  1008. def checkMessage(error):
  1009. if _PY3:
  1010. self.assertEqual(
  1011. "status must be str, not b'200 OK' (bytes)", str(error))
  1012. else:
  1013. self.assertEqual(
  1014. "status must be str, not u'200 OK' (unicode)", str(error))
  1015. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1016. def _headersTest(self, appHeaders, expectedHeaders):
  1017. """
  1018. Verify that if the response headers given by C{appHeaders} are passed
  1019. to the I{start_response} callable, then the response header lines given
  1020. by C{expectedHeaders} plus I{Server} and I{Date} header lines are
  1021. included in the response.
  1022. """
  1023. # Make the Date header value deterministic
  1024. self.patch(http, 'datetimeToString', lambda: 'Tuesday')
  1025. channel = DummyChannel()
  1026. def applicationFactory():
  1027. def application(environ, startResponse):
  1028. startResponse('200 OK', appHeaders)
  1029. return iter(())
  1030. return application
  1031. d, requestFactory = self.requestFactoryFactory()
  1032. def cbRendered(ignored):
  1033. response = channel.transport.written.getvalue()
  1034. headers, rest = response.split(b'\r\n\r\n', 1)
  1035. headerLines = headers.split(b'\r\n')[1:]
  1036. headerLines.sort()
  1037. allExpectedHeaders = expectedHeaders + [
  1038. b'Date: Tuesday',
  1039. b'Server: ' + version,
  1040. b'Transfer-Encoding: chunked']
  1041. allExpectedHeaders.sort()
  1042. self.assertEqual(headerLines, allExpectedHeaders)
  1043. d.addCallback(cbRendered)
  1044. self.lowLevelRender(
  1045. requestFactory, applicationFactory,
  1046. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1047. return d
  1048. def test_headers(self):
  1049. """
  1050. The headers passed to the I{start_response} callable are included in
  1051. the response as are the required I{Date} and I{Server} headers and the
  1052. necessary connection (hop to hop) header I{Transfer-Encoding}.
  1053. """
  1054. return self._headersTest(
  1055. [('foo', 'bar'), ('baz', 'quux')],
  1056. [b'Baz: quux', b'Foo: bar'])
  1057. def test_headersMustBeSequence(self):
  1058. """
  1059. The headers passed to the I{start_response} callable MUST be a
  1060. sequence.
  1061. """
  1062. headers = [("key", "value")]
  1063. def application(environ, startResponse):
  1064. startResponse("200 OK", iter(headers))
  1065. return iter(())
  1066. request, result = self.prepareRequest(application)
  1067. request.requestReceived()
  1068. def checkMessage(error):
  1069. self.assertRegex(
  1070. str(error), "headers must be a list, not "
  1071. r"<(list_?|sequence)iterator .+> [(]\1iterator[)]")
  1072. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1073. def test_headersShouldBePlainList(self):
  1074. """
  1075. According to PEP-3333, the headers passed to the I{start_response}
  1076. callable MUST be a plain list:
  1077. The response_headers argument ... must be a Python list; i.e.
  1078. type(response_headers) is ListType
  1079. However, for bug-compatibility, any sequence is accepted. In both
  1080. Python 2 and Python 3, only a warning is issued when a sequence other
  1081. than a list is encountered.
  1082. """
  1083. def application(environ, startResponse):
  1084. startResponse("200 OK", (("not", "list"),))
  1085. return iter(())
  1086. request, result = self.prepareRequest(application)
  1087. with warnings.catch_warnings(record=True) as caught:
  1088. request.requestReceived()
  1089. result = self.successResultOf(result)
  1090. self.assertEqual(1, len(caught))
  1091. self.assertEqual(RuntimeWarning, caught[0].category)
  1092. self.assertEqual(
  1093. "headers should be a list, not (('not', 'list'),) (tuple)",
  1094. str(caught[0].message))
  1095. def test_headersMustEachBeSequence(self):
  1096. """
  1097. Each header passed to the I{start_response} callable MUST be a
  1098. sequence.
  1099. """
  1100. header = ("key", "value")
  1101. def application(environ, startResponse):
  1102. startResponse("200 OK", [iter(header)])
  1103. return iter(())
  1104. request, result = self.prepareRequest(application)
  1105. request.requestReceived()
  1106. def checkMessage(error):
  1107. self.assertRegex(
  1108. str(error), "header must be a [(]str, str[)] tuple, not "
  1109. r"<(tuple_?|sequence)iterator .+> [(]\1iterator[)]")
  1110. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1111. def test_headersShouldEachBeTuple(self):
  1112. """
  1113. According to PEP-3333, each header passed to the I{start_response}
  1114. callable should be a tuple:
  1115. The response_headers argument is a list of (header_name,
  1116. header_value) tuples
  1117. However, for bug-compatibility, any 2 element sequence is also
  1118. accepted. In both Python 2 and Python 3, only a warning is issued when
  1119. a sequence other than a tuple is encountered.
  1120. """
  1121. def application(environ, startResponse):
  1122. startResponse("200 OK", [["not", "tuple"]])
  1123. return iter(())
  1124. request, result = self.prepareRequest(application)
  1125. with warnings.catch_warnings(record=True) as caught:
  1126. request.requestReceived()
  1127. result = self.successResultOf(result)
  1128. self.assertEqual(1, len(caught))
  1129. self.assertEqual(RuntimeWarning, caught[0].category)
  1130. self.assertEqual(
  1131. "header should be a (str, str) tuple, not ['not', 'tuple'] (list)",
  1132. str(caught[0].message))
  1133. def test_headersShouldEachHaveKeyAndValue(self):
  1134. """
  1135. Each header passed to the I{start_response} callable MUST hold a key
  1136. and a value, and ONLY a key and a value.
  1137. """
  1138. def application(environ, startResponse):
  1139. startResponse("200 OK", [("too", "many", "cooks")])
  1140. return iter(())
  1141. request, result = self.prepareRequest(application)
  1142. request.requestReceived()
  1143. def checkMessage(error):
  1144. self.assertEqual(
  1145. "header must be a (str, str) tuple, not "
  1146. "('too', 'many', 'cooks')", str(error))
  1147. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1148. def test_headerKeyMustBeNativeString(self):
  1149. """
  1150. Each header key passed to the I{start_response} callable MUST be at
  1151. native string in Python 2 and Python 3.
  1152. """
  1153. key = b"key" if _PY3 else u"key"
  1154. def application(environ, startResponse):
  1155. startResponse("200 OK", [(key, "value")])
  1156. return iter(())
  1157. request, result = self.prepareRequest(application)
  1158. request.requestReceived()
  1159. def checkMessage(error):
  1160. self.assertEqual(
  1161. "header must be (str, str) tuple, not (%r, 'value')" % (key,),
  1162. str(error))
  1163. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1164. def test_headerValueMustBeNativeString(self):
  1165. """
  1166. Each header value passed to the I{start_response} callable MUST be at
  1167. native string in Python 2 and Python 3.
  1168. """
  1169. value = b"value" if _PY3 else u"value"
  1170. def application(environ, startResponse):
  1171. startResponse("200 OK", [("key", value)])
  1172. return iter(())
  1173. request, result = self.prepareRequest(application)
  1174. request.requestReceived()
  1175. def checkMessage(error):
  1176. self.assertEqual(
  1177. "header must be (str, str) tuple, not ('key', %r)" % (value,),
  1178. str(error))
  1179. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1180. def test_applicationProvidedContentType(self):
  1181. """
  1182. If I{Content-Type} is included in the headers passed to the
  1183. I{start_response} callable, one I{Content-Type} header is included in
  1184. the response.
  1185. """
  1186. return self._headersTest(
  1187. [('content-type', 'monkeys are great')],
  1188. [b'Content-Type: monkeys are great'])
  1189. def test_applicationProvidedServerAndDate(self):
  1190. """
  1191. If either I{Server} or I{Date} is included in the headers passed to the
  1192. I{start_response} callable, they are disregarded.
  1193. """
  1194. return self._headersTest(
  1195. [('server', 'foo'), ('Server', 'foo'),
  1196. ('date', 'bar'), ('dATE', 'bar')],
  1197. [])
  1198. def test_delayedUntilReturn(self):
  1199. """
  1200. Nothing is written in response to a request when the I{start_response}
  1201. callable is invoked. If the iterator returned by the application
  1202. object produces only empty strings, the response is written after the
  1203. last element is produced.
  1204. """
  1205. channel = DummyChannel()
  1206. intermediateValues = []
  1207. def record():
  1208. intermediateValues.append(channel.transport.written.getvalue())
  1209. def applicationFactory():
  1210. def application(environ, startResponse):
  1211. startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
  1212. yield b''
  1213. record()
  1214. return application
  1215. d, requestFactory = self.requestFactoryFactory()
  1216. def cbRendered(ignored):
  1217. self.assertEqual(intermediateValues, [b''])
  1218. d.addCallback(cbRendered)
  1219. self.lowLevelRender(
  1220. requestFactory, applicationFactory,
  1221. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1222. return d
  1223. def test_delayedUntilContent(self):
  1224. """
  1225. Nothing is written in response to a request when the I{start_response}
  1226. callable is invoked. Once a non-empty string has been produced by the
  1227. iterator returned by the application object, the response status and
  1228. headers are written.
  1229. """
  1230. channel = DummyChannel()
  1231. intermediateValues = []
  1232. def record():
  1233. intermediateValues.append(channel.transport.written.getvalue())
  1234. def applicationFactory():
  1235. def application(environ, startResponse):
  1236. startResponse('200 OK', [('foo', 'bar')])
  1237. yield b''
  1238. record()
  1239. yield b'foo'
  1240. record()
  1241. return application
  1242. d, requestFactory = self.requestFactoryFactory()
  1243. def cbRendered(ignored):
  1244. self.assertFalse(intermediateValues[0])
  1245. self.assertTrue(intermediateValues[1])
  1246. d.addCallback(cbRendered)
  1247. self.lowLevelRender(
  1248. requestFactory, applicationFactory,
  1249. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1250. return d
  1251. def test_content(self):
  1252. """
  1253. Content produced by the iterator returned by the application object is
  1254. written to the request as it is produced.
  1255. """
  1256. channel = DummyChannel()
  1257. intermediateValues = []
  1258. def record():
  1259. intermediateValues.append(channel.transport.written.getvalue())
  1260. def applicationFactory():
  1261. def application(environ, startResponse):
  1262. startResponse('200 OK', [('content-length', '6')])
  1263. yield b'foo'
  1264. record()
  1265. yield b'bar'
  1266. record()
  1267. return application
  1268. d, requestFactory = self.requestFactoryFactory()
  1269. def cbRendered(ignored):
  1270. self.assertEqual(
  1271. self.getContentFromResponse(intermediateValues[0]),
  1272. b'foo')
  1273. self.assertEqual(
  1274. self.getContentFromResponse(intermediateValues[1]),
  1275. b'foobar')
  1276. d.addCallback(cbRendered)
  1277. self.lowLevelRender(
  1278. requestFactory, applicationFactory,
  1279. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1280. return d
  1281. def test_multipleStartResponse(self):
  1282. """
  1283. If the I{start_response} callable is invoked multiple times before a
  1284. data for the response body is produced, the values from the last call
  1285. are used.
  1286. """
  1287. channel = DummyChannel()
  1288. def applicationFactory():
  1289. def application(environ, startResponse):
  1290. startResponse('100 Foo', [])
  1291. startResponse('200 Bar', [])
  1292. return iter(())
  1293. return application
  1294. d, requestFactory = self.requestFactoryFactory()
  1295. def cbRendered(ignored):
  1296. self.assertTrue(
  1297. channel.transport.written.getvalue().startswith(
  1298. b'HTTP/1.1 200 Bar\r\n'))
  1299. d.addCallback(cbRendered)
  1300. self.lowLevelRender(
  1301. requestFactory, applicationFactory,
  1302. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1303. return d
  1304. def test_startResponseWithException(self):
  1305. """
  1306. If the I{start_response} callable is invoked with a third positional
  1307. argument before the status and headers have been written to the
  1308. response, the status and headers become the newly supplied values.
  1309. """
  1310. channel = DummyChannel()
  1311. def applicationFactory():
  1312. def application(environ, startResponse):
  1313. startResponse('100 Foo', [], (Exception, Exception("foo"), None))
  1314. return iter(())
  1315. return application
  1316. d, requestFactory = self.requestFactoryFactory()
  1317. def cbRendered(ignored):
  1318. self.assertTrue(
  1319. channel.transport.written.getvalue().startswith(
  1320. b'HTTP/1.1 100 Foo\r\n'))
  1321. d.addCallback(cbRendered)
  1322. self.lowLevelRender(
  1323. requestFactory, applicationFactory,
  1324. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1325. return d
  1326. def test_startResponseWithExceptionTooLate(self):
  1327. """
  1328. If the I{start_response} callable is invoked with a third positional
  1329. argument after the status and headers have been written to the
  1330. response, the supplied I{exc_info} values are re-raised to the
  1331. application.
  1332. """
  1333. channel = DummyChannel()
  1334. class SomeException(Exception):
  1335. pass
  1336. try:
  1337. raise SomeException()
  1338. except:
  1339. excInfo = exc_info()
  1340. reraised = []
  1341. def applicationFactory():
  1342. def application(environ, startResponse):
  1343. startResponse('200 OK', [])
  1344. yield b'foo'
  1345. try:
  1346. startResponse('500 ERR', [], excInfo)
  1347. except:
  1348. reraised.append(exc_info())
  1349. return application
  1350. d, requestFactory = self.requestFactoryFactory()
  1351. def cbRendered(ignored):
  1352. self.assertTrue(
  1353. channel.transport.written.getvalue().startswith(
  1354. b'HTTP/1.1 200 OK\r\n'))
  1355. self.assertEqual(reraised[0][0], excInfo[0])
  1356. self.assertEqual(reraised[0][1], excInfo[1])
  1357. # Show that the tracebacks end with the same stack frames.
  1358. tb1 = reraised[0][2].tb_next
  1359. tb2 = excInfo[2]
  1360. self.assertEqual(
  1361. # On Python 2 (str is bytes) we need to move back only one
  1362. # stack frame to skip. On Python 3 we need to move two frames.
  1363. traceback.extract_tb(tb1)[1 if str is bytes else 2],
  1364. traceback.extract_tb(tb2)[0]
  1365. )
  1366. d.addCallback(cbRendered)
  1367. self.lowLevelRender(
  1368. requestFactory, applicationFactory,
  1369. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1370. return d
  1371. def test_write(self):
  1372. """
  1373. I{start_response} returns the I{write} callable which can be used to
  1374. write bytes to the response body without buffering.
  1375. """
  1376. channel = DummyChannel()
  1377. intermediateValues = []
  1378. def record():
  1379. intermediateValues.append(channel.transport.written.getvalue())
  1380. def applicationFactory():
  1381. def application(environ, startResponse):
  1382. write = startResponse('100 Foo', [('content-length', '6')])
  1383. write(b'foo')
  1384. record()
  1385. write(b'bar')
  1386. record()
  1387. return iter(())
  1388. return application
  1389. d, requestFactory = self.requestFactoryFactory()
  1390. def cbRendered(ignored):
  1391. self.assertEqual(
  1392. self.getContentFromResponse(intermediateValues[0]),
  1393. b'foo')
  1394. self.assertEqual(
  1395. self.getContentFromResponse(intermediateValues[1]),
  1396. b'foobar')
  1397. d.addCallback(cbRendered)
  1398. self.lowLevelRender(
  1399. requestFactory, applicationFactory,
  1400. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1401. return d
  1402. def test_writeAcceptsOnlyByteStrings(self):
  1403. """
  1404. The C{write} callable returned from C{start_response} only accepts
  1405. byte strings.
  1406. """
  1407. def application(environ, startResponse):
  1408. write = startResponse("200 OK", [])
  1409. write(u"bogus")
  1410. return iter(())
  1411. request, result = self.prepareRequest(application)
  1412. request.requestReceived()
  1413. def checkMessage(error):
  1414. if _PY3:
  1415. self.assertEqual(
  1416. "Can only write bytes to a transport, not 'bogus'",
  1417. str(error))
  1418. else:
  1419. self.assertEqual(
  1420. "Can only write bytes to a transport, not u'bogus'",
  1421. str(error))
  1422. return self.assertFailure(result, TypeError).addCallback(checkMessage)
  1423. class ApplicationTests(WSGITestsMixin, TestCase):
  1424. """
  1425. Tests for things which are done to the application object and the iterator
  1426. it returns.
  1427. """
  1428. def enableThreads(self):
  1429. self.reactor = reactor
  1430. self.threadpool = ThreadPool()
  1431. self.threadpool.start()
  1432. self.addCleanup(self.threadpool.stop)
  1433. def test_close(self):
  1434. """
  1435. If the application object returns an iterator which also has a I{close}
  1436. method, that method is called after iteration is complete.
  1437. """
  1438. channel = DummyChannel()
  1439. class Result:
  1440. def __init__(self):
  1441. self.open = True
  1442. def __iter__(self):
  1443. for i in range(3):
  1444. if self.open:
  1445. yield intToBytes(i)
  1446. def close(self):
  1447. self.open = False
  1448. result = Result()
  1449. def applicationFactory():
  1450. def application(environ, startResponse):
  1451. startResponse('200 OK', [('content-length', '3')])
  1452. return result
  1453. return application
  1454. d, requestFactory = self.requestFactoryFactory()
  1455. def cbRendered(ignored):
  1456. self.assertEqual(
  1457. self.getContentFromResponse(
  1458. channel.transport.written.getvalue()),
  1459. b'012')
  1460. self.assertFalse(result.open)
  1461. d.addCallback(cbRendered)
  1462. self.lowLevelRender(
  1463. requestFactory, applicationFactory,
  1464. lambda: channel, 'GET', '1.1', [], [''])
  1465. return d
  1466. def test_applicationCalledInThread(self):
  1467. """
  1468. The application object is invoked and iterated in a thread which is not
  1469. the reactor thread.
  1470. """
  1471. self.enableThreads()
  1472. invoked = []
  1473. def applicationFactory():
  1474. def application(environ, startResponse):
  1475. def result():
  1476. for i in range(3):
  1477. invoked.append(getThreadID())
  1478. yield intToBytes(i)
  1479. invoked.append(getThreadID())
  1480. startResponse('200 OK', [('content-length', '3')])
  1481. return result()
  1482. return application
  1483. d, requestFactory = self.requestFactoryFactory()
  1484. def cbRendered(ignored):
  1485. self.assertNotIn(getThreadID(), invoked)
  1486. self.assertEqual(len(set(invoked)), 1)
  1487. d.addCallback(cbRendered)
  1488. self.lowLevelRender(
  1489. requestFactory, applicationFactory,
  1490. DummyChannel, 'GET', '1.1', [], [''])
  1491. return d
  1492. def test_writeCalledFromThread(self):
  1493. """
  1494. The I{write} callable returned by I{start_response} calls the request's
  1495. C{write} method in the reactor thread.
  1496. """
  1497. self.enableThreads()
  1498. invoked = []
  1499. class ThreadVerifier(Request):
  1500. def write(self, bytes):
  1501. invoked.append(getThreadID())
  1502. return Request.write(self, bytes)
  1503. def applicationFactory():
  1504. def application(environ, startResponse):
  1505. write = startResponse('200 OK', [])
  1506. write(b'foo')
  1507. return iter(())
  1508. return application
  1509. d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
  1510. def cbRendered(ignored):
  1511. self.assertEqual(set(invoked), set([getThreadID()]))
  1512. d.addCallback(cbRendered)
  1513. self.lowLevelRender(
  1514. requestFactory, applicationFactory, DummyChannel,
  1515. 'GET', '1.1', [], [''])
  1516. return d
  1517. def test_iteratedValuesWrittenFromThread(self):
  1518. """
  1519. Strings produced by the iterator returned by the application object are
  1520. written to the request in the reactor thread.
  1521. """
  1522. self.enableThreads()
  1523. invoked = []
  1524. class ThreadVerifier(Request):
  1525. def write(self, bytes):
  1526. invoked.append(getThreadID())
  1527. return Request.write(self, bytes)
  1528. def applicationFactory():
  1529. def application(environ, startResponse):
  1530. startResponse('200 OK', [])
  1531. yield b'foo'
  1532. return application
  1533. d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
  1534. def cbRendered(ignored):
  1535. self.assertEqual(set(invoked), set([getThreadID()]))
  1536. d.addCallback(cbRendered)
  1537. self.lowLevelRender(
  1538. requestFactory, applicationFactory, DummyChannel,
  1539. 'GET', '1.1', [], [''])
  1540. return d
  1541. def test_statusWrittenFromThread(self):
  1542. """
  1543. The response status is set on the request object in the reactor thread.
  1544. """
  1545. self.enableThreads()
  1546. invoked = []
  1547. class ThreadVerifier(Request):
  1548. def setResponseCode(self, code, message):
  1549. invoked.append(getThreadID())
  1550. return Request.setResponseCode(self, code, message)
  1551. def applicationFactory():
  1552. def application(environ, startResponse):
  1553. startResponse('200 OK', [])
  1554. return iter(())
  1555. return application
  1556. d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
  1557. def cbRendered(ignored):
  1558. self.assertEqual(set(invoked), set([getThreadID()]))
  1559. d.addCallback(cbRendered)
  1560. self.lowLevelRender(
  1561. requestFactory, applicationFactory, DummyChannel,
  1562. 'GET', '1.1', [], [''])
  1563. return d
  1564. def test_connectionClosedDuringIteration(self):
  1565. """
  1566. If the request connection is lost while the application object is being
  1567. iterated, iteration is stopped.
  1568. """
  1569. class UnreliableConnection(Request):
  1570. """
  1571. This is a request which pretends its connection is lost immediately
  1572. after the first write is done to it.
  1573. """
  1574. def write(self, bytes):
  1575. self.connectionLost(Failure(ConnectionLost("No more connection")))
  1576. self.badIter = False
  1577. def appIter():
  1578. yield b"foo"
  1579. self.badIter = True
  1580. raise Exception("Should not have gotten here")
  1581. def applicationFactory():
  1582. def application(environ, startResponse):
  1583. startResponse('200 OK', [])
  1584. return appIter()
  1585. return application
  1586. d, requestFactory = self.requestFactoryFactory(UnreliableConnection)
  1587. def cbRendered(ignored):
  1588. self.assertFalse(self.badIter, "Should not have resumed iteration")
  1589. d.addCallback(cbRendered)
  1590. self.lowLevelRender(
  1591. requestFactory, applicationFactory, DummyChannel,
  1592. 'GET', '1.1', [], [''])
  1593. return self.assertFailure(d, ConnectionLost)
  1594. def _internalServerErrorTest(self, application):
  1595. channel = DummyChannel()
  1596. def applicationFactory():
  1597. return application
  1598. d, requestFactory = self.requestFactoryFactory()
  1599. def cbRendered(ignored):
  1600. errors = self.flushLoggedErrors(RuntimeError)
  1601. self.assertEqual(len(errors), 1)
  1602. self.assertTrue(
  1603. channel.transport.written.getvalue().startswith(
  1604. b'HTTP/1.1 500 Internal Server Error'))
  1605. d.addCallback(cbRendered)
  1606. self.lowLevelRender(
  1607. requestFactory, applicationFactory,
  1608. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1609. return d
  1610. def test_applicationExceptionBeforeStartResponse(self):
  1611. """
  1612. If the application raises an exception before calling I{start_response}
  1613. then the response status is I{500} and the exception is logged.
  1614. """
  1615. def application(environ, startResponse):
  1616. raise RuntimeError("This application had some error.")
  1617. return self._internalServerErrorTest(application)
  1618. def test_applicationExceptionAfterStartResponse(self):
  1619. """
  1620. If the application calls I{start_response} but then raises an exception
  1621. before any data is written to the response then the response status is
  1622. I{500} and the exception is logged.
  1623. """
  1624. def application(environ, startResponse):
  1625. startResponse('200 OK', [])
  1626. raise RuntimeError("This application had some error.")
  1627. return self._internalServerErrorTest(application)
  1628. def _connectionClosedTest(self, application, responseContent):
  1629. channel = DummyChannel()
  1630. def applicationFactory():
  1631. return application
  1632. d, requestFactory = self.requestFactoryFactory()
  1633. # Capture the request so we can disconnect it later on.
  1634. requests = []
  1635. def requestFactoryWrapper(*a, **kw):
  1636. requests.append(requestFactory(*a, **kw))
  1637. return requests[-1]
  1638. def ebRendered(ignored):
  1639. errors = self.flushLoggedErrors(RuntimeError)
  1640. self.assertEqual(len(errors), 1)
  1641. response = channel.transport.written.getvalue()
  1642. self.assertTrue(response.startswith(b'HTTP/1.1 200 OK'))
  1643. # Chunked transfer-encoding makes this a little messy.
  1644. self.assertIn(responseContent, response)
  1645. d.addErrback(ebRendered)
  1646. self.lowLevelRender(
  1647. requestFactoryWrapper, applicationFactory,
  1648. lambda: channel, 'GET', '1.1', [], [''], None, [])
  1649. # By now the connection should be closed.
  1650. self.assertTrue(channel.transport.disconnected)
  1651. # Give it a little push to go the rest of the way.
  1652. requests[0].connectionLost(Failure(ConnectionLost("All gone")))
  1653. return d
  1654. def test_applicationExceptionAfterWrite(self):
  1655. """
  1656. If the application raises an exception after the response status has
  1657. already been sent then the connection is closed and the exception is
  1658. logged.
  1659. """
  1660. responseContent = (
  1661. b'Some bytes, triggering the server to start sending the response')
  1662. def application(environ, startResponse):
  1663. startResponse('200 OK', [])
  1664. yield responseContent
  1665. raise RuntimeError("This application had some error.")
  1666. return self._connectionClosedTest(application, responseContent)
  1667. def test_applicationCloseException(self):
  1668. """
  1669. If the application returns a closeable iterator and the C{close} method
  1670. raises an exception when called then the connection is still closed and
  1671. the exception is logged.
  1672. """
  1673. responseContent = b'foo'
  1674. class Application(object):
  1675. def __init__(self, environ, startResponse):
  1676. startResponse('200 OK', [])
  1677. def __iter__(self):
  1678. yield responseContent
  1679. def close(self):
  1680. raise RuntimeError("This application had some error.")
  1681. return self._connectionClosedTest(Application, responseContent)