Commit 85566b3b authored by root's avatar root

Add sockjs for python3

parent 46f2f5eb
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg
# Mac crap
.DS_Store
Christopher Gamble
\ No newline at end of file
========================
SockJS-Twisted Changelog
========================
0.1
===
**0.1.1**
* Converts all text to UTF-8 (prevents crashing websockets in chrome)
* Minor bug fixes
**0.1.0**
* Initial release
Copyright (c) 2012, Christopher Gamble
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Christopher Gamble nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
include AUTHORS
include LICENSE
include README.rst
\ No newline at end of file
==============
SockJS-Twisted
==============
A simple library for adding SockJS support to your twisted application.
Status
======
SockJS-Twisted passes all `SockJS-Protocol v0.3.3 <https://github.com/sockjs/sockjs-protocol>`_ tests,
and all `SockJS-Client qunit <https://github.com/sockjs/sockjs-client>`_ tests. It has been used in
production environments, and should be free of any critical bugs.
Usage
=====
Use ``txsockjs.factory.SockJSFactory`` to wrap your factories. That's it!
.. code-block:: python
from txsockjs.factory import SockJSFactory
reactor.listenTCP(8080, SockJSFactory(factory_to_wrap))
There is nothing else to it, no special setup involved.
Do you want a secure connection? Use ``listenSSL()`` instead of ``listenTCP()``.
Advanced Usage
==============
For those who want to host multiple SockJS services off of one port,
``txsockjs.factory.SockJSMultiFactory`` is designed to handle routing for you.
.. code-block:: python
from txsockjs.factory import SockJSMultiFactory
f = SockJSMultiFactory()
f.addFactory(EchoFactory(), "echo")
f.addFactory(ChatFactory(), "chat")
reactor.listenTCP(8080, f)
http://localhost:8080/echo and http://localhost:8080/chat will give you access
to your EchoFactory and ChatFactory.
Integration With Websites
=========================
It is possible to offer static resources, dynamic pages, and SockJS endpoints off of
a single port by using ``txsockjs.factory.SockJSResource``.
.. code-block:: python
from txsockjs.factory import SockJSResource
root = resource.Resource()
root.putChild("echo", SockJSResource(EchoFactory()))
root.putChild("chat", SockJSResource(ChatFactory()))
site = server.Site(root)
reactor.listenTCP(8080, site)
Multiplexing [Experimental]
===========================
SockJS-Twisted also has built-in support for multiplexing. See the
`Websocket-Multiplex <https://github.com/sockjs/websocket-multiplex>`_ library
for how to integrate multiplexing client side.
.. code-block:: python
from txsockjs.multiplex import SockJSMultiplexResource
multiplex = SockJSMultiplexResource()
multiplex.addFactory("echo", EchoFactory())
multiplex.addFactory("chat", ChatFactory())
root = resource.Resource()
root.putChild("multiplex", multiplex)
site = server.Site(root)
reactor.listenTCP(8080, site)
If you want PubSub functionality, just use ``txsockjs.multiplex.SockJSPubSubResource`` instead!
Options
=======
A dictionary of options can be passed into the factory to control SockJS behavior.
.. code-block:: python
options = {
'websocket': True,
'cookie_needed': False,
'heartbeat': 25,
'timeout': 5,
'streaming_limit': 128 * 1024,
'encoding': 'cp1252', # Latin1
'sockjs_url': 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js'
}
SockJSFactory(factory_to_wrap, options)
SockJSMultiFactory().addFactory(factory_to_wrap, prefix, options)
SockJSResource(factory_to_wrap, options)
SockJSMultiplexResource(options)
SockJSPubSubResource(options)
websocket :
whether websockets are supported as a protocol. Useful for proxies or load balancers that don't support websockets.
cookie_needed :
whether the JSESSIONID cookie is set. Results in less performant protocols being used, so don't require them unless your load balancer requires it.
heartbeat :
how often a heartbeat message is sent to keep the connection open. Do not increase this unless you know what you are doing.
timeout :
maximum delay between connections before the underlying protocol is disconnected
streaming_limit :
how many bytes can be sent over a streaming protocol before it is cycled. Allows browser-side garbage collection to lower RAM usage.
encoding :
All messages to and from txsockjs should be valid UTF-8. In the event that a message received by txsockjs is not UTF-8, fall back to this encoding.
sockjs_url :
The url of the SockJS library to use in iframes. By default this is served over HTTPS and therefore shouldn't need changing.
License
=======
SockJS-Twisted is (c) 2012 Christopher Gamble and is made available under the BSD license.
from setuptools import setup
import txsockjs
import os
setup(
author="Christopher Gamble",
author_email="chris@chrisgamble.net",
name="txsockjs",
version=txsockjs.__version__,
description="Twisted SockJS wrapper",
long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
url="http://github.com/Fugiman/sockjs-twisted",
license='BSD License',
platforms=['OS Independent'],
packages=["txsockjs","txsockjs.protocols"],
install_requires=[
"Twisted",
],
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: Twisted",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Topic :: Internet",
],
)
\ No newline at end of file
<!doctype html>
<html>
<head>
<title>IRC Client Test</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<style>
#container {
width: 800px;
margin: 40px auto;
padding: 0;
}
#irc-holder {
height: 600px;
width: 800px;
overflow: auto;
border: 2px solid black;
}
#irc { width: 100%; border-collapse: collapse; }
#irc tr { border-bottom: 1px solid gray; }
#chatter input[type="text"] { width: 700px; }
#chatter input[type="submit"] { width: 90px; }
#protocol { font-size: 1.5em; margin: auto; width: 300px; display: block; }
</style>
</head>
<body>
<div id="container">
<div id="irc-holder">
<table id="irc"></table>
</div>
<form method="GET" id="chatter">
<input type="text" />
<input type="submit" value="Send" />
<br />
<select id="protocol">
<option value="websocket" selected>Websocket</option>
<option value="xdr-streaming">XDR Streaming</option>
<option value="xhr-streaming">XHR Streaming</option>
<option value="iframe-eventsource">Eventsource</option>
<option value="iframe-htmlfile">HTMLFile</option>
<option value="xdr-polling">XDR Polling</option>
<option value="xhr-polling">XHR Polling</option>
<option value="iframe-xhr-polling">XHR Polling (IFrame)</option>
<option value="jsonp-polling">JSONP Polling</option>
</select>
</form>
</div>
<script>
$(document).keypress(function(e) { return e.which !== 0; });
var url = 'http://127.0.0.1:6672';
var conn = false;
function connect() {
if(conn)
conn.close()
conn = false
var protocol = $('#protocol').val();
conn = new SockJS(url,null,{debug:true,devel:true,protocols_whitelist:[protocol]});
var log = function(msg) { $('#irc').append($('<tr>').html($('<td>').html(msg))); };
conn.onopen = function(evt) { log('<i>Connected via '+$('#protocol').val()+'</i>'); };
conn.onclose = function(evt) { log('<i>Disconnected</i>'); };
conn.onmessage = function(evt) { log($('<div>').text(evt.data).html()); };
}
$('#chatter').submit(function() {
var txt = $(this).find('input[type="text"]');
var m = txt.val();
conn.send(m+"\r\n");
txt.val('');
return false;
});
$('#protocol').change(connect);
connect();
</script>
</body>
</html>
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
from txsockjs.factory import SockJSFactory
class Chat(LineReceiver):
def __init__(self, users):
self.users = users
self.name = None
self.state = "GETNAME"
def connectionMade(self):
print("IRC Connection Made!")
self.sendLine("What's your name?")
def connectionLost(self, reason):
print("IRC Connection Lost!")
if self.users.has_key(self.name):
del self.users[self.name]
def lineReceived(self, line):
if self.state == "GETNAME":
self.handle_GETNAME(line)
else:
self.handle_CHAT(line)
def handle_GETNAME(self, name):
if self.users.has_key(name):
self.sendLine("Name taken, please choose another.")
return
print("IRC User chose name - %s!" % name)
self.sendLine("Welcome, %s!" % (name,))
self.name = name
self.users[name] = self
self.state = "CHAT"
def handle_CHAT(self, message):
message = "<%s> %s" % (self.name, message)
print(message)
for name, protocol in self.users.iteritems():
protocol.sendLine(message)
class ChatFactory(Factory):
def __init__(self):
self.users = {} # maps user names to Chat instances
def buildProtocol(self, addr):
return Chat(self.users)
f = ChatFactory()
s = SockJSFactory(f)
reactor.listenTCP(6667, f)
reactor.listenTCP(6672, s)
reactor.run()
\ No newline at end of file
<!doctype html>
<html><head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script src="http://cdn.sockjs.org/websocket-multiplex-0.1.js"></script>
<style>
.box {
width: 300px;
float: left;
margin: 0 20px 0 20px;
}
.box div, .box input {
border: 1px solid;
-moz-border-radius: 4px;
border-radius: 4px;
width: 100%;
padding: 0px;
margin: 5px;
}
.box div {
border-color: grey;
height: 300px;
overflow: auto;
}
.box input {
height: 30px;
}
h1 {
margin-left: 75px;
}
body {
background-color: #F0F0F0;
font-family: "Arial";
}
</style>
</head><body lang="en">
<h1>SockJS Multiplex example</h1>
<div id="first" class="box">
<div></div>
<form><input autocomplete="off" value="Type here..."></input></form>
</div>
<div id="second" class="box">
<div></div>
<form><input autocomplete="off"></input></form>
</div>
<div id="third" class="box">
<div></div>
<form><input autocomplete="off"></input></form>
</div>
<script>
// Pipe - convenience wrapper to present data received from an
// object supporting WebSocket API in an html element. And the other
// direction: data typed into an input box shall be sent back.
var pipe = function(ws, el_name) {
var div = $(el_name + ' div');
var inp = $(el_name + ' input');
var form = $(el_name + ' form');
var print = function(m, p) {
p = (p === undefined) ? '' : JSON.stringify(p);
div.append($("<code>").text(m + ' ' + p));
div.append($("<br>"));
div.scrollTop(div.scrollTop() + 10000);
};
ws.onopen = function() {print('[*] open', ws.protocol);};
ws.onmessage = function(e) {print('[.] message', e.data);};
ws.onclose = function() {print('[*] close');};
form.submit(function() {
print('[ ] sending', inp.val());
ws.send(inp.val());
inp.val('');
return false;
});
};
var sockjs_url = 'http://127.0.0.1:8081/multiplex';
var sockjs = new SockJS(sockjs_url);
var multiplexer = new WebSocketMultiplex(sockjs);
var ann = multiplexer.channel('ann');
var bob = multiplexer.channel('bob');
var carl = multiplexer.channel('carl');
pipe(ann, '#first');
pipe(bob, '#second');
pipe(carl, '#third');
$('#first input').focus();
</script>
</body></html>
\ No newline at end of file
from twisted.internet import reactor, protocol
from twisted.web import server, resource
from txsockjs.multiplex import SockJSMultiplexResource
class AnnP(protocol.Protocol):
def connectionMade(self):
self.transport.write("Ann says hi!")
def dataReceived(self, data):
self.transport.write("Ann nods: " + data)
class BobP(protocol.Protocol):
def connectionMade(self):
self.transport.write("Bob doesn't agree.")
def dataReceived(self, data):
self.transport.write("Bob says no to: " + data)
class CarlP(protocol.Protocol):
def connectionMade(self):
self.transport.write("Carl says goodbye!")
self.transport.loseConnection()
class AnnF(protocol.Factory):
protocol = AnnP
class BobF(protocol.Factory):
protocol = BobP
class CarlF(protocol.Factory):
protocol = CarlP
multiplex = SockJSMultiplexResource()
multiplex.addFactory("ann", AnnF())
multiplex.addFactory("bob", BobF())
multiplex.addFactory("carl", CarlF())
root = resource.Resource()
root.putChild("multiplex", multiplex)
site = server.Site(root)
reactor.listenTCP(8081, site)
reactor.run()
\ No newline at end of file
from twisted.internet import reactor, protocol
from twisted.web import server, resource
from txsockjs.factory import SockJSMultiFactory, SockJSResource
class Echo(protocol.Protocol):
def dataReceived(self, data):
#print ">>> %s" % data
self.transport.write(data)
class EchoFactory(protocol.Factory):
def buildProtocol(self, addr):
return Echo()
class Close(protocol.Protocol):
def connectionMade(self):
self.transport.loseConnection()
class CloseFactory(protocol.Factory):
def buildProtocol(self, addr):
return Close()
echo = EchoFactory()
close = CloseFactory()
s = SockJSMultiFactory()
s.addFactory(echo, "echo", {'streaming_limit': 4 * 1024})
s.addFactory(close, "close", {'streaming_limit': 4 * 1024})
s.addFactory(echo, "disabled_websocket_echo", {'websocket': False, 'streaming_limit': 4 * 1024})
s.addFactory(echo, "cookie_needed_echo", {'cookie_needed': True, 'streaming_limit': 4 * 1024})
root = resource.Resource()
root.putChild("echo", SockJSResource(echo, {'streaming_limit': 4 * 1024}))
root.putChild("close", SockJSResource(close, {'streaming_limit': 4 * 1024}))
root.putChild("disabled_websocket_echo", SockJSResource(echo, {'websocket': False, 'streaming_limit': 4 * 1024}))
root.putChild("cookie_needed_echo", SockJSResource(echo, {'cookie_needed': True, 'streaming_limit': 4 * 1024}))
site = server.Site(root)
reactor.listenTCP(8081, s)
reactor.listenTCP(8082, site)
reactor.run()
\ No newline at end of file
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.web import resource, server
from txsockjs.protocols.base import Stub
from txsockjs.protocols.eventsource import EventSource
from txsockjs.protocols.htmlfile import HTMLFile
from txsockjs.protocols.jsonp import JSONP, JSONPSend
from txsockjs.protocols.static import Info, IFrame
from txsockjs.protocols.websocket import RawWebSocket, WebSocket
from txsockjs.protocols.xhr import XHR, XHRSend, XHRStream
class SockJSFactory(server.Site):
def __init__(self, factory, options = None):
server.Site.__init__(self, SockJSResource(factory, options))
class SockJSMultiFactory(server.Site):
def __init__(self):
server.Site.__init__(self, resource.Resource())
def addFactory(self, factory, prefix, options = None):
self.resource.putChild(prefix, SockJSResource(factory, options))
class SockJSResource(resource.Resource):
def __init__(self, factory, options = None):
resource.Resource.__init__(self)
self._factory = factory
self._sessions = {}
self._options = {
'websocket': True,
'cookie_needed': False,
'heartbeat': 25,
'timeout': 5,
'streaming_limit': 128 * 1024,
'encoding': 'cp1252', #Latin1
'sockjs_url': 'https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.js'
}
if options is not None:
self._options.update(options)
# Just in case somebody wants to mess with these
self._methods = {
'xhr': XHR,
'xhr_send': XHRSend,
'xhr_streaming': XHRStream,
'eventsource': EventSource,
'htmlfile': HTMLFile,
'jsonp': JSONP,
'jsonp_send': JSONPSend,
}
self._writeMethods = ('xhr_send','jsonp_send')
# Static Resources
self.putChild("info",Info())
self.putChild("iframe.html",IFrame())
self.putChild("websocket",RawWebSocket())
# Since it's constant, we can declare the websocket handler up here
self._websocket = WebSocket()
self._websocket.parent = self
def getChild(self, name, request):
# Check if it is the greeting url
if not name and not request.postpath:
return self
# Hacks to resove the iframe even when people are dumb
if len(name) > 10 and name[:6] == "iframe" and name[-5:] == ".html":
return self.children["iframe.html"]
# Sessions must have 3 parts, name is already the first. Also, no periods in the loadbalancer
if len(request.postpath) != 2 or "." in name or not name:
return resource.NoResource("No such child resource.")
# Extract session & request type. Discard load balancer
session, name = request.postpath
# No periods in the session
if "." in session or not session:
return resource.NoResource("No such child resource.")
# Websockets are a special case
if name == "websocket":
return self._websocket
# Reject invalid methods
if name not in self._methods:
return resource.NoResource("No such child resource.")
# Reject writes to invalid sessions, unless just checking options
if name in self._writeMethods and session not in self._sessions and request.method != "OPTIONS":
return resource.NoResource("No such child resource.")
# Generate session if doesn't exist, unless just checking options
if session not in self._sessions and request.method != "OPTIONS":
self._sessions[session] = Stub(self, session)
# Delegate request to appropriate handler
return self._methods[name](self, self._sessions[session] if request.method != "OPTIONS" else None)
def putChild(self, path, child):
child.parent = self
resource.Resource.putChild(self, path, child)
def setBaseHeaders(self, request, cookie=True):
origin = request.getHeader("Origin")
headers = request.getHeader('Access-Control-Request-Headers')
if origin is None or origin == "null":
origin = "*"
request.setHeader('access-control-allow-origin', origin)
request.setHeader('access-control-allow-credentials', 'true')
request.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
if headers is not None:
request.setHeader('Access-Control-Allow-Headers', headers)
if self._options["cookie_needed"] and cookie:
cookie = request.getCookie("JSESSIONID") if request.getCookie("JSESSIONID") else "dummy"
request.addCookie("JSESSIONID", cookie, path="/")
def render_GET(self, request):
self.setBaseHeaders(request,False)
request.setHeader('content-type', 'text/plain; charset=UTF-8')
return "Welcome to SockJS!\n"
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.internet.protocol import Protocol, Factory
from twisted.protocols.policies import ProtocolWrapper
from txsockjs.factory import SockJSResource
from txsockjs import utils
class BroadcastProtocol(Protocol):
def dataReceived(self, data):
self.transport.broadcast(data)
class BroadcastFactory(Factory):
protocol = BroadcastProtocol
class MultiplexProxy(ProtocolWrapper):
def __init__(self, factory, wrappedProtocol, transport, topic):
ProtocolWrapper.__init__(self, factory, wrappedProtocol)
self.topic = topic
self.makeConnection(transport)
def write(self, data):
self.transport.transport.write(",".join(["msg", self.topic, data]))
def writeSequence(self, data):
for d in data:
self.write(d)
def broadcast(self, data):
self.factory.broadcast(self.topic, data)
def loseConnection(self):
self.transport.transport.write(",".join(["uns", self.topic]))
class MultiplexProtocol(Protocol):
def connectionMade(self):
self.factory._connections[self] = {}
def dataReceived(self, message):
type, chaff, topic = message.partition(",")
if "," in topic:
topic, chaff, payload = topic.partition(",")
if type == "sub":
self.factory.subscribe(self, topic)
elif type == "msg":
self.factory.handleMessage(self, topic, payload)
elif type == "uns":
self.factory.unsubscribe(self, topic)
def connectionLost(self, reason=None):
for conn in list(self.factory._connections[self].values()):
conn.connectionLost(reason)
del self.factory._connections[self]
class MultiplexFactory(Factory):
protocol = MultiplexProtocol
def __init__(self, resource):
self._resource = resource
self._topics = {}
self._connections = {}
def addFactory(self, name, factory):
self._topics[name] = factory
def broadcast(self, name, message):
targets = []
message = ",".join(["msg", name, message])
for p, topics in list(self._connections.items()):
if name in topics:
targets.append(p)
utils.broadcast(message, targets)
def removeFactory(self, name, factory):
del self._topics[name]
def subscribe(self, p, name):
if name not in self._topics:
return
self._connections[p][name] = MultiplexProxy(self, self._topics[name].buildProtocol(p.transport.getPeer()), p, name)
def handleMessage(self, p, name, message):
if p not in self._connections:
return
if name not in self._connections[p]:
return
self._connections[p][name].dataReceived(message)
def unsubscribe(self, p, name):
if p not in self._connections:
return
if name not in self._connections[p]:
return
self._connections[p][name].connectionLost(None)
del self._connections[p][name]
def registerProtocol(self, p):
pass
def unregisterProtocol(self, p):
pass
class PubSubFactory(MultiplexFactory):
broadcastFactory = BroadcastFactory()
def subscribe(self, p, name):
if name not in self._topics:
self._topics[name] = self.broadcastFactory
MultiplexFactory.subscribe(self, p, name)
class SockJSMultiplexResource(SockJSResource):
def __init__(self, options=None):
SockJSResource.__init__(self, MultiplexFactory(self), options)
def addFactory(self, name, factory):
return self._factory.addFactory(name, factory)
def broadcast(self, name, message):
return self._factory.broadcast(name, message)
def removeFactory(self, name):
return self._factory.removeFactory(name)
class SockJSPubSubResource(SockJSMultiplexResource):
def __init__(self, options=None):
SockJSResource.__init__(self, PubSubFactory(self), options)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.internet.protocol import Protocol, Factory
from twisted.protocols.policies import ProtocolWrapper
from txsockjs.factory import SockJSResource
from txsockjs import utils
class BroadcastProtocol(Protocol):
def dataReceived(self, data):
self.transport.broadcast(data)
class BroadcastFactory(Factory):
protocol = BroadcastProtocol
class MultiplexProxy(ProtocolWrapper):
def __init__(self, factory, wrappedProtocol, transport, topic):
ProtocolWrapper.__init__(self, factory, wrappedProtocol)
self.topic = topic
self.makeConnection(transport)
def write(self, data):
self.transport.transport.write(",".join(["msg", self.topic, data]))
def writeSequence(self, data):
for d in data:
self.write(d)
def broadcast(self, data):
self.factory.broadcast(self.topic, data)
def loseConnection(self):
self.transport.transport.write(",".join(["uns", self.topic]))
class MultiplexProtocol(Protocol):
def connectionMade(self):
self.factory._connections[self] = {}
def dataReceived(self, message):
type, chaff, topic = message.partition(",")
if "," in topic:
topic, chaff, payload = topic.partition(",")
if type == "sub":
self.factory.subscribe(self, topic)
elif type == "msg":
self.factory.handleMessage(self, topic, payload)
elif type == "uns":
self.factory.unsubscribe(self, topic)
def connectionLost(self, reason=None):
for conn in self.factory._connections[self].values():
conn.connectionLost(reason)
del self.factory._connections[self]
class MultiplexFactory(Factory):
protocol = MultiplexProtocol
def __init__(self, resource):
self._resource = resource
self._topics = {}
self._connections = {}
def addFactory(self, name, factory):
self._topics[name] = factory
def broadcast(self, name, message):
targets = []
message = ",".join(["msg", name, message])
for p, topics in self._connections.items():
if name in topics:
targets.append(p)
utils.broadcast(message, targets)
def removeFactory(self, name, factory):
del self._topics[name]
def subscribe(self, p, name):
if name not in self._topics:
return
self._connections[p][name] = MultiplexProxy(self, self._topics[name].buildProtocol(p.transport.getPeer()), p, name)
def handleMessage(self, p, name, message):
if p not in self._connections:
return
if name not in self._connections[p]:
return
self._connections[p][name].dataReceived(message)
def unsubscribe(self, p, name):
if p not in self._connections:
return
if name not in self._connections[p]:
return
self._connections[p][name].connectionLost(None)
del self._connections[p][name]
def registerProtocol(self, p):
pass
def unregisterProtocol(self, p):
pass
class PubSubFactory(MultiplexFactory):
broadcastFactory = BroadcastFactory()
def subscribe(self, p, name):
if name not in self._topics:
self._topics[name] = self.broadcastFactory
MultiplexFactory.subscribe(self, p, name)
class SockJSMultiplexResource(SockJSResource):
def __init__(self, options=None):
SockJSResource.__init__(self, MultiplexFactory(self), options)
def addFactory(self, name, factory):
return self._factory.addFactory(name, factory)
def broadcast(self, name, message):
return self._factory.broadcast(name, message)
def removeFactory(self, name):
return self._factory.removeFactory(name)
class SockJSPubSubResource(SockJSMultiplexResource):
def __init__(self, options=None):
SockJSResource.__init__(self, PubSubFactory(self), options)
This diff is collapsed.
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from zope.interface import directlyProvides, providedBy
from twisted.internet import reactor, protocol
from twisted.web import resource, server, http
from twisted.protocols.policies import ProtocolWrapper
from txsockjs.utils import normalize
import json
class StubResource(resource.Resource, ProtocolWrapper):
isLeaf = True
def __init__(self, parent, session):
resource.Resource.__init__(self)
ProtocolWrapper.__init__(self, None, session)
self.parent = parent
self.session = session
self.putChild("", self)
def render_OPTIONS(self, request):
method = "POST" if getattr(self, "render_POST", None) is not None else "GET"
request.setResponseCode(http.NO_CONTENT)
self.parent.setBaseHeaders(request,False)
request.setHeader('Cache-Control', 'public, max-age=31536000')
request.setHeader('access-control-max-age', '31536000')
request.setHeader('Expires', 'Fri, 01 Jan 2500 00:00:00 GMT') #Get a new library by then
request.setHeader('Access-Control-Allow-Methods', 'OPTIONS, {}'.format(method)) # Hardcoding this may be bad?
return ""
def connect(self, request):
if self.session.attached:
return 'c[2010,"Another connection still open"]\n'
self.request = request
directlyProvides(self, providedBy(request.transport))
protocol.Protocol.makeConnection(self, request.transport)
self.session.makeConnection(self)
request.notifyFinish().addErrback(self.connectionLost)
return server.NOT_DONE_YET
def disconnect(self):
self.request.finish()
self.session.transportLeft()
def loseConnection(self):
self.request.finish()
self.session.transportLeft()
def connectionLost(self, reason=None):
self.wrappedProtocol.connectionLost(reason)
class Stub(ProtocolWrapper):
def __init__(self, parent, session):
self.parent = parent
self.session = session
self.pending = []
self.buffer = []
self.connecting = True
self.disconnecting = False
self.attached = False
self.transport = None # Upstream (SockJS)
self.protocol = None # Downstream (Wrapped Factory)
self.peer = None
self.host = None
self.timeout = reactor.callLater(self.parent._options['timeout'], self.disconnect)
#reactor.callLater(self.parent._options['heartbeat'], self.heartbeat)
def makeConnection(self, transport):
directlyProvides(self, providedBy(transport))
protocol.Protocol.makeConnection(self, transport)
self.attached = True
self.peer = self.transport.getPeer()
self.host = self.transport.getHost()
if self.timeout.active():
self.timeout.cancel()
if self.protocol is None:
self.protocol = self.parent._factory.buildProtocol(self.transport.getPeer())
self.protocol.makeConnection(self)
self.sendData()
def loseConnection(self):
self.disconnecting = True
self.sendData()
def connectionLost(self, reason=None):
if self.attached:
self.disconnecting = True
self.transport = None
self.attached = False
self.disconnect()
def heartbeat(self):
self.pending.append('h')
reactor.callLater(self.parent._options['heartbeat'], self.heartbeat)
def disconnect(self):
if self.protocol:
self.protocol.connectionLost(None)
del self.parent._sessions[self.session]
def transportLeft(self):
self.transport = None
self.attached = False
self.timeout = reactor.callLater(self.parent._options['timeout'], self.disconnect)
def write(self, data):
data = normalize(data, self.parent._options['encoding'])
self.buffer.append(data)
self.sendData()
def writeSequence(self, data):
for p in data:
p = normalize(p, self.parent._options['encoding'])
self.buffer.extend(data)
self.sendData()
def writeRaw(self, data):
self.flushData()
self.pending.append(data)
self.sendData()
def sendData(self):
if self.transport:
if self.connecting:
self.transport.write('o')
self.connecting = False
self.sendData()
elif self.disconnecting:
self.transport.write('c[3000,"Go away!"]')
if self.transport:
self.transport.loseConnection()
else:
self.flushData()
if self.pending:
data = list(self.pending)
self.pending = []
self.transport.writeSequence(data)
def flushData(self):
if self.buffer:
data = 'a{}'.format(json.dumps(self.buffer, separators=(',',':')))
self.buffer = []
self.pending.append(data)
def requeue(self, data):
data.extend(self.pending)
self.pending = data
def dataReceived(self, data):
if self.timeout.active():
self.timeout.reset(5)
if data == '':
return "Payload expected."
try:
packets = json.loads(data)
for p in packets:
p = normalize(p, self.parent._options['encoding'])
if self.protocol:
self.protocol.dataReceived(p)
return None
except ValueError:
return "Broken JSON encoding."
def getPeer(self):
return self.peer
def getHost(self):
return self.host
def registerProducer(self, producer, streaming):
if self.transport:
self.transport.registerProducer(producer, streaming)
def unregisterProducer(self):
if self.transport:
self.transport.unregisterProducer()
def stopConsuming(self):
if self.transport:
self.transport.stopConsuming()
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from txsockjs.protocols.base import StubResource
class EventSource(StubResource):
sent = 0
done = False
def render_GET(self, request):
self.parent.setBaseHeaders(request)
request.setHeader('content-type', 'text/event-stream; charset=UTF-8')
request.write("\r\n")
return self.connect(request)
def write(self, data):
if self.done:
self.session.requeue([data])
return
packet = "data: {}\r\n\r\n".format(data)
self.sent += len(packet)
self.request.write(packet)
if self.sent > self.parent._options['streaming_limit']:
self.done = True
self.disconnect()
def writeSequence(self, data):
for d in data:
self.write(d)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.web import http
from txsockjs.protocols.base import StubResource
class HTMLFile(StubResource):
sent = 0
done = False
def render_GET(self, request):
self.parent.setBaseHeaders(request)
callback = request.args.get('c',[None])[0]
if callback is None:
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
return '"callback" parameter required'
request.setHeader('content-type', 'text/html; charset=UTF-8')
request.write(r'''
<!doctype html>
<html><head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head><body><h2>Don't panic!</h2>
<script>
document.domain = document.domain;
var c = parent.{};
c.start();
function p(d) {{c.message(d);}};
window.onload = function() {{c.stop();}};
</script>{}
'''.format(callback, ' '*1024))
return self.connect(request)
def write(self, data):
if self.done:
self.session.requeue([data])
return
packet = "<script>\np(\"{}\");\n</script>\r\n".format(data.replace('\\','\\\\').replace('"','\\"'))
self.sent += len(packet)
self.request.write(packet)
if self.sent > self.parent._options['streaming_limit']:
self.done = True
self.disconnect()
def writeSequence(self, data):
for d in data:
self.write(d)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.web import http
from txsockjs.protocols.base import StubResource
class JSONP(StubResource):
written = False
def render_GET(self, request):
self.parent.setBaseHeaders(request)
self.callback = request.args.get('c',[None])[0]
if self.callback is None:
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
return '"callback" parameter required'
request.setHeader('content-type', 'application/javascript; charset=UTF-8')
return self.connect(request)
def write(self, data):
if self.written:
self.session.requeue([data])
return
self.written = True
self.request.write("{}(\"{}\");\r\n".format(self.callback, data.replace('\\','\\\\').replace('"','\\"')))
self.disconnect()
def writeSequence(self, data):
self.write(data.pop(0))
self.session.requeue(data)
class JSONPSend(StubResource):
def render_POST(self, request):
self.parent.setBaseHeaders(request)
request.setHeader('content-type', 'text/plain; charset=UTF-8')
urlencoded = request.getHeader("Content-Type") == 'application/x-www-form-urlencoded'
data = request.args.get('d', [''])[0] if urlencoded else request.content.read()
ret = self.session.dataReceived(data)
if not ret:
return "ok"
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
return "{}\r\n".format(ret)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
import json, random
from twisted.web import resource, http
class Info(resource.Resource):
def render_GET(self, request):
self.parent.setBaseHeaders(request,False)
request.setHeader('content-type', 'application/json; charset=UTF-8')
data = {
'websocket': self.parent._options['websocket'],
'cookie_needed': self.parent._options['cookie_needed'],
'origins': ['*:*'],
'entropy': random.randint(0,2**32-1)
}
return json.dumps(data)
def render_OPTIONS(self, request):
request.setResponseCode(http.NO_CONTENT)
self.parent.setBaseHeaders(request,False)
request.setHeader('Cache-Control', 'public, max-age=31536000')
request.setHeader('access-control-max-age', '31536000')
request.setHeader('Expires', 'Fri, 01 Jan 2500 00:00:00 GMT') #Get a new library by then
request.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET') # Hardcoding this may be bad?
return ""
class IFrame(resource.Resource):
etag = '00000000-0000-0000-0000-000000000000'
def render_GET(self, request):
self.parent.setBaseHeaders(request,False)
if request.setETag(self.etag):
request.setResponseCode(http.NOT_MODIFIED)
return ""
request.setHeader('content-type', 'text/html; charset=UTF-8')
request.setHeader('Cache-Control', 'public, max-age=31536000')
request.setHeader('access-control-max-age', '31536000')
request.setHeader('Expires', 'Fri, 01 Jan 2500 00:00:00 GMT') #Get a new library by then
return '''
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script>
document.domain = document.domain;
_sockjs_onload = function(){{SockJS.bootstrap_iframe();}};
</script>
<script src="{}"></script>
</head>
<body>
<h2>Don't panic!</h2>
<p>This is a SockJS hidden iframe. It's used for cross domain magic.</p>
</body>
</html>'''.format(self.parent._options["sockjs_url"])
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
try:
from twisted.web.websockets import WebSocketsResource
except ImportError:
from txsockjs.websockets import WebSocketsResource
from zope.interface import directlyProvides, providedBy
from twisted.internet.protocol import Protocol
from twisted.protocols.policies import WrappingFactory, ProtocolWrapper
from twisted.web.server import NOT_DONE_YET
from txsockjs.oldwebsockets import OldWebSocketsResource
from txsockjs.utils import normalize
import json
class JsonProtocol(ProtocolWrapper):
def makeConnection(self, transport):
directlyProvides(self, providedBy(transport))
Protocol.makeConnection(self, transport)
self.transport.write("o")
self.factory.registerProtocol(self)
self.wrappedProtocol.makeConnection(self)
def write(self, data):
self.writeSequence([data])
def writeSequence(self, data):
for p in data:
p = normalize(p, self.factory.parent._options['encoding'])
self.transport.write("a{}".format(json.dumps(data, separators=(',',':'))))
def writeRaw(self, data):
self.transport.write(data)
def loseConnection(self):
self.transport.write('c[3000,"Go away!"]')
ProtocolWrapper.loseConnection(self)
def dataReceived(self, data):
if not data:
return
try:
dat = json.loads(data)
except ValueError:
self.transport.loseConnection()
else:
for d in dat:
ProtocolWrapper.dataReceived(self, d)
class JsonFactory(WrappingFactory):
protocol = JsonProtocol
class RawWebSocket(WebSocketsResource, OldWebSocketsResource):
def __init__(self):
self._factory = None
def _makeFactory(self):
WebSocketsResource.__init__(self, self.parent._factory)
OldWebSocketsResource.__init__(self, self.parent._factory)
def render(self, request):
# Get around .parent limitation
if self._factory is None:
self._makeFactory()
# Override handling of invalid methods, returning 400 makes SockJS mad
if request.method != 'GET':
request.setResponseCode(405)
request.defaultContentType = None # SockJS wants this gone
request.setHeader('Allow','GET')
return ""
# Override handling of lack of headers, again SockJS requires non-RFC stuff
upgrade = request.getHeader("Upgrade")
if upgrade is None or "websocket" not in upgrade.lower():
request.setResponseCode(400)
return 'Can "Upgrade" only to "WebSocket".'
connection = request.getHeader("Connection")
if connection is None or "upgrade" not in connection.lower():
request.setResponseCode(400)
return '"Connection" must be "Upgrade".'
# Defer to inherited methods
ret = WebSocketsResource.render(self, request) # For RFC versions of websockets
if ret is NOT_DONE_YET:
return ret
return OldWebSocketsResource.render(self, request) # For non-RFC versions of websockets
class WebSocket(RawWebSocket):
def _makeFactory(self):
f = JsonFactory(self.parent._factory)
f.parent = self.parent
WebSocketsResource.__init__(self, f)
OldWebSocketsResource.__init__(self, f)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from twisted.web import resource, http
from txsockjs.protocols.base import StubResource
class XHR(StubResource):
written = False
def render_POST(self, request):
self.parent.setBaseHeaders(request)
request.setHeader('content-type', 'application/javascript; charset=UTF-8')
return self.connect(request)
def write(self, data):
if self.written:
self.session.requeue([data])
return
self.written = True
self.request.write("{}\n".format(data))
self.disconnect()
def writeSequence(self, data):
if not self.written:
self.write(data.pop(0))
self.session.requeue(data)
class XHRSend(StubResource):
def render_POST(self, request):
self.parent.setBaseHeaders(request)
request.setResponseCode(http.NO_CONTENT)
request.setHeader('content-type', 'text/plain; charset=UTF-8')
ret = self.session.dataReceived(request.content.read())
if not ret:
return ""
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
return "{}\r\n".format(ret)
class XHRStream(StubResource):
sent = 0
done = False
def render_POST(self, request):
self.parent.setBaseHeaders(request)
request.setHeader('content-type', 'application/javascript; charset=UTF-8')
request.write("{}\n".format('h'*2048))
return self.connect(request)
def write(self, data):
if self.done:
self.session.requeue([data])
return
packet = "{}\n".format(data)
self.sent += len(packet)
self.request.write(packet)
if self.sent > self.parent._options['streaming_limit']:
self.done = True
self.disconnect()
def writeSequence(self, data):
for d in data:
self.write(d)
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
def normalize(s, encoding):
if not isinstance(s, str):
try:
return str(s)
except UnicodeEncodeError:
return str(s).encode('utf-8','backslashreplace')
elif isinstance(s, str):
return s.encode('utf-8', 'backslashreplace')
else:
if s.decode('utf-8', 'ignore').encode('utf-8', 'ignore') == s: # Ensure s is a valid UTF-8 string
return s
else: # Otherwise assume it is Windows 1252
return s.decode(encoding, 'replace').encode('utf-8', 'backslashreplace')
def broadcast(message, targets, encoding="cp1252"):
message = normalize(message, encoding)
message = 'a{}'.format(json.dumps([message], separators=(',',':')))
for t in targets:
t.writeRaw(message)
\ No newline at end of file
# Copyright (c) 2012, Christopher Gamble
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Christopher Gamble nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
def normalize(s, encoding):
if not isinstance(s, basestring):
try:
return str(s)
except UnicodeEncodeError:
return unicode(s).encode('utf-8','backslashreplace')
elif isinstance(s, unicode):
return s.encode('utf-8', 'backslashreplace')
else:
if s.decode('utf-8', 'ignore').encode('utf-8', 'ignore') == s: # Ensure s is a valid UTF-8 string
return s
else: # Otherwise assume it is Windows 1252
return s.decode(encoding, 'replace').encode('utf-8', 'backslashreplace')
def broadcast(message, targets, encoding="cp1252"):
message = normalize(message, encoding)
message = 'a{}'.format(json.dumps([message], separators=(',',':')))
for t in targets:
t.writeRaw(message)
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment