# Buggy code
def render(self, request):
    """
    Render the resource. This will takeover the transport underlying
    the request, create a :class:`autobahn.twisted.websocket.WebSocketServerProtocol`
    and let that do any subsequent communication.
    """
    # Create Autobahn WebSocket protocol.
    #
    protocol = self._factory.buildProtocol(request.transport.getPeer())
    if not protocol:
        # If protocol creation fails, we signal "internal server error"
        request.setResponseCode(500)
        return b""

    # Take over the transport from Twisted Web
    #
    transport, request.channel.transport = request.channel.transport, None

    # Connect the transport to our protocol. Once #3204 is fixed, there
    # may be a cleaner way of doing this.
    # http://twistedmatrix.com/trac/ticket/3204
    #
    if isinstance(transport, ProtocolWrapper):
        # i.e. TLS is a wrapping protocol
        transport.wrappedProtocol = protocol
    else:
        transport.protocol = protocol
    protocol.makeConnection(transport)

    # On Twisted 16+, the transport is paused whilst the existing
    # request is served; there won't be any requests after us so
    # we can just resume this ourselves.
    # 17.1 version
    if hasattr(transport, "_networkProducer"):
        transport._networkProducer.resumeProducing()
    # 16.x version
    elif hasattr(transport, "resumeProducing"):
        transport.resumeProducing()

    # We recreate the request and forward the raw data. This is somewhat
    # silly (since Twisted Web already did the HTTP request parsing
    # which we will do a 2nd time), but it's totally non-invasive to our
    # code. Maybe improve this.
    #
    if PY3:
        data = request.method + b" " + request.uri + b" HTTP/1.1\x0d\x0a"
        for h in request.requestHeaders.getAllRawHeaders():
            data += h[0] + b": " + b",".join(h[1]) + b"\x0d\x0a"
        data += b"\x0d\x0a"
        data += request.content.read()

    else:
        data = "%s %s HTTP/1.1\x0d\x0a" % (request.method, request.uri)
        for h in request.requestHeaders.getAllRawHeaders():
            data += "%s: %s\x0d\x0a" % (h[0], ",".join(h[1]))
        data += "\x0d\x0a"
    protocol.dataReceived(data)

    return NOT_DONE_YET


# Traceback (builtins.AttributeError)
Jul 03 14:39:28 <redacted> unbuffer[2114]: Traceback (most recent call last):
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/_http2.py", line 186, in dataReceived
Jul 03 14:39:28 <redacted> unbuffer[2114]:     self._requestEnded(event)
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/_http2.py", line 495, in _requestEnded
Jul 03 14:39:28 <redacted> unbuffer[2114]:     stream.requestComplete()
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/_http2.py", line 992, in requestComplete
Jul 03 14:39:28 <redacted> unbuffer[2114]:     self._request.requestReceived(self.command, self.path, b'HTTP/2')
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/http.py", line 920, in requestReceived
Jul 03 14:39:28 <redacted> unbuffer[2114]:     self.process()
Jul 03 14:39:28 <redacted> unbuffer[2114]: --- <exception caught here> ---
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/server.py", line 199, in process
Jul 03 14:39:28 <redacted> unbuffer[2114]:     self.render(resrc)
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/cpy373_1/lib/python3.7/site-packages/twisted/web/server.py", line 259, in render
Jul 03 14:39:28 <redacted> unbuffer[2114]:     body = resrc.render(self)
Jul 03 14:39:28 <redacted> unbuffer[2114]:   File "/home/ubuntu/scm/crossbario/autobahn-python/autobahn/twisted/resource.py", line 121, in render
Jul 03 14:39:28 <redacted> unbuffer[2114]:     protocol = self._factory.buildProtocol(request.transport.getPeer())
Jul 03 14:39:28 <redacted> unbuffer[2114]: builtins.AttributeError: 'NoneType' object has no attribute 'getPeer'

# Fixed code
def render(self, request):
    """
    Render the resource. This will takeover the transport underlying
    the request, create a :class:`autobahn.twisted.websocket.WebSocketServerProtocol`
    and let that do any subsequent communication.
    """
    # for reasons unknown, the transport is already None when the
    # request is over HTTP2. request.channel.getPeer() is valid at
    # this point however
    if request.channel.transport is None:
        # render an "error, you're doing HTTPS over WSS" webpage
        from autobahn.websocket import protocol

        request.setResponseCode(426, b"Upgrade required")
        # RFC says MUST set upgrade along with 426 code:
        # https://tools.ietf.org/html/rfc7231#section-6.5.15
        request.setHeader(b"Upgrade", b"WebSocket")
        html = protocol._SERVER_STATUS_TEMPLATE % ("", protocol.__version__)
        return html.encode("utf8")

    # Create Autobahn WebSocket protocol.
    #
    protocol = self._factory.buildProtocol(request.transport.getPeer())
    if not protocol:
        # If protocol creation fails, we signal "internal server error"
        request.setResponseCode(500)
        return b""

    # Take over the transport from Twisted Web
    #
    transport, request.channel.transport = request.channel.transport, None

    # Connect the transport to our protocol. Once #3204 is fixed, there
    # may be a cleaner way of doing this.
    # http://twistedmatrix.com/trac/ticket/3204
    #
    if isinstance(transport, ProtocolWrapper):
        # i.e. TLS is a wrapping protocol
        transport.wrappedProtocol = protocol
    else:
        transport.protocol = protocol
    protocol.makeConnection(transport)

    # On Twisted 16+, the transport is paused whilst the existing
    # request is served; there won't be any requests after us so
    # we can just resume this ourselves.
    # 17.1 version
    if hasattr(transport, "_networkProducer"):
        transport._networkProducer.resumeProducing()
    # 16.x version
    elif hasattr(transport, "resumeProducing"):
        transport.resumeProducing()

    # We recreate the request and forward the raw data. This is somewhat
    # silly (since Twisted Web already did the HTTP request parsing
    # which we will do a 2nd time), but it's totally non-invasive to our
    # code. Maybe improve this.
    #
    if PY3:
        data = request.method + b" " + request.uri + b" HTTP/1.1\x0d\x0a"
        for h in request.requestHeaders.getAllRawHeaders():
            data += h[0] + b": " + b",".join(h[1]) + b"\x0d\x0a"
        data += b"\x0d\x0a"
        data += request.content.read()

    else:
        data = "%s %s HTTP/1.1\x0d\x0a" % (request.method, request.uri)
        for h in request.requestHeaders.getAllRawHeaders():
            data += "%s: %s\x0d\x0a" % (h[0], ",".join(h[1]))
        data += "\x0d\x0a"
    protocol.dataReceived(data)

    return NOT_DONE_YET