简体   繁体   中英

How to Upgrade HTTP Request to Websocket (Autobahn & Twisted Web )

To give you an idea of what I am trying to accomplish with Twisted Web and Autobahn websockets: my UI currently sends an initial HTTP GET request with an upgrade to a websocket in the header. Upon reading that in Twisted Web, the connection needs to switch from HTTP to a websocket protocol to pass data back and forth. Note that this websocket upgrade happens on the same port, port 8000 .

Does anyone know how I can implement what I am trying to do? Thank you so much.

EDIT: updated code for working example. You can find it here: Payload from POST Request is Cutoff (Twisted Web)

Here is my code using Twisted Web:

class HttpResource(resource.Resource):
    isLeaf = 1

    def __init__(self):
        self.children = {}
        self.ws_port = None

    print 'resource invoked'
    def render_GET(self, request):

        print 'render invoked'
        if request.getHeader('Sec-WebSocket-Key'):
            # Processing the Key as per RFC 6455
            key = request.getHeader('Sec-WebSocket-Key')
            h = hashlib.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
            request.setHeader('Sec-WebSocket-Accept', base64.b64encode(h.digest()))

            # setting response headers
            request.setHeader('Upgrade', 'websocket')
            request.setHeader('Connection', 'Upgrade')
            request.setResponseCode(101)
            return ''
        else:
            log("Regular HTTP GET request.")
            return "<html><body style='margin: 0; overflow: hidden;'><iframe style='width: 100%; height: 100%; border: none;' src='http://tsa-graphiql.herokuapp.com/'></iframe></body></html>"

    def render_POST(self,request):
        log("POST request")
        request.setResponseCode(200)

    def handle_single_query(self, queryData):
        log("Handle single query data.")
        return


class HttpWsChannel(http.HTTPChannel):

    def dataReceived(self, data):
        log('Data received:\n{}'.format(data))
        if data.startswith('GET'):
            # This will invoke the render method of resource provided
            http.HTTPChannel.dataReceived(self, data)
        if data.startswith('POST'):
            http.HTTPChannel.dataReceived(self, data)
        else:
            """
            Pass binary data to websocket class.
            """
            ws_protocol = self.site.ws_factory.protocol(self.site.ws_factory.connection_subscriptions)
            log(ws_protocol)
            #just echo for now
            # self.transport.write(data)


class HttpFactory(Site):
    """
    Factory which takes care of tracking which protocol
    instances or request instances are responsible for which
    named response channels, so incoming messages can be
    routed appropriately.
    """

    def __init__(self, resource):
        http.HTTPFactory.__init__(self)
        self.resource = resource
        self.ws_factory = WsProtocolFactory("ws://127.0.0.1:8000")
        self.ws_factory.protocol = WsProtocol

    def buildProtocol(self, addr):
        try:
            channel = HttpWsChannel()
            channel.requestFactory = self.requestFactory
            channel.site = self
            return channel
        except Exception as e:
            log("Could not build protocol: {}".format(e))

site = HttpFactory(HttpResource())

if __name__ == '__main__':
    reactor.listenTCP(8000, site)
    reactor.run()

EDIT 7/8/2017: Here is the new code I am trying below. The websocket messages are received successfully via the onMessage method. However the HTTP requests are not working. The current error I am getting on a GET request is:

<html>
  <head><title>404 - No Such Resource</title></head>
  <body>
    <h1>No Such Resource</h1>
    <p>No such child resource.</p>
  </body>
</html>

Python code

from twisted.web.server import (
    Site,
)
from twisted.internet import reactor
from twisted.web.resource import (
    Resource,
)
from autobahn.twisted.websocket import (
    WebSocketServerProtocol,
    WebSocketServerFactory,
)
from autobahn.twisted.resource import (
    WebSocketResource,
)


class WebSocketProtocol(WebSocketServerProtocol):

    def onConnect(self, request):
        print("WebSocket connection request: {}".format(request))

    def onMessage(self, payload, isBinary):
        print("onMessage: {}".format(payload))


if __name__ == '__main__':

    factory = WebSocketServerFactory()
    factory.protocol = WebSocketProtocol
    resource = WebSocketResource(factory)

    root = Resource()
    root.putChild(b"ws", resource)

    site = Site(root)
    reactor.listenTCP(8000, site)

    reactor.run()

Use WebSocketResource to expose a WebSocketServerFactory as part of a Site .

from twisted.web.server import (
    Site,
)
from twisted.web.resource import (
    Resource,
)
from autobahn.twisted.websocket import (
    WebSocketServerProtocol,
    WebSocketServerFactory,
)
from autobahn.twisted.resource import (
    WebSocketResource,
)

class YourAppProtocol(WebSocketServerProtocol):
    def onConnect(self, request):
        ...

    ...

def main():
    factory = WebSocketRendezvousFactory()
    factory.protocol = YourAppProtocol
    resource = WebSocketResource(factory)
    root = Resource()
    root.putChild(b"some-path-segment", resource)
    root.putChild(...)
    site = Site(root)
    reactor.listenTCP(8080, site)
    reactor.run()

The problems with truncated request bodies is probably because your upgrade protocol implementation is buggy. There is no framing applied at the level of dataReceived so you can't expect checks like startswith("GET") to be reliable.

Using WebSocketResource and Site gives you the correct HTTP parsing code from Twisted Web and Autobahn while also allowing you to speak WebSocket to a particular URL and regular HTTP to others.

So after reading a little bit on Google, I found this website that explains how to upgrade the HTTP connection to a websocket connection via Autobahn Twisted: Read and Set request headers via Autobahn Twisted .

The code that I was able to get to work is shown below!

from twisted.web.server import (
    Site,
)
from twisted.internet import reactor
from twisted.web.resource import (
    Resource,
)
from autobahn.twisted.websocket import (
    WebSocketServerProtocol,
    WebSocketServerFactory,
)
from autobahn.twisted.resource import (
    WebSocketResource,
)


class HttpResource(Resource):
    isLeaf = True

    def render_GET(self, request):
    return "<html><body style='margin: 0; overflow: hidden;'><iframe style='width: 100%; height: 100%; border: none;' src='http://tsa-graphiql.herokuapp.com/'></iframe></body></html>"


class WebSocketProtocol(WebSocketServerProtocol):

    def onConnect(self, request):
        custom_header = {}

        if request.headers['sec-websocket-key']:
            custom_header['sec-websocket-protocol'] = 'graphql-ws'
        return (None, custom_header)

    def onMessage(self, payload, isBinary):
        print("onMessage: {}".format(payload))


if __name__ == '__main__':

    factory = WebSocketServerFactory()
    factory.protocol = WebSocketProtocol
    resource = WebSocketResource(factory)

    root = Resource()
    root.putChild("", HttpResource())
    root.putChild(b"ws", ws_resource)

    site = Site(root)
    reactor.listenTCP(8000, site)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM