[Twisted-Python] Transplating a request's transport (SockJS)
![](https://secure.gravatar.com/avatar/415203f2727ceaf56d8f7f5e6d5d508b.jpg?s=120&d=mm&r=g)
Hi, I've been trying to play with SockJS + Twisted. There's an implementation here: https://github.com/Fugiman/sockjs-twisted However, I'm already running a web server, and as the README says sockjs-twisted is *not* intended to be run together with a web server. It doesn't really use any of the mechanics in twisted.web. So, I tried to make it work using Resources anyway: reconstructing the equivalent bytes in render, and replaying them with the SockJSFactory: class SockJSResource(resource.Resource): """ A resource that defers to a SockJS factory. """ isLeaf = True def __init__(self, factory, options=None): self._factory = SockJSFactory(factory, options) def render(self, request): transport, request.transport = request.transport, None protocol = self._factory.buildProtocol(transport.getPeer()) protocol.makeConnection(transport) path = "/".join([""] + request.postpath) lines = ["{0} {1} HTTP/1.1".format(request.method, path)] for name, values in request.requestHeaders.getAllRawHeaders(): lines.append("{0}: {1}".format(name, ",".join(values))) lines += ["", request.content.read()] data = "\r\n".join(lines) protocol.dataReceived(data) return server.NOT_DONE_YET This only kind of works. There's an external sockjs test suite (more like an acceptance test suite): https://github.com/sockjs/sockjs-protocol, that has multiple failing tests. If anyone needs help running these, I'll gladly assist there. In an attempt to begin debugging this, I've found that basically none of the protocol methods get called on that protocol instance I make on the third line of render. This took me a while to figure out because dataReceived *was* being called -- except then I realized I'm calling it myself in that render method :) This leads me to believe I'm essentially just screwing up transplanting this transport entirely. Is that the case? -- cheers lvh
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
On Oct 28, 2012, at 8:17 AM, Laurens Van Houtven <_@lvh.cc> wrote:
This leads me to believe I'm essentially just screwing up transplanting this transport entirely. Is that the case?
Since there's no supported way to transplant a transport (see <http://tm.tl/3204>)... yes. But, even if you were going to rely on undefined behavior and private APIs, I don't see anything in your code sample that changes anything on the transport to point at your new protocol. So I don't see why you would think that it would start calling methods on it :). -g
![](https://secure.gravatar.com/avatar/415203f2727ceaf56d8f7f5e6d5d508b.jpg?s=120&d=mm&r=g)
On Sun, Oct 28, 2012 at 9:17 PM, Glyph <glyph@twistedmatrix.com> wrote:
Since there's no supported way to transplant a transport (see < http://tm.tl/3204>)... yes.
Aha!
For some reason I thought IProtocol.makeConnection did that; I guess it's because the implementation in Protocol sets the `transport` attribute (I thought it was the other way around). I've set transport.protocol = myNewProtocol and now there's one extra passing acceptance test. I'm slowly beginning to wonder if it wouldn't be easier to write it from scratch using t.w.websockets from the get-go...
-- cheers lvh
![](https://secure.gravatar.com/avatar/607cfd4a5b41fe6c886c978128b9c03e.jpg?s=120&d=mm&r=g)
On 28 Oct, 08:40 pm, _@lvh.cc wrote:
In case anyone didn't read #3204: this is, of course, an unsupported use of transports and not guaranteed to work or continue working (currently it will not work if you happen to be using the (preferred) protocol- based SSL implementation). Jean-Paul
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
On Oct 29, 2012, at 8:02 AM, exarkun@twistedmatrix.com wrote:
Why would anyone not have read <http://tm.tl/3204>? Everyone, go read it, right now. (There's a branch, and everything!) -glyph
![](https://secure.gravatar.com/avatar/426d6dbf6554a9b3fca1fd04e6b75f38.jpg?s=120&d=mm&r=g)
On 10/30/2012 06:50 AM, Laurens Van Houtven wrote:
Would it not make more sense to subclass Site and swap out the Channel implementation for one that can be "pointed" somewhere else? It means you have to use that "Site" for any WebSocket enabled site, but it would be clean otherwise. Like so: class WebSockCapableChannel(http.HTTPChannel): def __init__(self): http.HTTPChannel.__init__(self) self.websocket = False def startWebSocket(self, proto): self.websocket = True self.websocket_proto = proto proto.transport = someWrapper(self) proto.connectionMade(...) self.setRawMode() def rawDataReceived(self, data): if self.websocket: self.websocket_proto.dataReceived(data) else: http.HTTPChannel.rawDataReceived(self, data) class WebSocketSite(server.Site): procol = WebSockCapableChannel ...then in your "Resource" do: class MyResource(...): def render(...): request.channel.startWebSocket(someProto())
![](https://secure.gravatar.com/avatar/415203f2727ceaf56d8f7f5e6d5d508b.jpg?s=120&d=mm&r=g)
On Tue, Oct 30, 2012 at 10:04 AM, Phil Mayers <p.mayers@imperial.ac.uk>wrote:
Just to be clear (I'm not sure it matters): SockJS is something that gives a WebSockets API on the client side, but implements that using whatever the best API is available there. It may in fact be implemented using a whole host of things, including XHR streaming and polling, iframes, ...
I'll try that; but isn't render called when an awful lot of parsing has already been done? Query arguments and headers are already read and parsed; sockjs-twisted expects its first line to be "POST /echo/bla/bla/bla HTTP/1.1\r\n" :) -- cheers lvh
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
On Oct 28, 2012, at 8:17 AM, Laurens Van Houtven <_@lvh.cc> wrote:
This leads me to believe I'm essentially just screwing up transplanting this transport entirely. Is that the case?
Since there's no supported way to transplant a transport (see <http://tm.tl/3204>)... yes. But, even if you were going to rely on undefined behavior and private APIs, I don't see anything in your code sample that changes anything on the transport to point at your new protocol. So I don't see why you would think that it would start calling methods on it :). -g
![](https://secure.gravatar.com/avatar/415203f2727ceaf56d8f7f5e6d5d508b.jpg?s=120&d=mm&r=g)
On Sun, Oct 28, 2012 at 9:17 PM, Glyph <glyph@twistedmatrix.com> wrote:
Since there's no supported way to transplant a transport (see < http://tm.tl/3204>)... yes.
Aha!
For some reason I thought IProtocol.makeConnection did that; I guess it's because the implementation in Protocol sets the `transport` attribute (I thought it was the other way around). I've set transport.protocol = myNewProtocol and now there's one extra passing acceptance test. I'm slowly beginning to wonder if it wouldn't be easier to write it from scratch using t.w.websockets from the get-go...
-- cheers lvh
![](https://secure.gravatar.com/avatar/607cfd4a5b41fe6c886c978128b9c03e.jpg?s=120&d=mm&r=g)
On 28 Oct, 08:40 pm, _@lvh.cc wrote:
In case anyone didn't read #3204: this is, of course, an unsupported use of transports and not guaranteed to work or continue working (currently it will not work if you happen to be using the (preferred) protocol- based SSL implementation). Jean-Paul
![](https://secure.gravatar.com/avatar/e1554622707bedd9202884900430b838.jpg?s=120&d=mm&r=g)
On Oct 29, 2012, at 8:02 AM, exarkun@twistedmatrix.com wrote:
Why would anyone not have read <http://tm.tl/3204>? Everyone, go read it, right now. (There's a branch, and everything!) -glyph
![](https://secure.gravatar.com/avatar/426d6dbf6554a9b3fca1fd04e6b75f38.jpg?s=120&d=mm&r=g)
On 10/30/2012 06:50 AM, Laurens Van Houtven wrote:
Would it not make more sense to subclass Site and swap out the Channel implementation for one that can be "pointed" somewhere else? It means you have to use that "Site" for any WebSocket enabled site, but it would be clean otherwise. Like so: class WebSockCapableChannel(http.HTTPChannel): def __init__(self): http.HTTPChannel.__init__(self) self.websocket = False def startWebSocket(self, proto): self.websocket = True self.websocket_proto = proto proto.transport = someWrapper(self) proto.connectionMade(...) self.setRawMode() def rawDataReceived(self, data): if self.websocket: self.websocket_proto.dataReceived(data) else: http.HTTPChannel.rawDataReceived(self, data) class WebSocketSite(server.Site): procol = WebSockCapableChannel ...then in your "Resource" do: class MyResource(...): def render(...): request.channel.startWebSocket(someProto())
![](https://secure.gravatar.com/avatar/415203f2727ceaf56d8f7f5e6d5d508b.jpg?s=120&d=mm&r=g)
On Tue, Oct 30, 2012 at 10:04 AM, Phil Mayers <p.mayers@imperial.ac.uk>wrote:
Just to be clear (I'm not sure it matters): SockJS is something that gives a WebSockets API on the client side, but implements that using whatever the best API is available there. It may in fact be implemented using a whole host of things, including XHR streaming and polling, iframes, ...
I'll try that; but isn't render called when an awful lot of parsing has already been done? Query arguments and headers are already read and parsed; sockjs-twisted expects its first line to be "POST /echo/bla/bla/bla HTTP/1.1\r\n" :) -- cheers lvh
participants (4)
-
exarkun@twistedmatrix.com
-
Glyph
-
Laurens Van Houtven
-
Phil Mayers