Ticket #7993: wsgi-python3-7993-3.1.patch

File wsgi-python3-7993-3.1.patch, 59.5 KB (added by Gavin Panella, 4 years ago)
  • twisted/web/test/test_wsgi.py

    diff --git a/twisted/web/test/test_wsgi.py b/twisted/web/test/test_wsgi.py
    index 4e902e5..ae14467 100644
    a b Tests for L{twisted.web.wsgi}. 
    88__metaclass__ = type
    99
    1010from sys import exc_info
    11 from urllib import quote
    12 from thread import get_ident
    13 import StringIO, cStringIO, tempfile
     11import tempfile
     12import traceback
    1413
    1514from zope.interface.verify import verifyObject
    1615
     16from twisted.python.compat import intToBytes, urlquote, _PY3
    1717from twisted.python.log import addObserver, removeObserver, err
    1818from twisted.python.failure import Failure
     19from twisted.python.threadable import getThreadID
    1920from twisted.python.threadpool import ThreadPool
    20 from twisted.internet.defer import Deferred, gatherResults
     21from twisted.internet.defer import Deferred, gatherResults, inlineCallbacks
    2122from twisted.internet import reactor
    2223from twisted.internet.error import ConnectionLost
    23 from twisted.trial.unittest import TestCase
     24from twisted.trial.unittest import TestCase, SkipTest
    2425from twisted.web import http
    2526from twisted.web.resource import IResource, Resource
    2627from twisted.web.server import Request, Site, version
    from twisted.web.wsgi import WSGIResource 
    2829from twisted.web.test.test_web import DummyChannel
    2930
    3031
     32
    3133class SynchronousThreadPool:
    3234    """
    3335    A single-threaded implementation of part of the L{ThreadPool} interface.
    class WSGIResourceTests(TestCase): 
    9698        self.assertRaises(
    9799            RuntimeError,
    98100            self.resource.getChildWithDefault,
    99             "foo", Request(DummyChannel(), False))
     101            b"foo", Request(DummyChannel(), False))
    100102        self.assertRaises(
    101103            RuntimeError,
    102104            self.resource.putChild,
    103             "foo", Resource())
     105            b"foo", Resource())
    104106
    105107
    106108class WSGITestsMixin:
    class WSGITestsMixin: 
    146148            object for this configuration and request (ie, the environment and
    147149            start_response callable).
    148150        """
     151        def _toByteString(string):
     152            # Twisted's HTTP implementation prefers byte strings. As a
     153            # convenience for tests, string arguments are encoded to an
     154            # ISO-8859-1 byte string (if not already) before being passed on.
     155            if isinstance(string, bytes):
     156                return string
     157            else:
     158                return string.encode('iso-8859-1')
     159
    149160        root = WSGIResource(
    150161            self.reactor, self.threadpool, applicationFactory())
    151162        resourceSegments.reverse()
    152163        for seg in resourceSegments:
    153164            tmp = Resource()
    154             tmp.putChild(seg, root)
     165            tmp.putChild(_toByteString(seg), root)
    155166            root = tmp
    156167
    157168        channel = channelFactory()
    158169        channel.site = Site(root)
    159170        request = requestFactory(channel, False)
    160171        for k, v in headers:
    161             request.requestHeaders.addRawHeader(k, v)
     172            request.requestHeaders.addRawHeader(
     173                _toByteString(k), _toByteString(v))
    162174        request.gotLength(0)
    163175        if body:
    164176            request.content.write(body)
    165177            request.content.seek(0)
    166         uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
     178        uri = '/' + '/'.join([urlquote(seg, safe) for seg in requestSegments])
    167179        if query is not None:
    168             uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
     180            uri += '?' + '&'.join(['='.join([urlquote(k, safe), urlquote(v, safe)])
    169181                                   for (k, v) in query])
    170         request.requestReceived(method, uri, 'HTTP/' + version)
     182        request.requestReceived(
     183            _toByteString(method), _toByteString(uri),
     184            b'HTTP/' + _toByteString(version))
    171185        return request
    172186
    173187
    class WSGITestsMixin: 
    199213
    200214
    201215    def getContentFromResponse(self, response):
    202         return response.split('\r\n\r\n', 1)[1]
     216        return response.split(b'\r\n\r\n', 1)[1]
     217
     218
     219    def prepareRequest(self, application=None):
     220        """
     221        Prepare a L{Request} which, when a request is received, captures the
     222        C{environ} and C{start_response} callable passed to a WSGI app.
     223
     224        @param application: An optional WSGI application callable that accepts
     225            the familiar C{environ} and C{start_response} args and returns an
     226            iterable of body content. If not supplied, C{start_response} will
     227            be called with a "200 OK" status and no headers, and no content
     228            will be yielded.
     229
     230        @return: A two-tuple of (C{request}, C{deferred}). The former is a
     231            Twisted L{Request}. The latter is a L{Deferred} which will be
     232            called back with a two-tuple of the arguments passed to a WSGI
     233            application (i.e. the C{environ} and C{start_response} callable),
     234            or will errback with any error arising within the WSGI app.
     235        """
     236        result = Deferred()
     237
     238        def outerApplication(environ, startResponse):
     239            try:
     240                if application is None:
     241                    startResponse('200 OK', [])
     242                    content = iter(())  # No content.
     243                else:
     244                    content = application(environ, startResponse)
     245            except:
     246                result.errback()
     247                startResponse('500 Error', [], exc_info())
     248                return iter(())
     249            else:
     250                result.callback((environ, startResponse))
     251                return content
     252
     253        resource = WSGIResource(
     254            self.reactor, self.threadpool, outerApplication)
     255
     256        root = Resource()
     257        root.putChild(b"res", resource)
     258
     259        channel = self.channelFactory()
     260        channel.site = Site(root)
     261
     262        class CannedRequest(Request):
     263            """
     264            Convenient L{Request} derivative which has canned values for all
     265            of C{requestReceived}'s arguments.
     266            """
     267            def requestReceived(
     268                    self, command=b"GET", path=b"/res", version=b"1.1"):
     269                return Request.requestReceived(
     270                    self, command=command, path=path, version=version)
     271
     272        request = CannedRequest(channel, queued=False)
     273        request.gotLength(0)  # Initialize buffer for request body.
     274
     275        return request, result
    203276
    204277
    205278
    class EnvironTests(WSGITestsMixin, TestCase): 
    209282    object by L{twisted.web.wsgi.WSGIResource}.
    210283    """
    211284    def environKeyEqual(self, key, value):
    212         def assertEnvironKeyEqual((environ, startResponse)):
     285        def assertEnvironKeyEqual(result):
     286            environ, startResponse = result
    213287            self.assertEqual(environ[key], value)
     288            return value
    214289        return assertEnvironKeyEqual
    215290
    216291
    class EnvironTests(WSGITestsMixin, TestCase): 
    220295        parameter which is exactly of type C{dict}.
    221296        """
    222297        d = self.render('GET', '1.1', [], [''])
    223         def cbRendered((environ, startResponse)):
     298        def cbRendered(result):
     299            environ, startResponse = result
    224300            self.assertIdentical(type(environ), dict)
     301            # Environment keys are always native strings.
     302            for name in environ:
     303                self.assertIsInstance(name, str)
    225304        d.addCallback(cbRendered)
    226305        return d
    227306
    class EnvironTests(WSGITestsMixin, TestCase): 
    243322        return gatherResults([get, post])
    244323
    245324
     325    @inlineCallbacks
     326    def test_requestMethodIsNativeString(self):
     327        """
     328        The C{'REQUEST_METHOD'} key of the C{environ} C{dict} passed to the
     329        application is always a native string.
     330        """
     331        for method in b"GET", u"GET":
     332            request, result = self.prepareRequest()
     333            request.requestReceived(method)
     334            result.addCallback(self.environKeyEqual('REQUEST_METHOD', 'GET'))
     335            self.assertIsInstance((yield result), str)
     336
     337
    246338    def test_scriptName(self):
    247339        """
    248340        The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    265357        internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
    266358
    267359        unencoded = self.render(
    268             'GET', '1.1', ['foo', '/', 'bar\xff'], ['foo', '/', 'bar\xff'])
     360            'GET', '1.1', ['foo', '/', b'bar\xff'], ['foo', '/', b'bar\xff'])
    269361        # The RFC says "(not URL-encoded)", even though that makes
    270362        # interpretation of SCRIPT_NAME ambiguous.
    271363        unencoded.addCallback(
    class EnvironTests(WSGITestsMixin, TestCase): 
    275367                root, emptyChild, leaf, container, internal, unencoded])
    276368
    277369
     370    @inlineCallbacks
     371    def test_scriptNameIsNativeString(self):
     372        """
     373        The C{'SCRIPT_NAME'} key of the C{environ} C{dict} passed to the
     374        application is always a native string.
     375        """
     376        request, result = self.prepareRequest()
     377        request.requestReceived(path=b"/res")
     378        result.addCallback(self.environKeyEqual('SCRIPT_NAME', '/res'))
     379        self.assertIsInstance((yield result), str)
     380
     381        if _PY3:
     382            # Native strings are rejected by Request.requestReceived() before
     383            # t.w.wsgi has any say in the matter.
     384            request, result = self.prepareRequest()
     385            self.assertRaises(TypeError, request.requestReceived, path=u"/res")
     386        else:
     387            request, result = self.prepareRequest()
     388            request.requestReceived(path=u"/res")
     389            result.addCallback(self.environKeyEqual('SCRIPT_NAME', '/res'))
     390            self.assertIsInstance((yield result), str)
     391
     392
    278393    def test_pathInfo(self):
    279394        """
    280395        The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    302417        internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
    303418        internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
    304419
    305         unencoded = self.render('GET', '1.1', [], ['foo', '/', 'bar\xff'])
     420        unencoded = self.render('GET', '1.1', [], ['foo', '/', b'bar\xff'])
    306421        unencoded.addCallback(
    307422            self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
    308423
    class EnvironTests(WSGITestsMixin, TestCase): 
    311426                internalContainer, unencoded])
    312427
    313428
     429    @inlineCallbacks
     430    def test_pathInfoIsNativeString(self):
     431        """
     432        The C{'PATH_INFO'} key of the C{environ} C{dict} passed to the
     433        application is always a native string.
     434        """
     435        request, result = self.prepareRequest()
     436        request.requestReceived(path=b"/res/foo/bar")
     437        result.addCallback(self.environKeyEqual('PATH_INFO', '/foo/bar'))
     438        self.assertIsInstance((yield result), str)
     439
     440        if _PY3:
     441            # Native strings are rejected by Request.requestReceived() before
     442            # t.w.wsgi has any say in the matter.
     443            request, result = self.prepareRequest()
     444            self.assertRaises(
     445                TypeError, request.requestReceived, path=u"/res/foo/bar")
     446        else:
     447            request, result = self.prepareRequest()
     448            request.requestReceived(path=u"/res/foo/bar")
     449            result.addCallback(self.environKeyEqual('PATH_INFO', '/foo/bar'))
     450            self.assertIsInstance((yield result), str)
     451
     452
    314453    def test_queryString(self):
    315454        """
    316455        The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    341480            missing, empty, present, unencoded, doubleQuestion])
    342481
    343482
     483    @inlineCallbacks
     484    def test_queryStringIsNativeString(self):
     485        """
     486        The C{'QUERY_STRING'} key of the C{environ} C{dict} passed to the
     487        application is always a native string.
     488        """
     489        request, result = self.prepareRequest()
     490        request.requestReceived(path=b"/res?foo=bar")
     491        result.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
     492        self.assertIsInstance((yield result), str)
     493
     494        if _PY3:
     495            # Native strings are rejected by Request.requestReceived() before
     496            # t.w.wsgi has any say in the matter.
     497            request, result = self.prepareRequest()
     498            self.assertRaises(
     499                TypeError, request.requestReceived, path=u"/res?foo=bar")
     500        else:
     501            request, result = self.prepareRequest()
     502            request.requestReceived(path=u"/res?foo=bar")
     503            result.addCallback(self.environKeyEqual('QUERY_STRING', 'foo=bar'))
     504            self.assertIsInstance((yield result), str)
     505
     506
    344507    def test_contentType(self):
    345508        """
    346509        The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    357520        return gatherResults([missing, present])
    358521
    359522
     523    @inlineCallbacks
     524    def test_contentTypeIsNativeString(self):
     525        """
     526        The C{'CONTENT_TYPE'} key of the C{environ} C{dict} passed to the
     527        application is always a native string.
     528        """
     529        for contentType in b"x-foo/bar", u"x-foo/bar":
     530            request, result = self.prepareRequest()
     531            request.requestHeaders.addRawHeader(b"Content-Type", contentType)
     532            request.requestReceived()
     533            result.addCallback(self.environKeyEqual('CONTENT_TYPE', 'x-foo/bar'))
     534            self.assertIsInstance((yield result), str)
     535
     536
    360537    def test_contentLength(self):
    361538        """
    362539        The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    373550        return gatherResults([missing, present])
    374551
    375552
     553    @inlineCallbacks
     554    def test_contentLengthIsNativeString(self):
     555        """
     556        The C{'CONTENT_LENGTH'} key of the C{environ} C{dict} passed to the
     557        application is always a native string.
     558        """
     559        for contentLength in b"1234", u"1234":
     560            request, result = self.prepareRequest()
     561            request.requestHeaders.addRawHeader(b"Content-Length", contentLength)
     562            request.requestReceived()
     563            result.addCallback(self.environKeyEqual('CONTENT_LENGTH', '1234'))
     564            self.assertIsInstance((yield result), str)
     565
     566
    376567    def test_serverName(self):
    377568        """
    378569        The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    393584        return gatherResults([missing, present])
    394585
    395586
     587    @inlineCallbacks
     588    def test_serverNameIsNativeString(self):
     589        """
     590        The C{'SERVER_NAME'} key of the C{environ} C{dict} passed to the
     591        application is always a native string.
     592        """
     593        for serverName in b"host.example.com", u"host.example.com":
     594            request, result = self.prepareRequest()
     595            # This is kind of a cheat; getRequestHostname() breaks in Python 3
     596            # when the "Host" request header is set to a native string because
     597            # it tries to split around b":", so we patch the method.
     598            request.getRequestHostname = lambda: serverName
     599            request.requestReceived()
     600            result.addCallback(self.environKeyEqual('SERVER_NAME', 'host.example.com'))
     601            self.assertIsInstance((yield result), str)
     602
     603
    396604    def test_serverPort(self):
    397605        """
    398606        The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    412620        return d
    413621
    414622
     623    @inlineCallbacks
     624    def test_serverPortIsNativeString(self):
     625        """
     626        The C{'SERVER_PORT'} key of the C{environ} C{dict} passed to the
     627        application is always a native string.
     628        """
     629        request, result = self.prepareRequest()
     630        request.requestReceived()
     631        result.addCallback(self.environKeyEqual('SERVER_PORT', '80'))
     632        self.assertIsInstance((yield result), str)
     633
     634
    415635    def test_serverProtocol(self):
    416636        """
    417637        The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    427647        return gatherResults([old, new])
    428648
    429649
     650    @inlineCallbacks
     651    def test_serverProtocolIsNativeString(self):
     652        """
     653        The C{'SERVER_PROTOCOL'} key of the C{environ} C{dict} passed to the
     654        application is always a native string.
     655        """
     656        for serverProtocol in b"1.1", u"1.1":
     657            request, result = self.prepareRequest()
     658            # In Python 3, native strings can be rejected by Request.write()
     659            # which will cause a crash after the bit we're trying to test, so
     660            # we patch write() out here to do nothing.
     661            request.write = lambda data: None
     662            request.requestReceived(version=b"1.1")
     663            result.addCallback(self.environKeyEqual('SERVER_PROTOCOL', '1.1'))
     664            self.assertIsInstance((yield result), str)
     665
     666
    430667    def test_remoteAddr(self):
    431668        """
    432669        The C{'REMOTE_ADDR'} key of the C{environ} C{dict} passed to the
    class EnvironTests(WSGITestsMixin, TestCase): 
    444681        """
    445682        singleValue = self.render(
    446683            'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
    447         def cbRendered((environ, startResponse)):
     684        def cbRendered(result):
     685            environ, startResponse = result
    448686            self.assertEqual(environ['HTTP_FOO'], 'bar')
    449687            self.assertEqual(environ['HTTP_BAZ'], 'quux')
    450688        singleValue.addCallback(cbRendered)
    class EnvironTests(WSGITestsMixin, TestCase): 
    541779        self.addCleanup(removeObserver, events.append)
    542780
    543781        errors = self.render('GET', '1.1', [], [''])
    544         def cbErrors((environ, startApplication)):
     782        def cbErrors(result):
     783            environ, startApplication = result
    545784            errors = environ['wsgi.errors']
    546785            errors.write('some message\n')
    547786            errors.writelines(['another\nmessage\n'])
    class EnvironTests(WSGITestsMixin, TestCase): 
    557796        return errors
    558797
    559798
     799    @inlineCallbacks
     800    def test_wsgiErrorsAcceptsOnlyNativeStrings(self):
     801        """
     802        The C{'wsgi.errors'} file-like object from the C{environ} C{dict} will
     803        permit writes of only native strings.
     804        """
     805        request, result = self.prepareRequest()
     806        request.requestReceived()
     807        environ, _ = yield result
     808        errors = environ["wsgi.errors"]
     809
     810        if _PY3:
     811            self.assertRaises(TypeError, errors.write, b"fred")
     812        else:
     813            self.assertRaises(TypeError, errors.write, u"fred")
     814
     815
     816
    560817class InputStreamTestMixin(WSGITestsMixin):
    561818    """
    562819    A mixin for L{TestCase} subclasses which defines a number of tests against
    class InputStreamTestMixin(WSGITestsMixin): 
    600857        Calling L{_InputStream.read} with no arguments returns the entire input
    601858        stream.
    602859        """
    603         bytes = "some bytes are here"
     860        bytes = b"some bytes are here"
    604861        d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
    605862        d.addCallback(self.assertEqual, bytes)
    606863        return d
    class InputStreamTestMixin(WSGITestsMixin): 
    612869        from the input stream, as long as it is less than or equal to the total
    613870        number of bytes available.
    614871        """
    615         bytes = "hello, world."
     872        bytes = b"hello, world."
    616873        d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
    617         d.addCallback(self.assertEqual, "hel")
     874        d.addCallback(self.assertEqual, b"hel")
    618875        return d
    619876
    620877
    class InputStreamTestMixin(WSGITestsMixin): 
    624881        total number of bytes in the input stream returns all bytes in the
    625882        input stream.
    626883        """
    627         bytes = "some bytes are here"
     884        bytes = b"some bytes are here"
    628885        d = self._renderAndReturnReaderResult(
    629886            lambda input: input.read(len(bytes) + 3), bytes)
    630887        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    636893        Calling L{_InputStream.read} a second time returns bytes starting from
    637894        the position after the last byte returned by the previous read.
    638895        """
    639         bytes = "some bytes, hello"
     896        bytes = b"some bytes, hello"
    640897        def read(input):
    641898            input.read(3)
    642899            return input.read()
    class InputStreamTestMixin(WSGITestsMixin): 
    650907        Calling L{_InputStream.read} with C{None} as an argument returns all
    651908        bytes in the input stream.
    652909        """
    653         bytes = "the entire stream"
     910        bytes = b"the entire stream"
    654911        d = self._renderAndReturnReaderResult(
    655912            lambda input: input.read(None), bytes)
    656913        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    662919        Calling L{_InputStream.read} with a negative integer as an argument
    663920        returns all bytes in the input stream.
    664921        """
    665         bytes = "all of the input"
     922        bytes = b"all of the input"
    666923        d = self._renderAndReturnReaderResult(
    667924            lambda input: input.read(-1), bytes)
    668925        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    674931        Calling L{_InputStream.readline} with no argument returns one line from
    675932        the input stream.
    676933        """
    677         bytes = "hello\nworld"
     934        bytes = b"hello\nworld"
    678935        d = self._renderAndReturnReaderResult(
    679936            lambda input: input.readline(), bytes)
    680         d.addCallback(self.assertEqual, "hello\n")
     937        d.addCallback(self.assertEqual, b"hello\n")
    681938        return d
    682939
    683940
    class InputStreamTestMixin(WSGITestsMixin): 
    692949        supports readline with a size argument. If you use it, be aware your
    693950        application may not be portable to other conformant WSGI servers.
    694951        """
    695         bytes = "goodbye\nworld"
     952        bytes = b"goodbye\nworld"
    696953        d = self._renderAndReturnReaderResult(
    697954            lambda input: input.readline(3), bytes)
    698         d.addCallback(self.assertEqual, "goo")
     955        d.addCallback(self.assertEqual, b"goo")
    699956        return d
    700957
    701958
    class InputStreamTestMixin(WSGITestsMixin): 
    704961        Calling L{_InputStream.readline} with an integer which is greater than
    705962        the number of bytes in the next line returns only the next line.
    706963        """
    707         bytes = "some lines\nof text"
     964        bytes = b"some lines\nof text"
    708965        d = self._renderAndReturnReaderResult(
    709966            lambda input: input.readline(20), bytes)
    710         d.addCallback(self.assertEqual, "some lines\n")
     967        d.addCallback(self.assertEqual, b"some lines\n")
    711968        return d
    712969
    713970
    class InputStreamTestMixin(WSGITestsMixin): 
    716973        Calling L{_InputStream.readline} a second time returns the line
    717974        following the line returned by the first call.
    718975        """
    719         bytes = "first line\nsecond line\nlast line"
     976        bytes = b"first line\nsecond line\nlast line"
    720977        def readline(input):
    721978            input.readline()
    722979            return input.readline()
    723980        d = self._renderAndReturnReaderResult(readline, bytes)
    724         d.addCallback(self.assertEqual, "second line\n")
     981        d.addCallback(self.assertEqual, b"second line\n")
    725982        return d
    726983
    727984
    class InputStreamTestMixin(WSGITestsMixin): 
    730987        Calling L{_InputStream.readline} with C{None} as an argument returns
    731988        one line from the input stream.
    732989        """
    733         bytes = "this is one line\nthis is another line"
     990        bytes = b"this is one line\nthis is another line"
    734991        d = self._renderAndReturnReaderResult(
    735992            lambda input: input.readline(None), bytes)
    736         d.addCallback(self.assertEqual, "this is one line\n")
     993        d.addCallback(self.assertEqual, b"this is one line\n")
    737994        return d
    738995
    739996
    class InputStreamTestMixin(WSGITestsMixin): 
    742999        Calling L{_InputStream.readline} with a negative integer as an argument
    7431000        returns one line from the input stream.
    7441001        """
    745         bytes = "input stream line one\nline two"
     1002        bytes = b"input stream line one\nline two"
    7461003        d = self._renderAndReturnReaderResult(
    7471004            lambda input: input.readline(-1), bytes)
    748         d.addCallback(self.assertEqual, "input stream line one\n")
     1005        d.addCallback(self.assertEqual, b"input stream line one\n")
    7491006        return d
    7501007
    7511008
    class InputStreamTestMixin(WSGITestsMixin): 
    7541011        Calling L{_InputStream.readlines} with no arguments returns a list of
    7551012        all lines from the input stream.
    7561013        """
    757         bytes = "alice\nbob\ncarol"
     1014        bytes = b"alice\nbob\ncarol"
    7581015        d = self._renderAndReturnReaderResult(
    7591016            lambda input: input.readlines(), bytes)
    760         d.addCallback(self.assertEqual, ["alice\n", "bob\n", "carol"])
     1017        d.addCallback(self.assertEqual, [b"alice\n", b"bob\n", b"carol"])
    7611018        return d
    7621019
    7631020
    class InputStreamTestMixin(WSGITestsMixin): 
    7671024        returns a list of lines from the input stream with the argument serving
    7681025        as an approximate bound on the total number of bytes to read.
    7691026        """
    770         bytes = "123\n456\n789\n0"
     1027        bytes = b"123\n456\n789\n0"
    7711028        d = self._renderAndReturnReaderResult(
    7721029            lambda input: input.readlines(5), bytes)
    7731030        def cbLines(lines):
    7741031            # Make sure we got enough lines to make 5 bytes.  Anything beyond
    7751032            # that is fine too.
    776             self.assertEqual(lines[:2], ["123\n", "456\n"])
     1033            self.assertEqual(lines[:2], [b"123\n", b"456\n"])
    7771034        d.addCallback(cbLines)
    7781035        return d
    7791036
    class InputStreamTestMixin(WSGITestsMixin): 
    7841041        the total number of bytes in the input stream returns a list of all
    7851042        lines from the input.
    7861043        """
    787         bytes = "one potato\ntwo potato\nthree potato"
     1044        bytes = b"one potato\ntwo potato\nthree potato"
    7881045        d = self._renderAndReturnReaderResult(
    7891046            lambda input: input.readlines(100), bytes)
    7901047        d.addCallback(
    7911048            self.assertEqual,
    792             ["one potato\n", "two potato\n", "three potato"])
     1049            [b"one potato\n", b"two potato\n", b"three potato"])
    7931050        return d
    7941051
    7951052
    class InputStreamTestMixin(WSGITestsMixin): 
    7991056        returns lines starting at the byte after the last byte returned by the
    8001057        C{read} call.
    8011058        """
    802         bytes = "hello\nworld\nfoo"
     1059        bytes = b"hello\nworld\nfoo"
    8031060        def readlines(input):
    8041061            input.read(7)
    8051062            return input.readlines()
    8061063        d = self._renderAndReturnReaderResult(readlines, bytes)
    807         d.addCallback(self.assertEqual, ["orld\n", "foo"])
     1064        d.addCallback(self.assertEqual, [b"orld\n", b"foo"])
    8081065        return d
    8091066
    8101067
    class InputStreamTestMixin(WSGITestsMixin): 
    8131070        Calling L{_InputStream.readlines} with C{None} as an argument returns
    8141071        all lines from the input.
    8151072        """
    816         bytes = "one fish\ntwo fish\n"
     1073        bytes = b"one fish\ntwo fish\n"
    8171074        d = self._renderAndReturnReaderResult(
    8181075            lambda input: input.readlines(None), bytes)
    819         d.addCallback(self.assertEqual, ["one fish\n", "two fish\n"])
     1076        d.addCallback(self.assertEqual, [b"one fish\n", b"two fish\n"])
    8201077        return d
    8211078
    8221079
    class InputStreamTestMixin(WSGITestsMixin): 
    8251082        Calling L{_InputStream.readlines} with a negative integer as an
    8261083        argument returns a list of all lines from the input.
    8271084        """
    828         bytes = "red fish\nblue fish\n"
     1085        bytes = b"red fish\nblue fish\n"
    8291086        d = self._renderAndReturnReaderResult(
    8301087            lambda input: input.readlines(-1), bytes)
    831         d.addCallback(self.assertEqual, ["red fish\n", "blue fish\n"])
     1088        d.addCallback(self.assertEqual, [b"red fish\n", b"blue fish\n"])
    8321089        return d
    8331090
    8341091
    class InputStreamTestMixin(WSGITestsMixin): 
    8361093        """
    8371094        Iterating over L{_InputStream} produces lines from the input stream.
    8381095        """
    839         bytes = "green eggs\nand ham\n"
     1096        bytes = b"green eggs\nand ham\n"
    8401097        d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
    841         d.addCallback(self.assertEqual, ["green eggs\n", "and ham\n"])
     1098        d.addCallback(self.assertEqual, [b"green eggs\n", b"and ham\n"])
    8421099        return d
    8431100
    8441101
    class InputStreamTestMixin(WSGITestsMixin): 
    8481105        produces lines from the input stream starting from the first byte after
    8491106        the last byte returned by the C{read} call.
    8501107        """
    851         bytes = "green eggs\nand ham\n"
     1108        bytes = b"green eggs\nand ham\n"
    8521109        def iterate(input):
    8531110            input.read(3)
    8541111            return list(input)
    8551112        d = self._renderAndReturnReaderResult(iterate, bytes)
    856         d.addCallback(self.assertEqual, ["en eggs\n", "and ham\n"])
     1113        d.addCallback(self.assertEqual, [b"en eggs\n", b"and ham\n"])
    8571114        return d
    8581115
    8591116
    8601117
    8611118class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
    8621119    """
    863     Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
     1120    Tests for L{_InputStream} when it is wrapped around a
     1121    L{StringIO.StringIO}.
     1122
     1123    This is only available in Python 2.
    8641124    """
    8651125    def getFileType(self):
    866         return StringIO.StringIO
     1126        try:
     1127            from StringIO import StringIO
     1128        except ImportError:
     1129            raise SkipTest("StringIO.StringIO is not available.")
     1130        else:
     1131            return StringIO
    8671132
    8681133
    8691134
    class InputStreamCStringIOTests(InputStreamTestMixin, TestCase): 
    8711136    """
    8721137    Tests for L{_InputStream} when it is wrapped around a
    8731138    L{cStringIO.StringIO}.
     1139
     1140    This is only available in Python 2.
     1141    """
     1142    def getFileType(self):
     1143        try:
     1144            from cStringIO import StringIO
     1145        except ImportError:
     1146            raise SkipTest("cStringIO.StringIO is not available.")
     1147        else:
     1148            return StringIO
     1149
     1150
     1151
     1152class InputStreamBytesIOTests(InputStreamTestMixin, TestCase):
     1153    """
     1154    Tests for L{_InputStream} when it is wrapped around an L{io.BytesIO}.
    8741155    """
    8751156    def getFileType(self):
    876         return cStringIO.StringIO
     1157        from io import BytesIO
     1158        return BytesIO
    8771159
    8781160
    8791161
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9081190        def cbRendered(ignored):
    9091191            self.assertTrue(
    9101192                channel.transport.written.getvalue().startswith(
    911                     'HTTP/1.1 107 Strange message'))
     1193                    b'HTTP/1.1 107 Strange message'))
    9121194        d.addCallback(cbRendered)
    9131195
    9141196        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9181200        return d
    9191201
    9201202
     1203    def test_statusMustBeNativeString(self):
     1204        """
     1205        The response status passed to the I{start_response} callable MUST be a
     1206        native string.
     1207        """
     1208        status = b"200 OK" if _PY3 else u"200 OK"
     1209
     1210        def application(environ, startResponse):
     1211            startResponse(status, [])
     1212            return iter(())
     1213
     1214        request, result = self.prepareRequest(application)
     1215        request.requestReceived()
     1216
     1217        def checkMessage(error):
     1218            if _PY3:
     1219                self.assertEqual(
     1220                    "status must be str, not b'200 OK' (bytes)", str(error))
     1221            else:
     1222                self.assertEqual(
     1223                    "status must be str, not u'200 OK' (unicode)", str(error))
     1224
     1225        return self.assertFailure(result, TypeError).addCallback(checkMessage)
     1226
     1227
    9211228    def _headersTest(self, appHeaders, expectedHeaders):
    9221229        """
    9231230        Verify that if the response headers given by C{appHeaders} are passed
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9391246        d, requestFactory = self.requestFactoryFactory()
    9401247        def cbRendered(ignored):
    9411248            response = channel.transport.written.getvalue()
    942             headers, rest = response.split('\r\n\r\n', 1)
    943             headerLines = headers.split('\r\n')[1:]
     1249            headers, rest = response.split(b'\r\n\r\n', 1)
     1250            headerLines = headers.split(b'\r\n')[1:]
    9441251            headerLines.sort()
    9451252            allExpectedHeaders = expectedHeaders + [
    946                 'Date: Tuesday',
    947                 'Server: ' + version,
    948                 'Transfer-Encoding: chunked']
     1253                b'Date: Tuesday',
     1254                b'Server: ' + version,
     1255                b'Transfer-Encoding: chunked']
    9491256            allExpectedHeaders.sort()
    9501257            self.assertEqual(headerLines, allExpectedHeaders)
    9511258
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9651272        """
    9661273        return self._headersTest(
    9671274            [('foo', 'bar'), ('baz', 'quux')],
    968             ['Baz: quux', 'Foo: bar'])
     1275            [b'Baz: quux', b'Foo: bar'])
     1276
     1277
     1278    def test_headersMustBePlainList(self):
     1279        """
     1280        The headers passed to the I{start_response} callable MUST be in a
     1281        plain list.
     1282        """
     1283        def application(environ, startResponse):
     1284            startResponse("200 OK", (("not", "list"),))
     1285            return iter(())
     1286
     1287        request, result = self.prepareRequest(application)
     1288        request.requestReceived()
     1289
     1290        def checkMessage(error):
     1291            self.assertEqual(
     1292                "headers must be a list, not (('not', 'list'),) (tuple)",
     1293                str(error))
     1294
     1295        return self.assertFailure(result, TypeError).addCallback(checkMessage)
     1296
     1297
     1298    def test_headersMustEachBeTuple(self):
     1299        """
     1300        Each header passed to the I{start_response} callable MUST be in a
     1301        tuple.
     1302        """
     1303        def application(environ, startResponse):
     1304            startResponse("200 OK", [["not", "tuple"]])
     1305            return iter(())
     1306
     1307        request, result = self.prepareRequest(application)
     1308        request.requestReceived()
     1309
     1310        def checkMessage(error):
     1311            self.assertEqual(
     1312                "header must be (str, str) tuple, not ['not', 'tuple'] (list)",
     1313                str(error))
     1314
     1315        return self.assertFailure(result, TypeError).addCallback(checkMessage)
     1316
     1317
     1318    def test_headerKeyMustBeNativeString(self):
     1319        """
     1320        Each header key passed to the I{start_response} callable MUST be at
     1321        native string.
     1322        """
     1323        key = b"key" if _PY3 else u"key"
     1324
     1325        def application(environ, startResponse):
     1326            startResponse("200 OK", [(key, "value")])
     1327            return iter(())
     1328
     1329        request, result = self.prepareRequest(application)
     1330        request.requestReceived()
     1331
     1332        def checkMessage(error):
     1333            self.assertEqual(
     1334                "header must be (str, str) tuple, not (%r, 'value')" % (key,),
     1335                str(error))
     1336
     1337        return self.assertFailure(result, TypeError).addCallback(checkMessage)
     1338
     1339
     1340    def test_headerValueMustBeNativeString(self):
     1341        """
     1342        Each header value passed to the I{start_response} callable MUST be at
     1343        native string.
     1344        """
     1345        value = b"value" if _PY3 else u"value"
     1346
     1347        def application(environ, startResponse):
     1348            startResponse("200 OK", [("key", value)])
     1349            return iter(())
     1350
     1351        request, result = self.prepareRequest(application)
     1352        request.requestReceived()
     1353
     1354        def checkMessage(error):
     1355            self.assertEqual(
     1356                "header must be (str, str) tuple, not ('key', %r)" % (value,),
     1357                str(error))
     1358
     1359        return self.assertFailure(result, TypeError).addCallback(checkMessage)
    9691360
    9701361
    9711362    def test_applicationProvidedContentType(self):
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9761367        """
    9771368        return self._headersTest(
    9781369            [('content-type', 'monkeys are great')],
    979             ['Content-Type: monkeys are great'])
     1370            [b'Content-Type: monkeys are great'])
    9801371
    9811372
    9821373    def test_applicationProvidedServerAndDate(self):
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10061397        def applicationFactory():
    10071398            def application(environ, startResponse):
    10081399                startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
    1009                 yield ''
     1400                yield b''
    10101401                record()
    10111402            return application
    10121403
    10131404        d, requestFactory = self.requestFactoryFactory()
    10141405        def cbRendered(ignored):
    1015             self.assertEqual(intermediateValues, [''])
     1406            self.assertEqual(intermediateValues, [b''])
    10161407        d.addCallback(cbRendered)
    10171408
    10181409        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10381429        def applicationFactory():
    10391430            def application(environ, startResponse):
    10401431                startResponse('200 OK', [('foo', 'bar')])
    1041                 yield ''
     1432                yield b''
    10421433                record()
    1043                 yield 'foo'
     1434                yield b'foo'
    10441435                record()
    10451436            return application
    10461437
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10711462        def applicationFactory():
    10721463            def application(environ, startResponse):
    10731464                startResponse('200 OK', [('content-length', '6')])
    1074                 yield 'foo'
     1465                yield b'foo'
    10751466                record()
    1076                 yield 'bar'
     1467                yield b'bar'
    10771468                record()
    10781469            return application
    10791470
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10811472        def cbRendered(ignored):
    10821473            self.assertEqual(
    10831474                self.getContentFromResponse(intermediateValues[0]),
    1084                 'foo')
     1475                b'foo')
    10851476            self.assertEqual(
    10861477                self.getContentFromResponse(intermediateValues[1]),
    1087                 'foobar')
     1478                b'foobar')
    10881479        d.addCallback(cbRendered)
    10891480
    10901481        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11131504        def cbRendered(ignored):
    11141505            self.assertTrue(
    11151506                channel.transport.written.getvalue().startswith(
    1116                     'HTTP/1.1 200 Bar\r\n'))
     1507                    b'HTTP/1.1 200 Bar\r\n'))
    11171508        d.addCallback(cbRendered)
    11181509
    11191510        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11411532        def cbRendered(ignored):
    11421533            self.assertTrue(
    11431534                channel.transport.written.getvalue().startswith(
    1144                     'HTTP/1.1 100 Foo\r\n'))
     1535                    b'HTTP/1.1 100 Foo\r\n'))
    11451536        d.addCallback(cbRendered)
    11461537
    11471538        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11731564        def applicationFactory():
    11741565            def application(environ, startResponse):
    11751566                startResponse('200 OK', [])
    1176                 yield 'foo'
     1567                yield b'foo'
    11771568                try:
    11781569                    startResponse('500 ERR', [], excInfo)
    11791570                except:
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11841575        def cbRendered(ignored):
    11851576            self.assertTrue(
    11861577                channel.transport.written.getvalue().startswith(
    1187                     'HTTP/1.1 200 OK\r\n'))
     1578                    b'HTTP/1.1 200 OK\r\n'))
    11881579            self.assertEqual(reraised[0][0], excInfo[0])
    11891580            self.assertEqual(reraised[0][1], excInfo[1])
    1190             self.assertEqual(reraised[0][2].tb_next, excInfo[2])
     1581
     1582            # Show that the tracebacks end with the same stack frames.
     1583            tb1 = reraised[0][2].tb_next
     1584            tb2 = excInfo[2]
     1585            self.assertEqual(
     1586                # On Python 2 (str is bytes) we need to move back only one
     1587                # stack frame to skip. On Python 3 we need to move two frames.
     1588                traceback.extract_tb(tb1)[1 if str is bytes else 2],
     1589                traceback.extract_tb(tb2)[0]
     1590            )
    11911591
    11921592        d.addCallback(cbRendered)
    11931593
    class StartResponseTests(WSGITestsMixin, TestCase): 
    12121612        def applicationFactory():
    12131613            def application(environ, startResponse):
    12141614                write = startResponse('100 Foo', [('content-length', '6')])
    1215                 write('foo')
     1615                write(b'foo')
    12161616                record()
    1217                 write('bar')
     1617                write(b'bar')
    12181618                record()
    12191619                return iter(())
    12201620            return application
    class StartResponseTests(WSGITestsMixin, TestCase): 
    12231623        def cbRendered(ignored):
    12241624            self.assertEqual(
    12251625                self.getContentFromResponse(intermediateValues[0]),
    1226                 'foo')
     1626                b'foo')
    12271627            self.assertEqual(
    12281628                self.getContentFromResponse(intermediateValues[1]),
    1229                 'foobar')
     1629                b'foobar')
    12301630        d.addCallback(cbRendered)
    12311631
    12321632        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    12361636        return d
    12371637
    12381638
     1639    def test_writeAcceptsOnlyByteStrings(self):
     1640        """
     1641        The C{write} callable returned from C{start_response} only accepts
     1642        byte strings.
     1643        """
     1644        def application(environ, startResponse):
     1645            write = startResponse("200 OK", [])
     1646            write(u"bogus")
     1647            return iter(())
     1648
     1649        request, result = self.prepareRequest(application)
     1650        request.requestReceived()
     1651
     1652        def checkMessage(error):
     1653            if _PY3:
     1654                self.assertEqual(
     1655                    "write() argument must be bytes, not 'bogus' (str)",
     1656                    str(error))
     1657            else:
     1658                self.assertEqual(
     1659                    "write() argument must be bytes, not u'bogus' (unicode)",
     1660                    str(error))
     1661
     1662        return self.assertFailure(result, TypeError).addCallback(checkMessage)
     1663
     1664
    12391665
    12401666class ApplicationTests(WSGITestsMixin, TestCase):
    12411667    """
    class ApplicationTests(WSGITestsMixin, TestCase): 
    12631689            def __iter__(self):
    12641690                for i in range(3):
    12651691                    if self.open:
    1266                         yield str(i)
     1692                        yield intToBytes(i)
    12671693
    12681694            def close(self):
    12691695                self.open = False
    class ApplicationTests(WSGITestsMixin, TestCase): 
    12801706            self.assertEqual(
    12811707                self.getContentFromResponse(
    12821708                    channel.transport.written.getvalue()),
    1283                 '012')
     1709                b'012')
    12841710            self.assertFalse(result.open)
    12851711        d.addCallback(cbRendered)
    12861712
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13031729            def application(environ, startResponse):
    13041730                def result():
    13051731                    for i in range(3):
    1306                         invoked.append(get_ident())
    1307                         yield str(i)
    1308                 invoked.append(get_ident())
     1732                        invoked.append(getThreadID())
     1733                        yield intToBytes(i)
     1734                invoked.append(getThreadID())
    13091735                startResponse('200 OK', [('content-length', '3')])
    13101736                return result()
    13111737            return application
    13121738
    13131739        d, requestFactory = self.requestFactoryFactory()
    13141740        def cbRendered(ignored):
    1315             self.assertNotIn(get_ident(), invoked)
     1741            self.assertNotIn(getThreadID(), invoked)
    13161742            self.assertEqual(len(set(invoked)), 1)
    13171743        d.addCallback(cbRendered)
    13181744
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13331759
    13341760        class ThreadVerifier(Request):
    13351761            def write(self, bytes):
    1336                 invoked.append(get_ident())
     1762                invoked.append(getThreadID())
    13371763                return Request.write(self, bytes)
    13381764
    13391765        def applicationFactory():
    13401766            def application(environ, startResponse):
    13411767                write = startResponse('200 OK', [])
    1342                 write('foo')
     1768                write(b'foo')
    13431769                return iter(())
    13441770            return application
    13451771
    13461772        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    13471773        def cbRendered(ignored):
    1348             self.assertEqual(set(invoked), set([get_ident()]))
     1774            self.assertEqual(set(invoked), set([getThreadID()]))
    13491775        d.addCallback(cbRendered)
    13501776
    13511777        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13651791
    13661792        class ThreadVerifier(Request):
    13671793            def write(self, bytes):
    1368                 invoked.append(get_ident())
     1794                invoked.append(getThreadID())
    13691795                return Request.write(self, bytes)
    13701796
    13711797        def applicationFactory():
    13721798            def application(environ, startResponse):
    13731799                startResponse('200 OK', [])
    1374                 yield 'foo'
     1800                yield b'foo'
    13751801            return application
    13761802
    13771803        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    13781804        def cbRendered(ignored):
    1379             self.assertEqual(set(invoked), set([get_ident()]))
     1805            self.assertEqual(set(invoked), set([getThreadID()]))
    13801806        d.addCallback(cbRendered)
    13811807
    13821808        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13951821
    13961822        class ThreadVerifier(Request):
    13971823            def setResponseCode(self, code, message):
    1398                 invoked.append(get_ident())
     1824                invoked.append(getThreadID())
    13991825                return Request.setResponseCode(self, code, message)
    14001826
    14011827        def applicationFactory():
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14061832
    14071833        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    14081834        def cbRendered(ignored):
    1409             self.assertEqual(set(invoked), set([get_ident()]))
     1835            self.assertEqual(set(invoked), set([getThreadID()]))
    14101836        d.addCallback(cbRendered)
    14111837
    14121838        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14311857
    14321858        self.badIter = False
    14331859        def appIter():
    1434             yield "foo"
     1860            yield b"foo"
    14351861            self.badIter = True
    14361862            raise Exception("Should not have gotten here")
    14371863
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14661892
    14671893            self.assertTrue(
    14681894                channel.transport.written.getvalue().startswith(
    1469                     'HTTP/1.1 500 Internal Server Error'))
     1895                    b'HTTP/1.1 500 Internal Server Error'))
    14701896        d.addCallback(cbRendered)
    14711897
    14721898        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15171943            self.assertEqual(len(errors), 1)
    15181944
    15191945            response = channel.transport.written.getvalue()
    1520             self.assertTrue(response.startswith('HTTP/1.1 200 OK'))
     1946            self.assertTrue(response.startswith(b'HTTP/1.1 200 OK'))
    15211947            # Chunked transfer-encoding makes this a little messy.
    15221948            self.assertIn(responseContent, response)
    15231949        d.addErrback(ebRendered)
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15411967        logged.
    15421968        """
    15431969        responseContent = (
    1544             'Some bytes, triggering the server to start sending the response')
     1970            b'Some bytes, triggering the server to start sending the response')
    15451971
    15461972        def application(environ, startResponse):
    15471973            startResponse('200 OK', [])
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15561982        raises an exception when called then the connection is still closed and
    15571983        the exception is logged.
    15581984        """
    1559         responseContent = 'foo'
     1985        responseContent = b'foo'
    15601986
    15611987        class Application(object):
    15621988            def __init__(self, environ, startResponse):
  • new file twisted/web/topfiles/7993.feature

    diff --git a/twisted/web/topfiles/7993.feature b/twisted/web/topfiles/7993.feature
    new file mode 100644
    index 0000000..48c9b06
    - +  
     1twisted.web.wsgi has been ported to Python 3.
  • twisted/web/wsgi.py

    diff --git a/twisted/web/wsgi.py b/twisted/web/wsgi.py
    index 0918c4d..62fc330 100644
    a b  
    33
    44"""
    55An implementation of
    6 U{Web Resource Gateway Interface<http://www.python.org/dev/peps/pep-0333/>}.
     6U{Python Web Server Gateway Interface v1.0.1<http://www.python.org/dev/peps/pep-3333/>}.
    77"""
    88
    99__metaclass__ = type
    1010
    1111from sys import exc_info
    1212
    13 from zope.interface import implements
     13from zope.interface import implementer
    1414
     15from twisted.python.compat import reraise
    1516from twisted.python.log import msg, err
    1617from twisted.python.failure import Failure
    1718from twisted.web.resource import IResource
    from twisted.web.server import NOT_DONE_YET 
    1920from twisted.web.http import INTERNAL_SERVER_ERROR
    2021
    2122
     23
     24# PEP-3333 -- which has superseded PEP-333 -- states that, in both Python 2
     25# and Python 3, text strings MUST be represented using the platform's native
     26# string type, limited to characters defined in ISO-8859-1. Byte strings are
     27# used only for values read from wsgi.input, passed to write() or yielded by
     28# the application.
     29#
     30# Put another way:
     31#
     32# - In Python 2, all text strings and binary data are of type str/bytes and
     33#   NEVER of type unicode. Whether the strings contain binary data or
     34#   ISO-8859-1 text depends on context.
     35#
     36# - In Python 3, all text strings are of type str, and all binary data are of
     37#   type bytes. Text MUST always be limited to that which can be encoded as
     38#   ISO-8859-1, U+0000 to U+00FF inclusive.
     39#
     40# The following pair of functions -- _wsgiString() and _wsgiStringToBytes() --
     41# are used to make Twisted's WSGI support compliant with the standard.
     42if str is bytes:
     43    def _wsgiString(string):  # Python 2.
     44        """
     45        Convert C{string} to an ISO-8859-1 byte string, if it is not already.
     46
     47        @type string: C{str}/C{bytes} or C{unicode}
     48        @rtype: C{str}/C{bytes}
     49
     50        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     51        """
     52        if isinstance(string, str):
     53            return string
     54        else:
     55            return string.encode('iso-8859-1')
     56
     57    def _wsgiStringToBytes(string):  # Python 2.
     58        """
     59        Return C{string} as is; a WSGI string is a byte string in Python 2.
     60
     61        @type string: C{str}/C{bytes}
     62        @rtype: C{str}/C{bytes}
     63        """
     64        return string
     65
     66else:
     67    def _wsgiString(string):  # Python 3.
     68        """
     69        Convert C{string} to a WSGI "bytes-as-unicode" string.
     70
     71        If it's a byte string, decode as ISO-8859-1. If it's a Unicode string,
     72        round-trip it to bytes and back using ISO-8859-1 as the encoding.
     73
     74        @type string: C{str} or C{bytes}
     75        @rtype: str
     76
     77        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     78        """
     79        if isinstance(string, str):
     80            return string.encode("iso-8859-1").decode('iso-8859-1')
     81        else:
     82            return string.decode("iso-8859-1")
     83
     84    def _wsgiStringToBytes(string):  # Python 3.
     85        """
     86        Convert C{string} from a WSGI "bytes-as-unicode" string to an
     87        ISO-8859-1 byte string.
     88
     89        @type string: C{str}
     90        @rtype: bytes
     91
     92        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     93        @raise TypeError: If C{string} is not a byte string.
     94        """
     95        return string.encode("iso-8859-1")
     96
     97
     98
    2299class _ErrorStream:
    23100    """
    24101    File-like object instances of which are used as the value for the
    class _ErrorStream: 
    30107    to expose more information in the events it logs, such as the application
    31108    object which generated the message.
    32109    """
    33     def write(self, bytes):
     110
     111    def write(self, data):
    34112        """
    35113        Generate an event for the logging system with the given bytes as the
    36114        message.
    37115
    38116        This is called in a WSGI application thread, not the I/O thread.
     117
     118        @type data: str
     119
     120        @raise TypeError: If C{data} is not a native string.
    39121        """
    40         msg(bytes, system='wsgi', isError=True)
     122        if not isinstance(data, str):
     123            raise TypeError(
     124                "write() argument must be str, not %r (%s)"
     125                % (data, type(data).__name__))
     126        msg(data, system='wsgi', isError=True)
    41127
    42128
    43129    def writelines(self, iovec):
    class _ErrorStream: 
    49135
    50136        @param iovec: A C{list} of C{'\\n'}-terminated C{str} which will be
    51137            logged.
     138
     139        @raise TypeError: If C{iovec} contains any non-native strings.
    52140        """
    53141        self.write(''.join(iovec))
    54142
    class _WSGIResponse: 
    174262        self.request.notifyFinish().addBoth(self._finished)
    175263
    176264        if request.prepath:
    177             scriptName = '/' + '/'.join(request.prepath)
     265            scriptName = b'/' + b'/'.join(request.prepath)
    178266        else:
    179             scriptName = ''
     267            scriptName = b''
    180268
    181269        if request.postpath:
    182             pathInfo = '/' + '/'.join(request.postpath)
     270            pathInfo = b'/' + b'/'.join(request.postpath)
    183271        else:
    184             pathInfo = ''
     272            pathInfo = b''
    185273
    186         parts = request.uri.split('?', 1)
     274        parts = request.uri.split(b'?', 1)
    187275        if len(parts) == 1:
    188             queryString = ''
     276            queryString = b''
    189277        else:
    190278            queryString = parts[1]
    191279
     280        # All keys and values need to be native strings, i.e. of type str in
     281        # *both* Python 2 and Python 3, so says PEP-3333.
    192282        self.environ = {
    193             'REQUEST_METHOD': request.method,
    194             'REMOTE_ADDR': request.getClientIP(),
    195             'SCRIPT_NAME': scriptName,
    196             'PATH_INFO': pathInfo,
    197             'QUERY_STRING': queryString,
    198             'CONTENT_TYPE': request.getHeader('content-type') or '',
    199             'CONTENT_LENGTH': request.getHeader('content-length') or '',
    200             'SERVER_NAME': request.getRequestHostname(),
    201             'SERVER_PORT': str(request.getHost().port),
    202             'SERVER_PROTOCOL': request.clientproto}
    203 
     283            'REQUEST_METHOD': _wsgiString(request.method),
     284            'REMOTE_ADDR': _wsgiString(request.getClientIP()),
     285            'SCRIPT_NAME': _wsgiString(scriptName),
     286            'PATH_INFO': _wsgiString(pathInfo),
     287            'QUERY_STRING': _wsgiString(queryString),
     288            'CONTENT_TYPE': _wsgiString(
     289                request.getHeader(b'content-type') or ''),
     290            'CONTENT_LENGTH': _wsgiString(
     291                request.getHeader(b'content-length') or ''),
     292            'SERVER_NAME': _wsgiString(request.getRequestHostname()),
     293            'SERVER_PORT': _wsgiString(str(request.getHost().port)),
     294            'SERVER_PROTOCOL': _wsgiString(request.clientproto)}
    204295
    205296        # The application object is entirely in control of response headers;
    206297        # disable the default Content-Type value normally provided by
    class _WSGIResponse: 
    208299        self.request.defaultContentType = None
    209300
    210301        for name, values in request.requestHeaders.getAllRawHeaders():
    211             name = 'HTTP_' + name.upper().replace('-', '_')
     302            name = 'HTTP_' + _wsgiString(name).upper().replace('-', '_')
    212303            # It might be preferable for http.HTTPChannel to clear out
    213304            # newlines.
    214             self.environ[name] = ','.join([
    215                     v.replace('\n', ' ') for v in values])
     305            self.environ[name] = ','.join(
     306                _wsgiString(v) for v in values).replace('\n', ' ')
    216307
    217308        self.environ.update({
    218309                'wsgi.version': (1, 0),
    class _WSGIResponse: 
    254345        This will be called in a non-I/O thread.
    255346        """
    256347        if self.started and excInfo is not None:
    257             raise excInfo[0], excInfo[1], excInfo[2]
     348            reraise(excInfo[1], excInfo[2])
     349
     350        # PEP-3333 mandates that status should be a native string.
     351        if not isinstance(status, str):
     352            raise TypeError(
     353                "status must be str, not %r (%s)"
     354                % (status, type(status).__name__))
     355
     356        # PEP-3333 mandates a plain list.
     357        if not isinstance(headers, list):
     358            raise TypeError(
     359                "headers must be a list, not %r (%s)"
     360                % (headers, type(headers).__name__))
     361
     362        # PEP-3333 mandates that each header should be a (str, str) tuple.
     363        for header in headers:
     364            if isinstance(header, tuple):
     365                is_okay = (
     366                    len(header) == 2 and
     367                    isinstance(header[0], str) and
     368                    isinstance(header[1], str)
     369                )
     370                if not is_okay:
     371                    raise TypeError(
     372                        "header must be (str, str) tuple, not %r" % (header, ))
     373            else:
     374                raise TypeError(
     375                    "header must be (str, str) tuple, not %r (%s)"
     376                    % (header, type(header).__name__))
     377
    258378        self.status = status
    259379        self.headers = headers
    260380        return self.write
    261381
    262382
    263     def write(self, bytes):
     383    def write(self, data):
    264384        """
    265385        The WSGI I{write} callable returned by the I{start_response} callable.
    266386        The given bytes will be written to the response body, possibly flushing
    267387        the status and headers first.
    268388
    269389        This will be called in a non-I/O thread.
     390
     391        @raise TypeError: If C{data} is not a byte string.
    270392        """
     393        # Check that `data` is bytes now because we will not get any feedback
     394        # from callFromThread() later on.
     395        if not isinstance(data, bytes):
     396            raise TypeError(
     397                "write() argument must be bytes, not %r (%s)"
     398                % (data, type(data).__name__))
     399
    271400        def wsgiWrite(started):
    272401            if not started:
    273402                self._sendResponseHeaders()
    274             self.request.write(bytes)
     403            self.request.write(data)
     404
     405        # PEP-3333 states:
     406        #
     407        #   The server or gateway must transmit the yielded bytestrings to the
     408        #   client in an unbuffered fashion, completing the transmission of
     409        #   each bytestring before requesting another one.
     410        #
     411        # This write() method is used for the imperative and (indirectly) for
     412        # the more familiar iterable-of-bytestrings WSGI mechanism, but offers
     413        # no back-pressure, and so violates this part of PEP-3333.
     414        #
     415        # PEP-3333 also says that a server may:
     416        #
     417        #   Use a different thread to ensure that the block continues to be
     418        #   transmitted while the application produces the next block.
     419        #
     420        # Which suggests that this is actually compliant with PEP-3333,
     421        # because writes are done in the reactor thread.
     422        #
     423        # However, providing some back-pressure may nevertheless be a Good
     424        # Thing at some point in the future.
    275425        self.reactor.callFromThread(wsgiWrite, self.started)
    276426        self.started = True
    277427
    class _WSGIResponse: 
    287437        """
    288438        code, message = self.status.split(None, 1)
    289439        code = int(code)
    290         self.request.setResponseCode(code, message)
     440        self.request.setResponseCode(code, _wsgiStringToBytes(message))
    291441
    292442        for name, value in self.headers:
    293443            # Don't allow the application to control these required headers.
    294444            if name.lower() not in ('server', 'date'):
    295                 self.request.responseHeaders.addRawHeader(name, value)
     445                self.request.responseHeaders.addRawHeader(
     446                    _wsgiStringToBytes(name), _wsgiStringToBytes(value))
    296447
    297448
    298449    def start(self):
    class _WSGIResponse: 
    341492
    342493
    343494
     495@implementer(IResource)
    344496class WSGIResource:
    345497    """
    346498    An L{IResource} implementation which delegates responsibility for all
    class WSGIResource: 
    354506
    355507    @ivar _application: The WSGI application object.
    356508    """
    357     implements(IResource)
    358509
    359510    # Further resource segments are left up to the WSGI application object to
    360511    # handle.