1
# Copyright (c) 2008-2009 Twisted Matrix Laboratories.
2
# See LICENSE for details.
5
Tests for L{twisted.web.wsgi}.
10
from sys import exc_info
11
from urllib import quote
12
from thread import get_ident
13
import StringIO, cStringIO, tempfile
15
from zope.interface.verify import verifyObject
17
from twisted.python.compat import set
18
from twisted.python.log import addObserver, removeObserver, err
19
from twisted.python.failure import Failure
20
from twisted.python.threadpool import ThreadPool
21
from twisted.internet.defer import Deferred, gatherResults
22
from twisted.internet import reactor
23
from twisted.internet.error import ConnectionLost
24
from twisted.trial.unittest import TestCase
25
from twisted.web import http
26
from twisted.web.resource import IResource, Resource
27
from twisted.web.server import Request, Site, version
28
from twisted.web.wsgi import WSGIResource
29
from twisted.web.test.test_web import DummyChannel
32
class SynchronousThreadPool:
34
A single-threaded implementation of part of the L{ThreadPool} interface.
35
This implementation calls functions synchronously rather than running
36
them in a thread pool. It is used to make the tests which are not
37
directly for thread-related behavior deterministic.
39
def callInThread(self, f, *a, **kw):
41
Call C{f(*a, **kw)} in this thread rather than scheduling it to be
47
# callInThread doesn't let exceptions propagate to the caller.
48
# None is always returned and any exception raised gets logged
50
err(None, "Callable passed to SynchronousThreadPool.callInThread failed")
54
class SynchronousReactorThreads:
56
A single-threaded implementation of part of the L{IReactorThreads}
57
interface. This implementation assumes that it will only be invoked
58
from the reactor thread, so it calls functions synchronously rather than
59
trying to schedule them to run in the reactor thread. It is used in
60
conjunction with L{SynchronousThreadPool} to make the tests which are
61
not directly for thread-related behavior deterministic.
63
def callFromThread(self, f, *a, **kw):
65
Call C{f(*a, **kw)} in this thread which should also be the reactor
72
class WSGIResourceTests(TestCase):
75
Create a L{WSGIResource} with synchronous threading objects and a no-op
76
application object. This is useful for testing certain things about
77
the resource implementation which are unrelated to WSGI.
79
self.resource = WSGIResource(
80
SynchronousReactorThreads(), SynchronousThreadPool(),
81
lambda environ, startResponse: None)
84
def test_interfaces(self):
86
L{WSGIResource} implements L{IResource} and stops resource traversal.
88
verifyObject(IResource, self.resource)
89
self.assertTrue(self.resource.isLeaf)
92
def test_unsupported(self):
94
A L{WSGIResource} cannot have L{IResource} children. Its
95
C{getChildWithDefault} and C{putChild} methods raise L{RuntimeError}.
99
self.resource.getChildWithDefault,
100
"foo", Request(DummyChannel(), False))
103
self.resource.putChild,
107
class WSGITestsMixin:
109
@ivar channelFactory: A no-argument callable which will be invoked to
110
create a new HTTP channel to associate with request objects.
112
channelFactory = DummyChannel
115
self.threadpool = SynchronousThreadPool()
116
self.reactor = SynchronousReactorThreads()
120
self, requestFactory, applicationFactory, channelFactory, method,
121
version, resourceSegments, requestSegments, query=None, headers=[],
124
@param method: A C{str} giving the request method to use.
126
@param version: A C{str} like C{'1.1'} giving the request version.
128
@param resourceSegments: A C{list} of unencoded path segments which
129
specifies the location in the resource hierarchy at which the
130
L{WSGIResource} will be placed, eg C{['']} for I{/}, C{['foo',
131
'bar', '']} for I{/foo/bar/}, etc.
133
@param requestSegments: A C{list} of unencoded path segments giving the
136
@param query: A C{list} of two-tuples of C{str} giving unencoded query
137
argument keys and values.
139
@param headers: A C{list} of two-tuples of C{str} giving request header
140
names and corresponding values.
142
@param safe: A C{str} giving the bytes which are to be considered
143
I{safe} for inclusion in the request URI and not quoted.
145
@return: A L{Deferred} which will be called back with a two-tuple of
146
the arguments passed which would be passed to the WSGI application
147
object for this configuration and request (ie, the environment and
148
start_response callable).
151
self.reactor, self.threadpool, applicationFactory())
152
resourceSegments.reverse()
153
for seg in resourceSegments:
155
tmp.putChild(seg, root)
158
channel = channelFactory()
159
channel.site = Site(root)
160
request = requestFactory(channel, False)
162
request.requestHeaders.addRawHeader(k, v)
165
request.content.write(body)
166
request.content.seek(0)
167
uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
168
if query is not None:
169
uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
170
for (k, v) in query])
171
request.requestReceived(method, uri, 'HTTP/' + version)
175
def render(self, *a, **kw):
177
def applicationFactory():
178
def application(*args):
179
environ, startResponse = args
180
result.callback(args)
181
startResponse('200 OK', [])
185
Request, applicationFactory, self.channelFactory, *a, **kw)
189
def requestFactoryFactory(self, requestClass=Request):
191
def requestFactory(*a, **kw):
192
request = requestClass(*a, **kw)
193
# If notifyFinish is called after lowLevelRender returns, it won't
194
# do the right thing, because the request will have already
195
# finished. One might argue that this is a bug in
196
# Request.notifyFinish.
197
request.notifyFinish().chainDeferred(d)
199
return d, requestFactory
202
def getContentFromResponse(self, response):
203
return response.split('\r\n\r\n', 1)[1]
207
class EnvironTests(WSGITestsMixin, TestCase):
209
Tests for the values in the C{environ} C{dict} passed to the application
210
object by L{twisted.web.wsgi.WSGIResource}.
212
def environKeyEqual(self, key, value):
213
def assertEnvironKeyEqual((environ, startResponse)):
214
self.assertEqual(environ[key], value)
215
return assertEnvironKeyEqual
218
def test_environIsDict(self):
220
L{WSGIResource} calls the application object with an C{environ}
221
parameter which is exactly of type C{dict}.
223
d = self.render('GET', '1.1', [], [''])
224
def cbRendered((environ, startResponse)):
225
self.assertIdentical(type(environ), dict)
226
d.addCallback(cbRendered)
230
def test_requestMethod(self):
232
The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
233
application contains the HTTP method in the request (RFC 3875, section
236
get = self.render('GET', '1.1', [], [''])
237
get.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
239
# Also make sure a different request method shows up as a different
240
# value in the environ dict.
241
post = self.render('POST', '1.1', [], [''])
242
post.addCallback(self.environKeyEqual('REQUEST_METHOD', 'POST'))
244
return gatherResults([get, post])
247
def test_scriptName(self):
249
The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
250
application contains the I{abs_path} (RFC 2396, section 3) to this
251
resource (RFC 3875, section 4.1.13).
253
root = self.render('GET', '1.1', [], [''])
254
root.addCallback(self.environKeyEqual('SCRIPT_NAME', ''))
256
emptyChild = self.render('GET', '1.1', [''], [''])
257
emptyChild.addCallback(self.environKeyEqual('SCRIPT_NAME', '/'))
259
leaf = self.render('GET', '1.1', ['foo'], ['foo'])
260
leaf.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
262
container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
263
container.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo/'))
265
internal = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
266
internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
268
unencoded = self.render(
269
'GET', '1.1', ['foo', '/', 'bar\xff'], ['foo', '/', 'bar\xff'])
270
# The RFC says "(not URL-encoded)", even though that makes
271
# interpretation of SCRIPT_NAME ambiguous.
272
unencoded.addCallback(
273
self.environKeyEqual('SCRIPT_NAME', '/foo///bar\xff'))
275
return gatherResults([
276
root, emptyChild, leaf, container, internal, unencoded])
279
def test_pathInfo(self):
281
The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
282
application contains the suffix of the request URI path which is not
283
included in the value for the C{'SCRIPT_NAME'} key (RFC 3875, section
286
assertKeyEmpty = self.environKeyEqual('PATH_INFO', '')
288
root = self.render('GET', '1.1', [], [''])
289
root.addCallback(self.environKeyEqual('PATH_INFO', '/'))
291
emptyChild = self.render('GET', '1.1', [''], [''])
292
emptyChild.addCallback(assertKeyEmpty)
294
leaf = self.render('GET', '1.1', ['foo'], ['foo'])
295
leaf.addCallback(assertKeyEmpty)
297
container = self.render('GET', '1.1', ['foo', ''], ['foo', ''])
298
container.addCallback(assertKeyEmpty)
300
internalLeaf = self.render('GET', '1.1', ['foo'], ['foo', 'bar'])
301
internalLeaf.addCallback(self.environKeyEqual('PATH_INFO', '/bar'))
303
internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
304
internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
306
unencoded = self.render('GET', '1.1', [], ['foo', '/', 'bar\xff'])
307
unencoded.addCallback(
308
self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
310
return gatherResults([
311
root, leaf, container, internalLeaf,
312
internalContainer, unencoded])
315
def test_queryString(self):
317
The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
318
application contains the portion of the request URI after the first
319
I{?} (RFC 3875, section 4.1.7).
321
missing = self.render('GET', '1.1', [], [''], None)
322
missing.addCallback(self.environKeyEqual('QUERY_STRING', ''))
324
empty = self.render('GET', '1.1', [], [''], [])
325
empty.addCallback(self.environKeyEqual('QUERY_STRING', ''))
327
present = self.render('GET', '1.1', [], [''], [('foo', 'bar')])
328
present.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
330
unencoded = self.render('GET', '1.1', [], [''], [('/', '/')])
331
unencoded.addCallback(self.environKeyEqual('QUERY_STRING', '%2F=%2F'))
333
# "?" is reserved in the <searchpart> portion of a URL. However, it
334
# seems to be a common mistake of clients to forget to quote it. So,
335
# make sure we handle that invalid case.
336
doubleQuestion = self.render(
337
'GET', '1.1', [], [''], [('foo', '?bar')], safe='?')
338
doubleQuestion.addCallback(
339
self.environKeyEqual('QUERY_STRING', 'foo=?bar'))
341
return gatherResults([
342
missing, empty, present, unencoded, doubleQuestion])
345
def test_contentType(self):
347
The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
348
application contains the value of the I{Content-Type} request header
349
(RFC 3875, section 4.1.3).
351
missing = self.render('GET', '1.1', [], [''])
352
missing.addCallback(self.environKeyEqual('CONTENT_TYPE', ''))
354
present = self.render(
355
'GET', '1.1', [], [''], None, [('content-type', 'x-foo/bar')])
356
present.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
358
return gatherResults([missing, present])
361
def test_contentLength(self):
363
The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
364
application contains the value of the I{Content-Length} request header
365
(RFC 3875, section 4.1.2).
367
missing = self.render('GET', '1.1', [], [''])
368
missing.addCallback(self.environKeyEqual('CONTENT_LENGTH', ''))
370
present = self.render(
371
'GET', '1.1', [], [''], None, [('content-length', '1234')])
372
present.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
374
return gatherResults([missing, present])
377
def test_serverName(self):
379
The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
380
application contains the best determination of the server hostname
381
possible, using either the value of the I{Host} header in the request
382
or the address the server is listening on if that header is not
383
present (RFC 3875, section 4.1.14).
385
missing = self.render('GET', '1.1', [], [''])
386
# 10.0.0.1 value comes from a bit far away -
387
# twisted.test.test_web.DummyChannel.transport.getHost().host
388
missing.addCallback(self.environKeyEqual('SERVER_NAME', '10.0.0.1'))
390
present = self.render(
391
'GET', '1.1', [], [''], None, [('host', 'example.org')])
392
present.addCallback(self.environKeyEqual('SERVER_NAME', 'example.org'))
394
return gatherResults([missing, present])
397
def test_serverPort(self):
399
The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
400
application contains the port number of the server which received the
401
request (RFC 3875, section 4.1.15).
405
channel = DummyChannel()
406
channel.transport = DummyChannel.TCP()
407
channel.transport.port = portNumber
409
self.channelFactory = makeChannel
411
d = self.render('GET', '1.1', [], [''])
412
d.addCallback(self.environKeyEqual('SERVER_PORT', str(portNumber)))
416
def test_serverProtocol(self):
418
The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
419
application contains the HTTP version number received in the request
420
(RFC 3875, section 4.1.16).
422
old = self.render('GET', '1.0', [], [''])
423
old.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.0'))
425
new = self.render('GET', '1.1', [], [''])
426
new.addCallback(self.environKeyEqual('SERVER_PROTOCOL', 'HTTP/1.1'))
428
return gatherResults([old, new])
431
def test_remoteAddr(self):
433
The C{'REMOTE_ADDR'} key of the C{environ} C{dict} passed to the
434
application contains the address of the client making the request.
436
d = self.render('GET', '1.1', [], [''])
437
d.addCallback(self.environKeyEqual('REMOTE_ADDR', '192.168.1.1'))
441
def test_headers(self):
443
HTTP request headers are copied into the C{environ} C{dict} passed to
444
the application with a C{HTTP_} prefix added to their names.
446
singleValue = self.render(
447
'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
448
def cbRendered((environ, startResponse)):
449
self.assertEqual(environ['HTTP_FOO'], 'bar')
450
self.assertEqual(environ['HTTP_BAZ'], 'quux')
451
singleValue.addCallback(cbRendered)
453
multiValue = self.render(
454
'GET', '1.1', [], [''], None, [('foo', 'bar'), ('foo', 'baz')])
455
multiValue.addCallback(self.environKeyEqual('HTTP_FOO', 'bar,baz'))
457
withHyphen = self.render(
458
'GET', '1.1', [], [''], None, [('foo-bar', 'baz')])
459
withHyphen.addCallback(self.environKeyEqual('HTTP_FOO_BAR', 'baz'))
461
multiLine = self.render(
462
'GET', '1.1', [], [''], None, [('foo', 'bar\n\tbaz')])
463
multiLine.addCallback(self.environKeyEqual('HTTP_FOO', 'bar \tbaz'))
465
return gatherResults([singleValue, multiValue, withHyphen, multiLine])
468
def test_wsgiVersion(self):
470
The C{'wsgi.version'} key of the C{environ} C{dict} passed to the
471
application has the value C{(1, 0)} indicating that this is a WSGI 1.0
474
versionDeferred = self.render('GET', '1.1', [], [''])
475
versionDeferred.addCallback(self.environKeyEqual('wsgi.version', (1, 0)))
476
return versionDeferred
479
def test_wsgiRunOnce(self):
481
The C{'wsgi.run_once'} key of the C{environ} C{dict} passed to the
482
application is set to C{False}.
484
once = self.render('GET', '1.1', [], [''])
485
once.addCallback(self.environKeyEqual('wsgi.run_once', False))
489
def test_wsgiMultithread(self):
491
The C{'wsgi.multithread'} key of the C{environ} C{dict} passed to the
492
application is set to C{True}.
494
thread = self.render('GET', '1.1', [], [''])
495
thread.addCallback(self.environKeyEqual('wsgi.multithread', True))
499
def test_wsgiMultiprocess(self):
501
The C{'wsgi.multiprocess'} key of the C{environ} C{dict} passed to the
502
application is set to C{False}.
504
process = self.render('GET', '1.1', [], [''])
505
process.addCallback(self.environKeyEqual('wsgi.multiprocess', False))
509
def test_wsgiURLScheme(self):
511
The C{'wsgi.url_scheme'} key of the C{environ} C{dict} passed to the
512
application has the request URL scheme.
514
# XXX Does this need to be different if the request is for an absolute
516
def channelFactory():
517
channel = DummyChannel()
518
channel.transport = DummyChannel.SSL()
521
self.channelFactory = DummyChannel
522
httpDeferred = self.render('GET', '1.1', [], [''])
523
httpDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'http'))
525
self.channelFactory = channelFactory
526
httpsDeferred = self.render('GET', '1.1', [], [''])
527
httpsDeferred.addCallback(self.environKeyEqual('wsgi.url_scheme', 'https'))
529
return gatherResults([httpDeferred, httpsDeferred])
532
def test_wsgiErrors(self):
534
The C{'wsgi.errors'} key of the C{environ} C{dict} passed to the
535
application is a file-like object (as defined in the U{Input and Errors
536
Streams<http://www.python.org/dev/peps/pep-0333/#input-and-error-streams>}
537
section of PEP 333) which converts bytes written to it into events for
541
addObserver(events.append)
542
self.addCleanup(removeObserver, events.append)
544
errors = self.render('GET', '1.1', [], [''])
545
def cbErrors((environ, startApplication)):
546
errors = environ['wsgi.errors']
547
errors.write('some message\n')
548
errors.writelines(['another\nmessage\n'])
550
self.assertEqual(events[0]['message'], ('some message\n',))
551
self.assertEqual(events[0]['system'], 'wsgi')
552
self.assertTrue(events[0]['isError'])
553
self.assertEqual(events[1]['message'], ('another\nmessage\n',))
554
self.assertEqual(events[1]['system'], 'wsgi')
555
self.assertTrue(events[1]['isError'])
556
self.assertEqual(len(events), 2)
557
errors.addCallback(cbErrors)
561
class InputStreamTestMixin(WSGITestsMixin):
563
A mixin for L{TestCase} subclasses which defines a number of tests against
564
L{_InputStream}. The subclass is expected to create a file-like object to
565
be wrapped by an L{_InputStream} under test.
567
def getFileType(self):
568
raise NotImplementedError(
569
"%s.getFile must be implemented" % (self.__class__.__name__,))
572
def _renderAndReturnReaderResult(self, reader, content):
573
contentType = self.getFileType()
574
class CustomizedRequest(Request):
575
def gotLength(self, length):
576
# Always allocate a file of the specified type, instead of
577
# using the base behavior of selecting one depending on the
579
self.content = contentType()
581
def appFactoryFactory(reader):
583
def applicationFactory():
584
def application(*args):
585
environ, startResponse = args
586
result.callback(reader(environ['wsgi.input']))
587
startResponse('200 OK', [])
590
return result, applicationFactory
591
d, appFactory = appFactoryFactory(reader)
593
CustomizedRequest, appFactory, DummyChannel,
594
'PUT', '1.1', [], [''], None, [],
599
def test_readAll(self):
601
Calling L{_InputStream.read} with no arguments returns the entire input
604
bytes = "some bytes are here"
605
d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
606
d.addCallback(self.assertEquals, bytes)
610
def test_readSome(self):
612
Calling L{_InputStream.read} with an integer returns that many bytes
613
from the input stream, as long as it is less than or equal to the total
614
number of bytes available.
616
bytes = "hello, world."
617
d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
618
d.addCallback(self.assertEquals, "hel")
622
def test_readMoreThan(self):
624
Calling L{_InputStream.read} with an integer that is greater than the
625
total number of bytes in the input stream returns all bytes in the
628
bytes = "some bytes are here"
629
d = self._renderAndReturnReaderResult(
630
lambda input: input.read(len(bytes) + 3), bytes)
631
d.addCallback(self.assertEquals, bytes)
635
def test_readTwice(self):
637
Calling L{_InputStream.read} a second time returns bytes starting from
638
the position after the last byte returned by the previous read.
640
bytes = "some bytes, hello"
644
d = self._renderAndReturnReaderResult(read, bytes)
645
d.addCallback(self.assertEquals, bytes[3:])
649
def test_readNone(self):
651
Calling L{_InputStream.read} with C{None} as an argument returns all
652
bytes in the input stream.
654
bytes = "the entire stream"
655
d = self._renderAndReturnReaderResult(
656
lambda input: input.read(None), bytes)
657
d.addCallback(self.assertEquals, bytes)
661
def test_readNegative(self):
663
Calling L{_InputStream.read} with a negative integer as an argument
664
returns all bytes in the input stream.
666
bytes = "all of the input"
667
d = self._renderAndReturnReaderResult(
668
lambda input: input.read(-1), bytes)
669
d.addCallback(self.assertEquals, bytes)
673
def test_readline(self):
675
Calling L{_InputStream.readline} with no argument returns one line from
678
bytes = "hello\nworld"
679
d = self._renderAndReturnReaderResult(
680
lambda input: input.readline(), bytes)
681
d.addCallback(self.assertEquals, "hello\n")
685
def test_readlineSome(self):
687
Calling L{_InputStream.readline} with an integer returns at most that
688
many bytes, even if it is not enough to make up a complete line.
690
COMPATIBILITY NOTE: the size argument is excluded from the WSGI
691
specification, but is provided here anyhow, because useful libraries
692
such as python stdlib's cgi.py assume their input file-like-object
693
supports readline with a size argument. If you use it, be aware your
694
application may not be portable to other conformant WSGI servers.
696
bytes = "goodbye\nworld"
697
d = self._renderAndReturnReaderResult(
698
lambda input: input.readline(3), bytes)
699
d.addCallback(self.assertEquals, "goo")
703
def test_readlineMoreThan(self):
705
Calling L{_InputStream.readline} with an integer which is greater than
706
the number of bytes in the next line returns only the next line.
708
bytes = "some lines\nof text"
709
d = self._renderAndReturnReaderResult(
710
lambda input: input.readline(20), bytes)
711
d.addCallback(self.assertEquals, "some lines\n")
715
def test_readlineTwice(self):
717
Calling L{_InputStream.readline} a second time returns the line
718
following the line returned by the first call.
720
bytes = "first line\nsecond line\nlast line"
723
return input.readline()
724
d = self._renderAndReturnReaderResult(readline, bytes)
725
d.addCallback(self.assertEquals, "second line\n")
729
def test_readlineNone(self):
731
Calling L{_InputStream.readline} with C{None} as an argument returns
732
one line from the input stream.
734
bytes = "this is one line\nthis is another line"
735
d = self._renderAndReturnReaderResult(
736
lambda input: input.readline(None), bytes)
737
d.addCallback(self.assertEquals, "this is one line\n")
741
def test_readlineNegative(self):
743
Calling L{_InputStream.readline} with a negative integer as an argument
744
returns one line from the input stream.
746
bytes = "input stream line one\nline two"
747
d = self._renderAndReturnReaderResult(
748
lambda input: input.readline(-1), bytes)
749
d.addCallback(self.assertEquals, "input stream line one\n")
753
def test_readlines(self):
755
Calling L{_InputStream.readlines} with no arguments returns a list of
756
all lines from the input stream.
758
bytes = "alice\nbob\ncarol"
759
d = self._renderAndReturnReaderResult(
760
lambda input: input.readlines(), bytes)
761
d.addCallback(self.assertEquals, ["alice\n", "bob\n", "carol"])
765
def test_readlinesSome(self):
767
Calling L{_InputStream.readlines} with an integer as an argument
768
returns a list of lines from the input stream with the argument serving
769
as an approximate bound on the total number of bytes to read.
771
bytes = "123\n456\n789\n0"
772
d = self._renderAndReturnReaderResult(
773
lambda input: input.readlines(5), bytes)
775
# Make sure we got enough lines to make 5 bytes. Anything beyond
777
self.assertEquals(lines[:2], ["123\n", "456\n"])
778
d.addCallback(cbLines)
782
def test_readlinesMoreThan(self):
784
Calling L{_InputStream.readlines} with an integer which is greater than
785
the total number of bytes in the input stream returns a list of all
786
lines from the input.
788
bytes = "one potato\ntwo potato\nthree potato"
789
d = self._renderAndReturnReaderResult(
790
lambda input: input.readlines(100), bytes)
793
["one potato\n", "two potato\n", "three potato"])
797
def test_readlinesAfterRead(self):
799
Calling L{_InputStream.readlines} after a call to L{_InputStream.read}
800
returns lines starting at the byte after the last byte returned by the
803
bytes = "hello\nworld\nfoo"
804
def readlines(input):
806
return input.readlines()
807
d = self._renderAndReturnReaderResult(readlines, bytes)
808
d.addCallback(self.assertEquals, ["orld\n", "foo"])
812
def test_readlinesNone(self):
814
Calling L{_InputStream.readlines} with C{None} as an argument returns
815
all lines from the input.
817
bytes = "one fish\ntwo fish\n"
818
d = self._renderAndReturnReaderResult(
819
lambda input: input.readlines(None), bytes)
820
d.addCallback(self.assertEquals, ["one fish\n", "two fish\n"])
824
def test_readlinesNegative(self):
826
Calling L{_InputStream.readlines} with a negative integer as an
827
argument returns a list of all lines from the input.
829
bytes = "red fish\nblue fish\n"
830
d = self._renderAndReturnReaderResult(
831
lambda input: input.readlines(-1), bytes)
832
d.addCallback(self.assertEquals, ["red fish\n", "blue fish\n"])
836
def test_iterable(self):
838
Iterating over L{_InputStream} produces lines from the input stream.
840
bytes = "green eggs\nand ham\n"
841
d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
842
d.addCallback(self.assertEquals, ["green eggs\n", "and ham\n"])
846
def test_iterableAfterRead(self):
848
Iterating over L{_InputStream} after calling L{_InputStream.read}
849
produces lines from the input stream starting from the first byte after
850
the last byte returned by the C{read} call.
852
bytes = "green eggs\nand ham\n"
856
d = self._renderAndReturnReaderResult(iterate, bytes)
857
d.addCallback(self.assertEquals, ["en eggs\n", "and ham\n"])
862
class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
864
Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
866
def getFileType(self):
867
return StringIO.StringIO
871
class InputStreamCStringIOTests(InputStreamTestMixin, TestCase):
873
Tests for L{_InputStream} when it is wrapped around a
874
L{cStringIO.StringIO}.
876
def getFileType(self):
877
return cStringIO.StringIO
881
class InputStreamTemporaryFileTests(InputStreamTestMixin, TestCase):
883
Tests for L{_InputStream} when it is wrapped around a L{tempfile.TemporaryFile}.
885
def getFileType(self):
886
return tempfile.TemporaryFile
890
class StartResponseTests(WSGITestsMixin, TestCase):
892
Tests for the I{start_response} parameter passed to the application object
895
def test_status(self):
897
The response status passed to the I{start_response} callable is written
898
as the status of the response to the request.
900
channel = DummyChannel()
902
def applicationFactory():
903
def application(environ, startResponse):
904
startResponse('107 Strange message', [])
908
d, requestFactory = self.requestFactoryFactory()
909
def cbRendered(ignored):
911
channel.transport.written.getvalue().startswith(
912
'HTTP/1.1 107 Strange message'))
913
d.addCallback(cbRendered)
915
request = self.lowLevelRender(
916
requestFactory, applicationFactory,
917
lambda: channel, 'GET', '1.1', [], [''], None, [])
922
def _headersTest(self, appHeaders, expectedHeaders):
924
Verify that if the response headers given by C{appHeaders} are passed
925
to the I{start_response} callable, then the response header lines given
926
by C{expectedHeaders} plus I{Server} and I{Date} header lines are
927
included in the response.
929
# Make the Date header value deterministic
930
self.patch(http, 'datetimeToString', lambda: 'Tuesday')
932
channel = DummyChannel()
934
def applicationFactory():
935
def application(environ, startResponse):
936
startResponse('200 OK', appHeaders)
940
d, requestFactory = self.requestFactoryFactory()
941
def cbRendered(ignored):
942
response = channel.transport.written.getvalue()
943
headers, rest = response.split('\r\n\r\n', 1)
944
headerLines = headers.split('\r\n')[1:]
946
allExpectedHeaders = expectedHeaders + [
948
'Server: ' + version,
949
'Transfer-Encoding: chunked']
950
allExpectedHeaders.sort()
951
self.assertEqual(headerLines, allExpectedHeaders)
953
d.addCallback(cbRendered)
955
request = self.lowLevelRender(
956
requestFactory, applicationFactory,
957
lambda: channel, 'GET', '1.1', [], [''], None, [])
961
def test_headers(self):
963
The headers passed to the I{start_response} callable are included in
964
the response as are the required I{Date} and I{Server} headers and the
965
necessary connection (hop to hop) header I{Transfer-Encoding}.
967
return self._headersTest(
968
[('foo', 'bar'), ('baz', 'quux')],
969
['Baz: quux', 'Foo: bar'])
972
def test_applicationProvidedContentType(self):
974
If I{Content-Type} is included in the headers passed to the
975
I{start_response} callable, one I{Content-Type} header is included in
978
return self._headersTest(
979
[('content-type', 'monkeys are great')],
980
['Content-Type: monkeys are great'])
983
def test_applicationProvidedServerAndDate(self):
985
If either I{Server} or I{Date} is included in the headers passed to the
986
I{start_response} callable, they are disregarded.
988
return self._headersTest(
989
[('server', 'foo'), ('Server', 'foo'),
990
('date', 'bar'), ('dATE', 'bar')],
994
def test_delayedUntilReturn(self):
996
Nothing is written in response to a request when the I{start_response}
997
callable is invoked. If the iterator returned by the application
998
object produces only empty strings, the response is written after the
999
last element is produced.
1001
channel = DummyChannel()
1003
intermediateValues = []
1005
intermediateValues.append(channel.transport.written.getvalue())
1007
def applicationFactory():
1008
def application(environ, startResponse):
1009
startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
1014
d, requestFactory = self.requestFactoryFactory()
1015
def cbRendered(ignored):
1016
self.assertEqual(intermediateValues, [''])
1017
d.addCallback(cbRendered)
1019
request = self.lowLevelRender(
1020
requestFactory, applicationFactory,
1021
lambda: channel, 'GET', '1.1', [], [''], None, [])
1026
def test_delayedUntilContent(self):
1028
Nothing is written in response to a request when the I{start_response}
1029
callable is invoked. Once a non-empty string has been produced by the
1030
iterator returned by the application object, the response status and
1031
headers are written.
1033
channel = DummyChannel()
1035
intermediateValues = []
1037
intermediateValues.append(channel.transport.written.getvalue())
1039
def applicationFactory():
1040
def application(environ, startResponse):
1041
startResponse('200 OK', [('foo', 'bar')])
1048
d, requestFactory = self.requestFactoryFactory()
1049
def cbRendered(ignored):
1050
self.assertFalse(intermediateValues[0])
1051
self.assertTrue(intermediateValues[1])
1052
d.addCallback(cbRendered)
1054
request = self.lowLevelRender(
1055
requestFactory, applicationFactory,
1056
lambda: channel, 'GET', '1.1', [], [''], None, [])
1061
def test_content(self):
1063
Content produced by the iterator returned by the application object is
1064
written to the request as it is produced.
1066
channel = DummyChannel()
1068
intermediateValues = []
1070
intermediateValues.append(channel.transport.written.getvalue())
1072
def applicationFactory():
1073
def application(environ, startResponse):
1074
startResponse('200 OK', [('content-length', '6')])
1081
d, requestFactory = self.requestFactoryFactory()
1082
def cbRendered(ignored):
1084
self.getContentFromResponse(intermediateValues[0]),
1087
self.getContentFromResponse(intermediateValues[1]),
1089
d.addCallback(cbRendered)
1091
request = self.lowLevelRender(
1092
requestFactory, applicationFactory,
1093
lambda: channel, 'GET', '1.1', [], [''], None, [])
1098
def test_multipleStartResponse(self):
1100
If the I{start_response} callable is invoked multiple times before a
1101
data for the response body is produced, the values from the last call
1104
channel = DummyChannel()
1106
def applicationFactory():
1107
def application(environ, startResponse):
1108
startResponse('100 Foo', [])
1109
startResponse('200 Bar', [])
1113
d, requestFactory = self.requestFactoryFactory()
1114
def cbRendered(ignored):
1116
channel.transport.written.getvalue().startswith(
1117
'HTTP/1.1 200 Bar\r\n'))
1118
d.addCallback(cbRendered)
1120
request = self.lowLevelRender(
1121
requestFactory, applicationFactory,
1122
lambda: channel, 'GET', '1.1', [], [''], None, [])
1127
def test_startResponseWithException(self):
1129
If the I{start_response} callable is invoked with a third positional
1130
argument before the status and headers have been written to the
1131
response, the status and headers become the newly supplied values.
1133
channel = DummyChannel()
1135
def applicationFactory():
1136
def application(environ, startResponse):
1137
startResponse('100 Foo', [], (Exception, Exception("foo"), None))
1141
d, requestFactory = self.requestFactoryFactory()
1142
def cbRendered(ignored):
1144
channel.transport.written.getvalue().startswith(
1145
'HTTP/1.1 100 Foo\r\n'))
1146
d.addCallback(cbRendered)
1148
request = self.lowLevelRender(
1149
requestFactory, applicationFactory,
1150
lambda: channel, 'GET', '1.1', [], [''], None, [])
1155
def test_startResponseWithExceptionTooLate(self):
1157
If the I{start_response} callable is invoked with a third positional
1158
argument after the status and headers have been written to the
1159
response, the supplied I{exc_info} values are re-raised to the
1162
channel = DummyChannel()
1164
class SomeException(Exception):
1168
raise SomeException()
1170
excInfo = exc_info()
1174
def applicationFactory():
1175
def application(environ, startResponse):
1176
startResponse('200 OK', [])
1179
startResponse('500 ERR', [], excInfo)
1181
reraised.append(exc_info())
1184
d, requestFactory = self.requestFactoryFactory()
1185
def cbRendered(ignored):
1187
channel.transport.written.getvalue().startswith(
1188
'HTTP/1.1 200 OK\r\n'))
1189
self.assertEqual(reraised[0][0], excInfo[0])
1190
self.assertEqual(reraised[0][1], excInfo[1])
1191
self.assertEqual(reraised[0][2].tb_next, excInfo[2])
1193
d.addCallback(cbRendered)
1195
request = self.lowLevelRender(
1196
requestFactory, applicationFactory,
1197
lambda: channel, 'GET', '1.1', [], [''], None, [])
1202
def test_write(self):
1204
I{start_response} returns the I{write} callable which can be used to
1205
write bytes to the response body without buffering.
1207
channel = DummyChannel()
1209
intermediateValues = []
1211
intermediateValues.append(channel.transport.written.getvalue())
1213
def applicationFactory():
1214
def application(environ, startResponse):
1215
write = startResponse('100 Foo', [('content-length', '6')])
1223
d, requestFactory = self.requestFactoryFactory()
1224
def cbRendered(ignored):
1226
self.getContentFromResponse(intermediateValues[0]),
1229
self.getContentFromResponse(intermediateValues[1]),
1231
d.addCallback(cbRendered)
1233
request = self.lowLevelRender(
1234
requestFactory, applicationFactory,
1235
lambda: channel, 'GET', '1.1', [], [''], None, [])
1241
class ApplicationTests(WSGITestsMixin, TestCase):
1243
Tests for things which are done to the application object and the iterator
1246
def enableThreads(self):
1247
self.reactor = reactor
1248
self.threadpool = ThreadPool()
1249
self.threadpool.start()
1250
self.addCleanup(self.threadpool.stop)
1253
def test_close(self):
1255
If the application object returns an iterator which also has a I{close}
1256
method, that method is called after iteration is complete.
1258
channel = DummyChannel()
1273
def applicationFactory():
1274
def application(environ, startResponse):
1275
startResponse('200 OK', [('content-length', '3')])
1279
d, requestFactory = self.requestFactoryFactory()
1280
def cbRendered(ignored):
1282
self.getContentFromResponse(
1283
channel.transport.written.getvalue()),
1285
self.assertFalse(result.open)
1286
d.addCallback(cbRendered)
1288
self.lowLevelRender(
1289
requestFactory, applicationFactory,
1290
lambda: channel, 'GET', '1.1', [], [''])
1295
def test_applicationCalledInThread(self):
1297
The application object is invoked and iterated in a thread which is not
1300
self.enableThreads()
1303
def applicationFactory():
1304
def application(environ, startResponse):
1307
invoked.append(get_ident())
1309
invoked.append(get_ident())
1310
startResponse('200 OK', [('content-length', '3')])
1314
d, requestFactory = self.requestFactoryFactory()
1315
def cbRendered(ignored):
1316
self.assertNotIn(get_ident(), invoked)
1317
self.assertEqual(len(set(invoked)), 1)
1318
d.addCallback(cbRendered)
1320
self.lowLevelRender(
1321
requestFactory, applicationFactory,
1322
DummyChannel, 'GET', '1.1', [], [''])
1327
def test_writeCalledFromThread(self):
1329
The I{write} callable returned by I{start_response} calls the request's
1330
C{write} method in the reactor thread.
1332
self.enableThreads()
1335
class ThreadVerifier(Request):
1336
def write(self, bytes):
1337
invoked.append(get_ident())
1338
return Request.write(self, bytes)
1340
def applicationFactory():
1341
def application(environ, startResponse):
1342
write = startResponse('200 OK', [])
1347
d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
1348
def cbRendered(ignored):
1349
self.assertEqual(set(invoked), set([get_ident()]))
1350
d.addCallback(cbRendered)
1352
self.lowLevelRender(
1353
requestFactory, applicationFactory, DummyChannel,
1354
'GET', '1.1', [], [''])
1359
def test_iteratedValuesWrittenFromThread(self):
1361
Strings produced by the iterator returned by the application object are
1362
written to the request in the reactor thread.
1364
self.enableThreads()
1367
class ThreadVerifier(Request):
1368
def write(self, bytes):
1369
invoked.append(get_ident())
1370
return Request.write(self, bytes)
1372
def applicationFactory():
1373
def application(environ, startResponse):
1374
startResponse('200 OK', [])
1378
d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
1379
def cbRendered(ignored):
1380
self.assertEqual(set(invoked), set([get_ident()]))
1381
d.addCallback(cbRendered)
1383
self.lowLevelRender(
1384
requestFactory, applicationFactory, DummyChannel,
1385
'GET', '1.1', [], [''])
1390
def test_statusWrittenFromThread(self):
1392
The response status is set on the request object in the reactor thread.
1394
self.enableThreads()
1397
class ThreadVerifier(Request):
1398
def setResponseCode(self, code, message):
1399
invoked.append(get_ident())
1400
return Request.setResponseCode(self, code, message)
1402
def applicationFactory():
1403
def application(environ, startResponse):
1404
startResponse('200 OK', [])
1408
d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
1409
def cbRendered(ignored):
1410
self.assertEqual(set(invoked), set([get_ident()]))
1411
d.addCallback(cbRendered)
1413
self.lowLevelRender(
1414
requestFactory, applicationFactory, DummyChannel,
1415
'GET', '1.1', [], [''])
1420
def test_connectionClosedDuringIteration(self):
1422
If the request connection is lost while the application object is being
1423
iterated, iteration is stopped.
1425
class UnreliableConnection(Request):
1427
This is a request which pretends its connection is lost immediately
1428
after the first write is done to it.
1430
def write(self, bytes):
1431
self.connectionLost(Failure(ConnectionLost("No more connection")))
1433
self.badIter = False
1437
raise Exception("Should not have gotten here")
1439
def applicationFactory():
1440
def application(environ, startResponse):
1441
startResponse('200 OK', [])
1445
d, requestFactory = self.requestFactoryFactory(UnreliableConnection)
1446
def cbRendered(ignored):
1447
self.assertFalse(self.badIter, "Should not have resumed iteration")
1448
d.addCallback(cbRendered)
1450
self.lowLevelRender(
1451
requestFactory, applicationFactory, DummyChannel,
1452
'GET', '1.1', [], [''])
1454
return self.assertFailure(d, ConnectionLost)
1457
def _internalServerErrorTest(self, application):
1458
channel = DummyChannel()
1460
def applicationFactory():
1463
d, requestFactory = self.requestFactoryFactory()
1464
def cbRendered(ignored):
1465
errors = self.flushLoggedErrors(RuntimeError)
1466
self.assertEquals(len(errors), 1)
1469
channel.transport.written.getvalue().startswith(
1470
'HTTP/1.1 500 Internal Server Error'))
1471
d.addCallback(cbRendered)
1473
request = self.lowLevelRender(
1474
requestFactory, applicationFactory,
1475
lambda: channel, 'GET', '1.1', [], [''], None, [])
1480
def test_applicationExceptionBeforeStartResponse(self):
1482
If the application raises an exception before calling I{start_response}
1483
then the response status is I{500} and the exception is logged.
1485
def application(environ, startResponse):
1486
raise RuntimeError("This application had some error.")
1487
return self._internalServerErrorTest(application)
1490
def test_applicationExceptionAfterStartResponse(self):
1492
If the application calls I{start_response} but then raises an exception
1493
before any data is written to the response then the response status is
1494
I{500} and the exception is logged.
1496
def application(environ, startResponse):
1497
startResponse('200 OK', [])
1498
raise RuntimeError("This application had some error.")
1499
return self._internalServerErrorTest(application)
1502
def _connectionClosedTest(self, application, responseContent):
1503
channel = DummyChannel()
1505
def applicationFactory():
1508
d, requestFactory = self.requestFactoryFactory()
1510
# Capture the request so we can disconnect it later on.
1512
def requestFactoryWrapper(*a, **kw):
1513
requests.append(requestFactory(*a, **kw))
1516
def ebRendered(ignored):
1517
errors = self.flushLoggedErrors(RuntimeError)
1518
self.assertEquals(len(errors), 1)
1520
response = channel.transport.written.getvalue()
1521
self.assertTrue(response.startswith('HTTP/1.1 200 OK'))
1522
# Chunked transfer-encoding makes this a little messy.
1523
self.assertIn(responseContent, response)
1524
d.addErrback(ebRendered)
1526
request = self.lowLevelRender(
1527
requestFactoryWrapper, applicationFactory,
1528
lambda: channel, 'GET', '1.1', [], [''], None, [])
1530
# By now the connection should be closed.
1531
self.assertTrue(channel.transport.disconnected)
1532
# Give it a little push to go the rest of the way.
1533
requests[0].connectionLost(Failure(ConnectionLost("All gone")))
1538
def test_applicationExceptionAfterWrite(self):
1540
If the application raises an exception after the response status has
1541
already been sent then the connection is closed and the exception is
1545
'Some bytes, triggering the server to start sending the response')
1547
def application(environ, startResponse):
1548
startResponse('200 OK', [])
1549
yield responseContent
1550
raise RuntimeError("This application had some error.")
1551
return self._connectionClosedTest(application, responseContent)
1554
def test_applicationCloseException(self):
1556
If the application returns a closeable iterator and the C{close} method
1557
raises an exception when called then the connection is still closed and
1558
the exception is logged.
1560
responseContent = 'foo'
1562
class Application(object):
1563
def __init__(self, environ, startResponse):
1564
startResponse('200 OK', [])
1567
yield responseContent
1570
raise RuntimeError("This application had some error.")
1572
return self._connectionClosedTest(Application, responseContent)