python-cyclone-1.1/ 0000755 0001750 0001750 00000000000 12124336260 013351 5 ustar lunar lunar python-cyclone-1.1/MANIFEST.in 0000644 0001750 0001750 00000000153 12124336260 015106 0 ustar lunar lunar include cyclone/appskel_default.zip
include cyclone/appskel_foreman.zip
include cyclone/appskel_signup.zip
python-cyclone-1.1/setup.py 0000644 0001750 0001750 00000006554 12124336260 015075 0 ustar lunar lunar #!/usr/bin/env python
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import platform
from distutils import log
from distutils.version import LooseVersion
from distutils.version import StrictVersion
requires = ["twisted"]
# Avoid installation problems on old RedHat distributions (ex. CentOS 5)
# http://stackoverflow.com/questions/7340784/easy-install-pyopenssl-error
py_version = platform.python_version()
if LooseVersion(py_version) < StrictVersion('2.6'):
distname, version, _id = platform.dist()
else:
distname, version, _id = platform.linux_distribution()
is_redhat = distname in ["CentOS", "redhat"]
if is_redhat and version and StrictVersion(version) < StrictVersion('6.0'):
requires.append("pyopenssl==0.12")
else:
requires.append("pyopenssl")
# PyPy and setuptools don't get along too well, yet.
if sys.subversion[0].lower().startswith("pypy"):
import distutils.core
setup = distutils.core.setup
extra = dict(requires=requires)
else:
import setuptools
setup = setuptools.setup
extra = dict(install_requires=requires)
try:
from setuptools.command import egg_info
egg_info.write_toplevel_names
except (ImportError, AttributeError):
pass
else:
"""
'twisted' should not occur in the top_level.txt file as this
triggers a bug in pip that removes all of twisted when a package
with a twisted plugin is removed.
"""
def _top_level_package(name):
return name.split('.', 1)[0]
def _hacked_write_toplevel_names(cmd, basename, filename):
pkgs = dict.fromkeys(
[_top_level_package(k)
for k in cmd.distribution.iter_distribution_names()
if _top_level_package(k) != "twisted"
]
)
cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n')
egg_info.write_toplevel_names = _hacked_write_toplevel_names
setup(
name="cyclone",
version="1.1",
author="fiorix",
author_email="fiorix@gmail.com",
url="http://cyclone.io/",
license="http://www.apache.org/licenses/LICENSE-2.0",
description="Non-blocking web server. "
"A facebook's Tornado on top of Twisted.",
keywords="python non-blocking web server twisted facebook tornado",
packages=["cyclone", "twisted.plugins"],
package_data={"twisted": ["plugins/cyclone_plugin.py"],
"cyclone": ["appskel_default.zip",
"appskel_foreman.zip",
"appskel_signup.zip"]},
scripts=["scripts/cyclone"],
**extra
)
try:
from twisted.plugin import IPlugin, getPlugins
list(getPlugins(IPlugin))
except Exception, e:
log.warn("*** Failed to update Twisted plugin cache. ***")
log.warn(str(e))
python-cyclone-1.1/.gitignore 0000644 0001750 0001750 00000000110 12124336260 015331 0 ustar lunar lunar *.swp
*.pyc
*.so
*~
build
dist
cyclone.egg-info
dropin.cache
*DS_Store*
python-cyclone-1.1/demos/ 0000755 0001750 0001750 00000000000 12124336260 014460 5 ustar lunar lunar python-cyclone-1.1/demos/bottle/ 0000755 0001750 0001750 00000000000 12124336260 015751 5 ustar lunar lunar python-cyclone-1.1/demos/bottle/xmlrpc_client.py 0000644 0001750 0001750 00000001411 12124336260 021163 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import xmlrpclib
srv = xmlrpclib.Server("http://localhost:8888/xmlrpc")
print "echo:", srv.echo("hello world!")
python-cyclone-1.1/demos/bottle/README 0000644 0001750 0001750 00000001422 12124336260 016630 0 ustar lunar lunar This is a complete cyclone.bottle demo.
For a basic hello world, check out the helloworld_bottle.py demo.
The idea is to show how easy it is to create simple bottle-like apps, and
still capture all the functionality such as database support, setting
a parent class (the BaseHandler) for your request handlers and integrating with
other types of handlers such as XmlRPC and WebSocket.
The authentication mechanism is similar to the one used in
httpauthdemo_redis.py and require that the "cyclone:user" key exists in redis.
Basically, run the redis server and use the redis client to set up a fake
user account:
$ redis-cli set cyclone:root 123
Then authenticate yourself at http://localhost:8888 as root/123.
XmlRPC functionality can be tested by executing the xmlrpc_client.py.
python-cyclone-1.1/demos/bottle/bottledemo.py 0000755 0001750 0001750 00000007230 12124336260 020466 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.escape
import cyclone.redis
import cyclone.sqlite
import cyclone.util
import cyclone.web
import cyclone.websocket
import cyclone.xmlrpc
from cyclone.bottle import run, route
from twisted.internet import defer
from twisted.python import log
class BaseHandler(cyclone.web.RequestHandler):
@property
def redisdb(self):
return self.settings.db_handlers.redis
def get_current_user(self):
print "Getting user cookie"
return self.get_secure_cookie("user")
@route("/")
def index(cli):
cli.write('sign in')
@route("/auth/login")
def auth_login_page(cli):
cli.write("""
""")
@route("/auth/login", method="post")
@defer.inlineCallbacks
def auth_login(cli):
usr = cli.get_argument("usr")
pwd = cli.get_argument("pwd")
try:
redis_pwd = yield cli.redisdb.get("cyclone:%s" % usr)
except Exception, e:
log.msg("Redis failed to get('cyclone:%s'): %s" % (usr, str(e)))
raise cyclone.web.HTTPError(503) # Service Unavailable
if pwd != str(redis_pwd):
cli.write("Invalid user or password. "
'try again')
else:
cli.set_secure_cookie("user", usr)
cli.redirect(cli.get_argument("next", "/private"))
@route("/auth/logout")
@cyclone.web.authenticated
def auth_logout(cli):
cli.clear_cookie("user")
cli.redirect("/")
@route("/private")
@cyclone.web.authenticated
def private(cli):
cli.write("Hi, %s " % cli.current_user)
cli.write("""logout""")
class WebSocketHandler(cyclone.websocket.WebSocketHandler):
def connectionMade(self, *args, **kwargs):
print "connection made:", args, kwargs
def messageReceived(self, message):
self.sendMessage("echo: %s" % message)
def connectionLost(self, why):
print "connection lost:", why
class XmlrpcHandler(cyclone.xmlrpc.XmlrpcRequestHandler):
allowNone = True
def xmlrpc_echo(self, text):
return text
try:
raise Exception("COMMENT_THIS_LINE_AND_LOG_TO_DAILY_FILE")
from twisted.python.logfile import DailyLogFile
logFile = DailyLogFile.fromFullPath("server.log")
print("Logging to daily log file: server.log")
except Exception, e:
import sys
logFile = sys.stdout
run(host="127.0.0.1", port=8888,
log=logFile,
debug=True,
static_path="./static",
template_path="./template",
locale_path="./locale",
login_url="/auth/login",
cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
base_handler=BaseHandler,
db_handlers=cyclone.util.ObjectDict(
#sqlite=cyclone.sqlite.InlineSQLite(":memory:"),
redis=cyclone.redis.lazyConnectionPool(),
),
more_handlers=[
(r"/websocket", WebSocketHandler),
(r"/xmlrpc", XmlrpcHandler),
])
python-cyclone-1.1/demos/chat/ 0000755 0001750 0001750 00000000000 12124336260 015377 5 ustar lunar lunar python-cyclone-1.1/demos/chat/chatdemo.py 0000755 0001750 0001750 00000011204 12124336260 017536 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import cyclone.web
import cyclone.auth
import cyclone.escape
from twisted.python import log
from twisted.internet import reactor
import os.path
import uuid
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/auth/login", AuthLoginHandler),
(r"/auth/logout", AuthLogoutHandler),
(r"/a/message/new", MessageNewHandler),
(r"/a/message/updates", MessageUpdatesHandler),
]
settings = dict(
cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
login_url="/auth/login",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
autoescape=None,
)
cyclone.web.Application.__init__(self, handlers, **settings)
class BaseHandler(cyclone.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if not user_json:
return None
return cyclone.escape.json_decode(user_json)
class MainHandler(BaseHandler):
@cyclone.web.authenticated
def get(self):
self.render("index.html", messages=MessageMixin.cache)
class MessageMixin(object):
waiters = []
cache = []
cache_size = 200
def wait_for_messages(self, callback, cursor=None):
cls = MessageMixin
if cursor:
index = 0
for i in xrange(len(cls.cache)):
index = len(cls.cache) - i - 1
if cls.cache[index]["id"] == cursor:
break
recent = cls.cache[index + 1:]
if recent:
callback(recent)
return
cls.waiters.append(callback)
def new_messages(self, messages):
cls = MessageMixin
log.msg("Sending new message to %r listeners" % len(cls.waiters))
for callback in cls.waiters:
try:
callback(messages)
except:
log.err()
cls.waiters = []
cls.cache.extend(messages)
if len(cls.cache) > self.cache_size:
cls.cache = cls.cache[-self.cache_size:]
class MessageNewHandler(BaseHandler, MessageMixin):
@cyclone.web.authenticated
def post(self):
message = {
"id": str(uuid.uuid4()),
"from": self.current_user["first_name"],
"body": self.get_argument("body"),
}
message["html"] = self.render_string("message.html", message=message)
if self.get_argument("next", None):
self.redirect(self.get_argument("next"))
else:
self.write(message)
self.new_messages([message])
class MessageUpdatesHandler(BaseHandler, MessageMixin):
@cyclone.web.authenticated
@cyclone.web.asynchronous
def post(self):
cursor = self.get_argument("cursor", None)
self.wait_for_messages(self.on_new_messages, cursor=cursor)
def on_new_messages(self, messages):
# Closed client connection
#if self.request.connection.stream.closed():
#return
self.finish(dict(messages=messages))
class AuthLoginHandler(BaseHandler, cyclone.auth.GoogleMixin):
@cyclone.web.asynchronous
def get(self):
if self.get_argument("openid.mode", None):
self.get_authenticated_user(self._on_auth)
return
self.authenticate_redirect(ax_attrs=["name"])
def _on_auth(self, user):
if not user:
raise cyclone.web.HTTPError(500, "Google auth failed")
self.set_secure_cookie("user", cyclone.escape.json_encode(user))
self.redirect("/")
class AuthLogoutHandler(BaseHandler):
def get(self):
self.clear_cookie("user")
self.write("You are now logged out")
def main():
reactor.listenTCP(8888, Application())
reactor.run()
if __name__ == "__main__":
log.startLogging(sys.stdout)
main()
python-cyclone-1.1/demos/chat/templates/ 0000755 0001750 0001750 00000000000 12124336260 017375 5 ustar lunar lunar python-cyclone-1.1/demos/chat/templates/index.html 0000644 0001750 0001750 00000002415 12124336260 021374 0 ustar lunar lunar
Tornado Chat Demo
python-cyclone-1.1/demos/chat/static/ 0000755 0001750 0001750 00000000000 12124336260 016666 5 ustar lunar lunar python-cyclone-1.1/demos/chat/static/chat.js 0000644 0001750 0001750 00000007201 12124336260 020143 0 ustar lunar lunar // Copyright 2009 FriendFeed
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
$("#messageform").live("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
$("#message").select();
updater.poll();
});
function newMessage(form) {
var message = form.formToDict();
var disabled = form.find("input[type=submit]");
disabled.disable();
$.postJSON("/a/message/new", message, function(response) {
updater.showMessage(response);
if (message.id) {
form.parent().remove();
} else {
form.find("input[type=text]").val("").select();
disabled.enable();
}
});
}
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
jQuery.postJSON = function(url, args, callback) {
args._xsrf = getCookie("_xsrf");
$.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
success: function(response) {
if (callback) callback(eval("(" + response + ")"));
}, error: function(response) {
console.log("ERROR:", response)
}});
};
jQuery.fn.formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
jQuery.fn.disable = function() {
this.enable(false);
return this;
};
jQuery.fn.enable = function(opt_enable) {
if (arguments.length && !opt_enable) {
this.attr("disabled", "disabled");
} else {
this.removeAttr("disabled");
}
return this;
};
var updater = {
errorSleepTime: 500,
cursor: null,
poll: function() {
var args = {"_xsrf": getCookie("_xsrf")};
if (updater.cursor) args.cursor = updater.cursor;
$.ajax({url: "/a/message/updates", type: "POST", dataType: "text",
data: $.param(args), success: updater.onSuccess,
error: updater.onError});
},
onSuccess: function(response) {
try {
updater.newMessages(eval("(" + response + ")"));
} catch (e) {
updater.onError();
return;
}
updater.errorSleepTime = 500;
window.setTimeout(updater.poll, 0);
},
onError: function(response) {
updater.errorSleepTime *= 2;
console.log("Poll error; sleeping for", updater.errorSleepTime, "ms");
window.setTimeout(updater.poll, updater.errorSleepTime);
},
newMessages: function(response) {
if (!response.messages) return;
updater.cursor = response.cursor;
var messages = response.messages;
updater.cursor = messages[messages.length - 1].id;
console.log(messages.length, "new messages, cursor:", updater.cursor);
for (var i = 0; i < messages.length; i++) {
updater.showMessage(messages[i]);
}
},
showMessage: function(message) {
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
};
python-cyclone-1.1/demos/chat/static/chat.css 0000644 0001750 0001750 00000001735 12124336260 020325 0 ustar lunar lunar /*
* Copyright 2009 FriendFeed
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
body {
background: white;
margin: 10px;
}
body,
input {
font-family: sans-serif;
font-size: 10pt;
color: black;
}
table {
border-collapse: collapse;
border: 0;
}
td {
border: 0;
padding: 0;
}
#body {
position: absolute;
bottom: 10px;
left: 10px;
}
#input {
margin-top: 0.5em;
}
#inbox .message {
padding-top: 0.25em;
}
#nav {
float: right;
z-index: 99;
}
python-cyclone-1.1/demos/httpauth/ 0000755 0001750 0001750 00000000000 12124336260 016321 5 ustar lunar lunar python-cyclone-1.1/demos/httpauth/httpauthdemo_redis.py 0000755 0001750 0001750 00000005607 12124336260 022602 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import functools
import sys
import cyclone.redis
import cyclone.web
from twisted.python import log
from twisted.internet import defer, reactor
class Application(cyclone.web.Application):
def __init__(self):
# Defaults to localhost:6379, dbid=0
redisdb = cyclone.redis.lazyConnectionPool()
handlers = [
(r"/", IndexHandler, dict(redisdb=redisdb)),
]
cyclone.web.Application.__init__(self, handlers, debug=True)
def HTTPBasic(method):
@defer.inlineCallbacks
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
msg = None
if "Authorization" in self.request.headers:
auth_type, data = self.request.headers["Authorization"].split()
try:
assert auth_type == "Basic"
usr, pwd = base64.b64decode(data).split(":", 1)
redis_pwd = yield self.redisdb.get("cyclone:%s" % usr)
assert pwd == str(redis_pwd) # it may come back as an int!
except AssertionError:
msg = "Authentication failed"
except cyclone.redis.ConnectionError, e:
# There's nothing we can do here. Just wait for the
# connection to resume.
log.msg("Redis is unavailable: %s" % e)
raise cyclone.web.HTTPError(503) # Service Unavailable
else:
msg = "Authentication required"
if msg:
raise cyclone.web.HTTPAuthenticationRequired(
log_message=msg, auth_type="Basic", realm="DEMO")
else:
self._current_user = usr
yield defer.maybeDeferred(method, self, *args, **kwargs)
return wrapper
class IndexHandler(cyclone.web.RequestHandler):
def initialize(self, redisdb):
self.redisdb = redisdb
@HTTPBasic
def get(self):
self.write("Hi, %s." % self._current_user)
def main():
log.startLogging(sys.stdout)
log.msg(">>>> Set the password from command line: "
"redis-cli set cyclone:root 123")
log.msg(">>>> Then authenticate as root/123 from the browser")
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/httpauth/README 0000644 0001750 0001750 00000002643 12124336260 017206 0 ustar lunar lunar HTTP Basic Authentication
httpauthdemo.py:
A very simple, pretty much useless example.
The idea is to show how to build a decorator to request valid credentials,
and send HTTP "401 Unauthorized" when necessary.
httpauthdemo_redis.py:
This is more of a real-world example, authenticating against a database,
in this case, redis. You can easily port it to standard RDBMs such as
MySQL, PostgreSQL and others, using twisted.enterprise.adbapi.
Again, it does so via the decorator, which extract credentials from Redis
and compare them against what people typed on their browser.
If you run this example without a redis-server, it will always return HTTP
503 "Service Unavailable" due to lack of communication with the database
backend. Once you start redis, it will automatically connect to it.
Make sure you create a user in redis to test the authentication system.
From the command line, run:
$ redis-cli set cyclone:root 123
Then point your browser to http://localhost:8888 and authenticate as root/123.
httpauthdemo_mongo.py:
Same as the above, but for the MongoDB database.
Make sure you have txredis installed, otherwise go grab it from GitHub:
https://github.com/fiorix/mongo-async-python-driver
Create a new user:
curl -D - -d "username=root&password=123" http://localhost:8888/createuser
Then point your browser to http://localhost:8888 and authenticate as root/123.
Have fun!
python-cyclone-1.1/demos/httpauth/httpauthdemo_mongo.py 0000755 0001750 0001750 00000006643 12124336260 022614 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import functools
import sys
import cyclone.redis
import cyclone.web
from twisted.python import log
from twisted.internet import defer, reactor
try:
import txmongo
except ImportError:
print("You need txmongo: "
"https://github.com/fiorix/mongo-async-python-driver")
sys.exit(1)
class Application(cyclone.web.Application):
def __init__(self):
mongodb = txmongo.lazyMongoConnectionPool()
handlers = [
(r"/", IndexHandler, dict(mongodb=mongodb)),
(r"/createuser", CreateUserHandler, dict(mongodb=mongodb)),
]
cyclone.web.Application.__init__(self, handlers, debug=True)
def HTTPBasic(method):
@defer.inlineCallbacks
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
try:
auth_type, auth_data = \
self.request.headers["Authorization"].split()
assert auth_type == "Basic"
usr, pwd = base64.b64decode(auth_data).split(":", 1)
except:
raise cyclone.web.HTTPAuthenticationRequired
try:
# search for user under the "cyclonedb.users" collection
response = yield self.mongodb.cyclonedb.users.find_one(
{"usr": usr, "pwd": pwd}, fields=["usr"])
mongo_usr = response.get("usr")
except Exception, e:
log.msg("MongoDB failed to find(): %s" % str(e))
raise cyclone.web.HTTPError(503) # Service Unavailable
if usr != mongo_usr:
raise cyclone.web.HTTPAuthenticationRequired
else:
self._current_user = usr
defer.returnValue(method(self, *args, **kwargs))
return wrapper
class IndexHandler(cyclone.web.RequestHandler):
def initialize(self, mongodb):
self.mongodb = mongodb
@HTTPBasic
def get(self):
self.write("Hi, %s." % self._current_user)
class CreateUserHandler(cyclone.web.RequestHandler):
def initialize(self, mongodb):
self.mongodb = mongodb
@defer.inlineCallbacks
def post(self):
usr = self.get_argument("username")
pwd = self.get_argument("password")
try:
# create user under the "cyclonedb.users" collection
ObjId = yield self.mongodb.cyclonedb.users.update(
{"usr": usr}, {"usr": usr, "pwd": pwd},
upsert=True, safe=True)
except Exception, e:
log.msg("MongoDB failed to upsert(): %s" % str(e))
raise cyclone.web.HTTPError(503) # Service Unavailable
self.write("User created. ObjId=%s\r\n" % ObjId)
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/httpauth/httpauthdemo.py 0000755 0001750 0001750 00000004163 12124336260 021410 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
import functools
import sys
import cyclone.web
from twisted.python import log
from twisted.internet import reactor
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", IndexHandler),
]
cyclone.web.Application.__init__(self, handlers, debug=True)
def HTTPBasic(method):
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
msg = None
if "Authorization" in self.request.headers:
auth_type, data = self.request.headers["Authorization"].split()
try:
assert auth_type == "Basic"
usr, pwd = base64.b64decode(data).split(":", 1)
assert usr == "root@localhost"
assert pwd == "123"
except AssertionError:
msg = "Authentication failed"
else:
msg = "Authentication required"
if msg:
raise cyclone.web.HTTPAuthenticationRequired(
log_message=msg, auth_type="Basic", realm="DEMO")
else:
self._current_user = usr
return method(self, *args, **kwargs)
return wrapper
class IndexHandler(cyclone.web.RequestHandler):
@HTTPBasic
def get(self):
self.write("Hi, %s." % self._current_user)
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="0.0.0.0")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/sse/ 0000755 0001750 0001750 00000000000 12124336260 015252 5 ustar lunar lunar python-cyclone-1.1/demos/sse/ssedemo.py 0000755 0001750 0001750 00000005321 12124336260 017267 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import cyclone.sse
import cyclone.web
from twisted.internet import protocol
from twisted.internet import reactor
from twisted.protocols import telnet
from twisted.python import log
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", cyclone.web.RedirectHandler, {"url": "/static/index.html"}),
(r"/live", LiveHandler),
]
settings = dict(
debug=True,
static_path="./static",
template_path="./template",
)
cyclone.web.Application.__init__(self, handlers, **settings)
class StarWarsMixin(object):
mbuffer = ""
waiters = []
def subscribe(self, client):
StarWarsMixin.waiters.append(client)
def unsubscribe(self, client):
StarWarsMixin.waiters.remove(client)
def broadcast(self, message):
cls = StarWarsMixin
chunks = (self.mbuffer + message.replace("\x1b[J", "")).split("\x1b[H")
self.mbuffer = ""
for chunk in chunks:
if len(chunk) == 985:
chunk = chunk.replace("\r\n", " ")
log.msg("Sending new message to %r listeners" % \
len(cls.waiters))
for client in cls.waiters:
try:
client.sendEvent(chunk)
except:
log.err()
else:
self.mbuffer = chunk
class LiveHandler(cyclone.sse.SSEHandler, StarWarsMixin):
def bind(self):
self.subscribe(self)
def unbind(self):
self.unsubscribe(self)
class BlinkenlightsProtocol(telnet.Telnet, StarWarsMixin):
def dataReceived(self, data):
self.broadcast(data)
def main():
log.startLogging(sys.stdout)
blinkenlights = protocol.ReconnectingClientFactory()
blinkenlights.protocol = BlinkenlightsProtocol
reactor.connectTCP("towel.blinkenlights.nl", 23, blinkenlights)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/sse/README 0000644 0001750 0001750 00000000612 12124336260 016131 0 ustar lunar lunar Server Sent Events Demo
SSE is a mechanism akin to comet but with fine grained control over the data. You can name events, send structured and partial data and set the proper timeout.
To understand better, check http://www.html5rocks.com/en/tutorials/eventsource/basics/
The client side gets really simple than controlling a COMET resource using js, by using the window.EventSource object.
python-cyclone-1.1/demos/sse/static/ 0000755 0001750 0001750 00000000000 12124336260 016541 5 ustar lunar lunar python-cyclone-1.1/demos/sse/static/index.html 0000644 0001750 0001750 00000001273 12124336260 020541 0 ustar lunar lunar
Episode IV Live from towel.blinkenlights.nl
python-cyclone-1.1/demos/websocket/ 0000755 0001750 0001750 00000000000 12124336260 016446 5 ustar lunar lunar python-cyclone-1.1/demos/websocket/chat/ 0000755 0001750 0001750 00000000000 12124336260 017365 5 ustar lunar lunar python-cyclone-1.1/demos/websocket/chat/chatdemo.py 0000755 0001750 0001750 00000010572 12124336260 021533 0 ustar lunar lunar #!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Simplified chat demo for websockets.
Authentication, error handling, etc are left as an exercise for the reader :)
"""
import os.path
import uuid
import sys
import time
from collections import defaultdict
from twisted.python import log
from twisted.internet import reactor, task
import cyclone.escape
import cyclone.web
import cyclone.websocket
class Application(cyclone.web.Application):
def __init__(self):
stats = Stats()
handlers = [
(r"/", MainHandler, dict(stats=stats)),
(r"/stats", StatsPageHandler),
(r"/statssocket", StatsSocketHandler, dict(stats=stats)),
(r"/chatsocket", ChatSocketHandler, dict(stats=stats)),
]
settings = dict(
cookie_secret="43oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
autoescape=None,
)
cyclone.web.Application.__init__(self, handlers, **settings)
class MainHandler(cyclone.web.RequestHandler):
def initialize(self, stats):
self.stats = stats
def get(self):
self.stats.newVisit()
self.render("index.html", messages=ChatSocketHandler.cache)
class ChatSocketHandler(cyclone.websocket.WebSocketHandler):
waiters = set()
cache = []
cache_size = 200
def initialize(self, stats):
self.stats = stats
def connectionMade(self):
ChatSocketHandler.waiters.add(self)
self.stats.newChatter()
def connectionLost(self, reason):
ChatSocketHandler.waiters.remove(self)
self.stats.lostChatter()
@classmethod
def update_cache(cls, chat):
cls.cache.append(chat)
if len(cls.cache) > cls.cache_size:
cls.cache = cls.cache[-cls.cache_size:]
@classmethod
def send_updates(cls, chat):
log.msg("sending message to %d waiters" % len(cls.waiters))
for waiter in cls.waiters:
try:
waiter.sendMessage(chat)
except Exception, e:
log.err("Error sending message. %s" % str(e))
def messageReceived(self, message):
log.msg("got message %s" % message)
parsed = cyclone.escape.json_decode(message)
chat = {
"id": str(uuid.uuid4()),
"body": parsed["body"],
}
chat["html"] = self.render_string("message.html", message=chat)
ChatSocketHandler.update_cache(chat)
ChatSocketHandler.send_updates(chat)
class StatsSocketHandler(cyclone.websocket.WebSocketHandler):
def initialize(self, stats):
self.stats = stats
self._updater = task.LoopingCall(self._sendData)
def connectionMade(self):
self._updater.start(2)
def connectionLost(self, reason):
self._updater.stop()
def _sendData(self):
data = dict(visits=self.stats.todaysVisits(),
chatters=self.stats.chatters)
self.sendMessage(cyclone.escape.json_encode(data))
class Stats(object):
def __init__(self):
self.visits = defaultdict(int)
self.chatters = 0
def todaysVisits(self):
today = time.localtime()
key = time.strftime('%Y%m%d', today)
return self.visits[key]
def newChatter(self):
self.chatters += 1
def lostChatter(self):
self.chatters -= 1
def newVisit(self):
today = time.localtime()
key = time.strftime('%Y%m%d', today)
self.visits[key] += 1
class StatsPageHandler(cyclone.web.RequestHandler):
def get(self):
self.render("stats.html")
def main():
reactor.listenTCP(8888, Application())
reactor.run()
if __name__ == "__main__":
log.startLogging(sys.stdout)
main()
python-cyclone-1.1/demos/websocket/chat/templates/ 0000755 0001750 0001750 00000000000 12124336260 021363 5 ustar lunar lunar python-cyclone-1.1/demos/websocket/chat/templates/index.html 0000644 0001750 0001750 00000002534 12124336260 023364 0 ustar lunar lunar
Cyclone Chat Demo
{% for message in messages %}
{% include "message.html" %}
{% end %}
python-cyclone-1.1/demos/locale/frontend/template/hello.txt 0000644 0001750 0001750 00000000062 12124336260 023213 0 ustar lunar lunar Simple text file.
{{ _("%s, translated") % msg }}
python-cyclone-1.1/demos/locale/mytest_pt_BR.po 0000644 0001750 0001750 00000002446 12124336260 020700 0 ustar lunar lunar # cyclone locale demo
# Copyright (C) 2010 Alexandre Fiori
# This file is distributed under the same license as the cyclone package.
# Alexandre Fiori , 2010
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-01-20 12:19-0200\n"
"PO-Revision-Date: 2010-01-20 12:21-0300\n"
"Last-Translator: Alexandre Fiori \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=n != 1;\n"
#: localedemo.py:76
msgid "hello world"
msgstr "olá mundo"
#: frontend/template/hello.txt:2
#, python-format
msgid "%s, translated"
msgstr "%s, traduzido"
#: frontend/template/index.html:5 frontend/template/index.html:25
msgid "cyclone locale demo"
msgstr "demonstração de internacionalização do cyclone"
#: frontend/template/index.html:29
msgid "Please select your language:"
msgstr "Por favor, selecione sua lÃngua:"
#: frontend/template/index.html:35
msgid "How many apples?"
msgstr "Quantas maçãs?"
#: frontend/template/index.html:39
msgid "Submit"
msgstr "Enviar"
#: frontend/template/index.html:42
#, python-format
msgid "One apple"
msgid_plural "%(count)d apples"
msgstr[0] "Uma maçã"
msgstr[1] "%(count)d maçãs"
python-cyclone-1.1/demos/s3/ 0000755 0001750 0001750 00000000000 12124336260 015005 5 ustar lunar lunar python-cyclone-1.1/demos/s3/README 0000644 0001750 0001750 00000000736 12124336260 015673 0 ustar lunar lunar Port of http://github.com/facebook/tornado/raw/master/tornado/s3server.py (s3 clone on tornado)
TODO:
- plug auth module
- plug riak, redis or another storage besides FS
- be awesome.
RUNNING
$ twistd -ny se.tac
TESTING:
create bucket: $ curl --request PUT "http://localhost:4000/meh/"
put some data (README file): curl --data "@README" --request PUT --header "Content-Type: text/plain" "http://localhost:4000/meh/README"
retrieve: curl http://localhost:4000/meh/README
python-cyclone-1.1/demos/s3/se.tac 0000644 0001750 0001750 00000000604 12124336260 016105 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
# twistd -ny s3.tac
# gleicon moraes (http://zenmachine.wordpress.com | http://github.com/gleicon)
SERVER_PORT = 4000
import s3server
from twisted.application import service, internet
application = service.Application("s3")
srv = internet.TCPServer(SERVER_PORT, s3server.S3Application(root_directory="/tmp/s3"))
srv.setServiceParent(application)
python-cyclone-1.1/demos/s3/s3server.py 0000644 0001750 0001750 00000022346 12124336260 017142 0 ustar lunar lunar #!/usr/bin/env python
#
# Copyright 2009 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# port to cyclone: took out ioloop initialization, fixed imports and created
# a .tac file.
# gleicon 04/10
"""Implementation of an S3-like storage server based on local files.
Useful to test features that will eventually run on S3, or if you want to
run something locally that was once running on S3.
We don't support all the features of S3, but it does work with the
standard S3 client for the most basic semantics. To use the standard
S3 client with this module:
c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
is_secure=False)
c.create_bucket("mybucket")
c.put("mybucket", "mykey", "a value")
print c.get("mybucket", "mykey").body
"""
from cyclone import escape
from cyclone import web
from twisted.python import log
import datetime
import bisect
import hashlib
import os
import os.path
import urllib
class S3Application(web.Application):
"""Implementation of an S3-like storage server based on local files.
If bucket depth is given, we break files up into multiple directories
to prevent hitting file system limits for number of files in each
directories. 1 means one level of directories, 2 means 2, etc.
"""
def __init__(self, root_directory, bucket_depth=0):
web.Application.__init__(self, [
(r"/", RootHandler),
(r"/([^/]+)/(.+)", ObjectHandler),
(r"/([^/]+)/", BucketHandler),
])
self.directory = os.path.abspath(root_directory)
if not os.path.exists(self.directory):
os.makedirs(self.directory)
self.bucket_depth = bucket_depth
class BaseRequestHandler(web.RequestHandler):
# SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD")
def render_xml(self, value):
assert isinstance(value, dict) and len(value) == 1
self.set_header("Content-Type", "application/xml; charset=UTF-8")
name = value.keys()[0]
parts = []
parts.append('<' + escape.utf8(name) +
' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
self._render_parts(value.values()[0], parts)
parts.append('' + escape.utf8(name) + '>')
self.finish('\n' +
''.join(parts))
def _render_parts(self, value, parts=[]):
if isinstance(value, basestring):
parts.append(escape.xhtml_escape(value))
elif isinstance(value, int) or isinstance(value, long):
parts.append(str(value))
elif isinstance(value, datetime.datetime):
parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
elif isinstance(value, dict):
for name, subvalue in value.iteritems():
if not isinstance(subvalue, list):
subvalue = [subvalue]
for subsubvalue in subvalue:
parts.append('<' + escape.utf8(name) + '>')
self._render_parts(subsubvalue, parts)
parts.append('' + escape.utf8(name) + '>')
else:
raise Exception("Unknown S3 value type %r", value)
def _object_path(self, bucket, object_name):
if self.application.bucket_depth < 1:
return os.path.abspath(os.path.join(
self.application.directory, bucket, object_name))
hash = hashlib.md5(object_name).hexdigest()
path = os.path.abspath(os.path.join(
self.application.directory, bucket))
for i in range(self.application.bucket_depth):
path = os.path.join(path, hash[:2 * (i + 1)])
return os.path.join(path, object_name)
class RootHandler(BaseRequestHandler):
def get(self):
names = os.listdir(self.application.directory)
buckets = []
for name in names:
path = os.path.join(self.application.directory, name)
info = os.stat(path)
buckets.append({
"Name": name,
"CreationDate": datetime.datetime.utcfromtimestamp(
info.st_ctime),
})
self.render_xml({"ListAllMyBucketsResult": {
"Buckets": {"Bucket": buckets},
}})
class BucketHandler(BaseRequestHandler):
def get(self, bucket_name):
prefix = self.get_argument("prefix", u"")
marker = self.get_argument("marker", u"")
max_keys = int(self.get_argument("max-keys", 50000))
path = os.path.abspath(os.path.join(self.application.directory,
bucket_name))
terse = int(self.get_argument("terse", 0))
if not path.startswith(self.application.directory) or \
not os.path.isdir(path):
raise web.HTTPError(404)
object_names = []
for root, dirs, files in os.walk(path):
for file_name in files:
object_names.append(os.path.join(root, file_name))
skip = len(path) + 1
for i in range(self.application.bucket_depth):
skip += 2 * (i + 1) + 1
object_names = [n[skip:] for n in object_names]
object_names.sort()
contents = []
start_pos = 0
if marker:
start_pos = bisect.bisect_right(object_names, marker, start_pos)
if prefix:
start_pos = bisect.bisect_left(object_names, prefix, start_pos)
truncated = False
for object_name in object_names[start_pos:]:
if not object_name.startswith(prefix):
break
if len(contents) >= max_keys:
truncated = True
break
object_path = self._object_path(bucket_name, object_name)
c = {"Key": object_name}
if not terse:
info = os.stat(object_path)
c.update({
"LastModified": datetime.datetime.utcfromtimestamp(
info.st_mtime),
"Size": info.st_size,
})
contents.append(c)
marker = object_name
self.render_xml({"ListBucketResult": {
"Name": bucket_name,
"Prefix": prefix,
"Marker": marker,
"MaxKeys": max_keys,
"IsTruncated": truncated,
"Contents": contents,
}})
def put(self, bucket_name):
log.msg('bruxao')
path = os.path.abspath(os.path.join(
self.application.directory, bucket_name))
if not path.startswith(self.application.directory) or \
os.path.exists(path):
raise web.HTTPError(403)
os.makedirs(path)
self.finish()
def delete(self, bucket_name):
path = os.path.abspath(os.path.join(
self.application.directory, bucket_name))
if not path.startswith(self.application.directory) or \
not os.path.isdir(path):
raise web.HTTPError(404)
if len(os.listdir(path)) > 0:
raise web.HTTPError(403)
os.rmdir(path)
self.set_status(204)
self.finish()
class ObjectHandler(BaseRequestHandler):
def get(self, bucket, object_name):
object_name = urllib.unquote(object_name)
path = self._object_path(bucket, object_name)
if not path.startswith(self.application.directory) or \
not os.path.isfile(path):
raise web.HTTPError(404)
info = os.stat(path)
self.set_header("Content-Type", "application/unknown")
self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(
info.st_mtime))
object_file = open(path, "r")
try:
self.finish(object_file.read())
finally:
object_file.close()
def put(self, bucket, object_name):
object_name = urllib.unquote(object_name)
bucket_dir = os.path.abspath(os.path.join(
self.application.directory, bucket))
if not bucket_dir.startswith(self.application.directory) or \
not os.path.isdir(bucket_dir):
raise web.HTTPError(404)
path = self._object_path(bucket, object_name)
if not path.startswith(bucket_dir) or os.path.isdir(path):
raise web.HTTPError(403)
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
object_file = open(path, "w")
object_file.write(self.request.body)
object_file.close()
self.finish()
def delete(self, bucket, object_name):
object_name = urllib.unquote(object_name)
path = self._object_path(bucket, object_name)
if not path.startswith(self.application.directory) or \
not os.path.isfile(path):
raise web.HTTPError(404)
os.unlink(path)
self.set_status(204)
self.finish()
python-cyclone-1.1/demos/auth/ 0000755 0001750 0001750 00000000000 12124336260 015421 5 ustar lunar lunar python-cyclone-1.1/demos/auth/authdemo.py 0000755 0001750 0001750 00000004752 12124336260 017614 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import cyclone.auth
import cyclone.escape
import cyclone.web
from twisted.python import log
from twisted.internet import reactor
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/auth/login", AuthHandler),
(r"/auth/logout", LogoutHandler),
]
settings = dict(
cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
debug=True,
login_url="/auth/login",
)
cyclone.web.Application.__init__(self, handlers, **settings)
class BaseHandler(cyclone.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if not user_json:
return None
return cyclone.escape.json_decode(user_json)
class MainHandler(BaseHandler):
@cyclone.web.authenticated
def get(self):
name = cyclone.escape.xhtml_escape(self.current_user["name"])
self.write("Hello, " + name)
self.write("
Log out")
class AuthHandler(BaseHandler, cyclone.auth.GoogleMixin):
@cyclone.web.asynchronous
def get(self):
if self.get_argument("openid.mode", None):
self.get_authenticated_user(self._on_auth)
return
self.authenticate_redirect()
def _on_auth(self, user):
if not user:
raise cyclone.web.HTTPError(500, "Google auth failed")
self.set_secure_cookie("user", cyclone.escape.json_encode(user))
self.redirect("/")
class LogoutHandler(BaseHandler):
def get(self):
self.clear_cookie("user")
self.redirect("/")
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/pycket/ 0000755 0001750 0001750 00000000000 12124336260 015757 5 ustar lunar lunar python-cyclone-1.1/demos/pycket/README.md 0000644 0001750 0001750 00000000601 12124336260 017233 0 ustar lunar lunar # pycket Demo
[pycket] is a session library, written for use with Redis or Memcached, and Tornado web server. It works equally well with cyclone.
[pycket]: https://github.com/diogobaeder/pycket
This demo shows how to set, get, and delete sessions stored persistently in redis (though it can easily be made to use memcached). See the [pycket] page for additional documentation.
python-cyclone-1.1/demos/pycket/pycketdemo.py 0000755 0001750 0001750 00000005743 12124336260 020511 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import cyclone.auth
import cyclone.escape
import cyclone.web
from twisted.python import log
from twisted.internet import reactor
from pycket.session import SessionMixin
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/auth/login", AuthHandler),
(r"/auth/logout", LogoutHandler),
]
settings = dict(
cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
debug=True,
login_url="/auth/login",
logout_url="/auth/logout",
)
settings['pycket'] = {
'engine': 'redis',
'storage': {
'host': 'localhost',
'port': 6379,
'db_sessions': 10,
'db_notifications': 11
}
}
cyclone.web.Application.__init__(self, handlers, **settings)
class BaseHandler(cyclone.web.RequestHandler, SessionMixin):
def get_current_user(self):
user = self.session.get('user')
if not user:
return None
return user
class MainHandler(BaseHandler):
@cyclone.web.authenticated
def get(self):
name = cyclone.escape.xhtml_escape(self.current_user)
self.write("Hello, " + name)
self.write("
Log out")
class AuthHandler(BaseHandler, SessionMixin):
def get(self):
self.write('')
def post(self):
username = self.get_argument('username')
if not username:
self.write('')
else:
self.session.set('user', username)
self.redirect('/')
class LogoutHandler(BaseHandler, SessionMixin):
def get(self):
self.session.delete('user')
self.redirect("/")
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/ssl/ 0000755 0001750 0001750 00000000000 12124336260 015261 5 ustar lunar lunar python-cyclone-1.1/demos/ssl/mkcert.sh 0000755 0001750 0001750 00000000517 12124336260 017110 0 ustar lunar lunar #!/bin/bash
echo -- key
openssl genrsa -des3 -out server.key 1024
echo -- csr
openssl req -new -key server.key -out server.csr
echo -- remove passphrase
cp server.key orig.server.key
openssl rsa -in orig.server.key -out server.key
echo -- generate crt
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
python-cyclone-1.1/demos/ssl/helloworld_ssl.py 0000755 0001750 0001750 00000002611 12124336260 020672 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.web
import sys
from twisted.internet import reactor
from twisted.internet import ssl
from twisted.python import log
class MainHandler(cyclone.web.RequestHandler):
def get(self):
self.write("Hello, %s" % self.request.protocol)
def main():
log.startLogging(sys.stdout)
application = cyclone.web.Application([
(r"/", MainHandler)
])
interface = "127.0.0.1"
reactor.listenTCP(8888, application, interface=interface)
reactor.listenSSL(8443, application,
ssl.DefaultOpenSSLContextFactory("server.key",
"server.crt"),
interface=interface)
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/ssl/README 0000644 0001750 0001750 00000000377 12124336260 016150 0 ustar lunar lunar create a pair of certs:
$ mkcert (some questions, write down your passphrase, etc)
$ python helloworld_ssl.py
point your browser to https://localhost:8443
try using cyclone's twisted plugin:
$ twistd -n cyclone --ssl-app=helloworld_simple.Application
python-cyclone-1.1/demos/ssl/helloworld_simple.py 0000755 0001750 0001750 00000001705 12124336260 021365 0 ustar lunar lunar # coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Run: twistd -n cyclone --ssl-app helloworld_simple.Application
# For more info: twistd -n cyclone --help
import cyclone.web
class MainHandler(cyclone.web.RequestHandler):
def get(self):
self.write("Hello, %s" % self.request.protocol)
Application = lambda: cyclone.web.Application([(r"/", MainHandler)])
python-cyclone-1.1/demos/rpc/ 0000755 0001750 0001750 00000000000 12124336260 015244 5 ustar lunar lunar python-cyclone-1.1/demos/rpc/xmlrpc_client.py 0000644 0001750 0001750 00000001623 12124336260 020463 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import xmlrpclib
srv = xmlrpclib.Server("http://localhost:8888/xmlrpc")
print "echo:", srv.echo("hello world!")
print "sort:", srv.sort(["foo", "bar"])
print "count:", srv.count(["foo", "bar"])
print "geoip_lookup:\n", srv.geoip_lookup("google.com")
python-cyclone-1.1/demos/rpc/rpcdemo.py 0000755 0001750 0001750 00000004016 12124336260 017253 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import cyclone.httpclient
import cyclone.jsonrpc
import cyclone.xmlrpc
from twisted.python import log
from twisted.internet import defer, reactor
class XmlrpcHandler(cyclone.xmlrpc.XmlrpcRequestHandler):
allowNone = True
def xmlrpc_echo(self, text):
return text
def xmlrpc_sort(self, items):
return sorted(items)
def xmlrpc_count(self, items):
return len(items)
@defer.inlineCallbacks
def xmlrpc_geoip_lookup(self, address):
result = yield cyclone.httpclient.fetch(
"http://freegeoip.net/xml/%s" % address.encode("utf-8"))
defer.returnValue(result.body)
class JsonrpcHandler(cyclone.jsonrpc.JsonrpcRequestHandler):
def jsonrpc_echo(self, text):
return text
def jsonrpc_sort(self, items):
return sorted(items)
def jsonrpc_count(self, items):
return len(items)
@defer.inlineCallbacks
def jsonrpc_geoip_lookup(self, address):
result = yield cyclone.httpclient.fetch(
"http://freegeoip.net/json/%s" % address.encode("utf-8"))
defer.returnValue(result.body)
def main():
log.startLogging(sys.stdout)
application = cyclone.web.Application([
(r"/xmlrpc", XmlrpcHandler),
(r"/jsonrpc", JsonrpcHandler),
])
reactor.listenTCP(8888, application)
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/rpc/jsonrpc_sync_client.py 0000644 0001750 0001750 00000002350 12124336260 021666 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import urllib
def request(url, func, *args):
req = json.dumps({"method": func, "params": args, "id": 1})
result = urllib.urlopen(url, req).read()
try:
response = json.loads(result)
except:
return "error: %s" % result
else:
return response.get("result", response.get("error"))
url = "http://localhost:8888/jsonrpc"
print "echo:", request(url, "echo", "foo bar")
print "sort:", request(url, "sort", ["foo", "bar"])
print "count:", request(url, "count", ["foo", "bar"])
print "geoip_lookup:", request(url, "geoip_lookup", "google.com")
python-cyclone-1.1/demos/rpc/jsonrpc_async_client.py 0000644 0001750 0001750 00000002160 12124336260 022026 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.httpclient
from twisted.internet import defer, reactor
@defer.inlineCallbacks
def main():
cli = cyclone.httpclient.JsonRPC("http://localhost:8888/jsonrpc")
print "echo:", (yield cli.echo("foo bar"))
print "sort:", (yield cli.sort(["foo", "bar"]))
print "count:", (yield cli.count(["foo", "bar"]))
print "geoip_lookup:", (yield cli.geoip_lookup("google.com"))
reactor.stop()
if __name__ == "__main__":
main()
reactor.run()
python-cyclone-1.1/demos/facebook/ 0000755 0001750 0001750 00000000000 12124336260 016231 5 ustar lunar lunar python-cyclone-1.1/demos/facebook/templates/ 0000755 0001750 0001750 00000000000 12124336260 020227 5 ustar lunar lunar python-cyclone-1.1/demos/facebook/templates/stream.html 0000644 0001750 0001750 00000001461 12124336260 022412 0 ustar lunar lunar
Tornado Facebook Stream Demo
python-cyclone-1.1/demos/facebook/README 0000644 0001750 0001750 00000000527 12124336260 017115 0 ustar lunar lunar Running the Tornado Facebook example
=====================================
To work with the provided Facebook api key, this example must be
accessed at http://localhost:8888/ to match the Connect URL set in the
example application.
To use any other domain, a new Facebook application must be registered
with a Connect URL set to that domain.
python-cyclone-1.1/demos/facebook/uimodules.py 0000644 0001750 0001750 00000001410 12124336260 020605 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.web
class Entry(cyclone.web.UIModule):
def render(self):
return '
ENTRY
'
python-cyclone-1.1/demos/facebook/facebook.py 0000755 0001750 0001750 00000007501 12124336260 020362 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import os.path
import cyclone.auth
import cyclone.escape
import cyclone.web
from twisted.python import log
from twisted.internet import reactor
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
(r"/auth/login", AuthLoginHandler),
(r"/auth/logout", AuthLogoutHandler),
]
settings = dict(
cookie_secret="12oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
login_url="/auth/login",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
facebook_api_key="9e2ada1b462142c4dfcc8e894ea1e37c",
facebook_secret="32fc6114554e3c53d5952594510021e2",
ui_modules={"Post": PostModule},
debug=True,
)
cyclone.web.Application.__init__(self, handlers, **settings)
class BaseHandler(cyclone.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("user")
if not user_json:
return None
return cyclone.escape.json_decode(user_json)
class MainHandler(BaseHandler, cyclone.auth.FacebookMixin):
@cyclone.web.authenticated
@cyclone.web.asynchronous
def get(self):
self.facebook_request(
method="stream.get",
callback=self._on_stream,
session_key=self.current_user["session_key"])
def _on_stream(self, stream):
if stream is None:
# Session may have expired
self.redirect("/auth/login")
return
# Turn profiles into a dict mapping id => profile
stream["profiles"] = dict((p["id"], p) for p in stream["profiles"])
self.render("stream.html", stream=stream)
class AuthLoginHandler(BaseHandler, cyclone.auth.FacebookMixin):
@cyclone.web.asynchronous
def get(self):
if self.get_argument("session", None):
self.get_authenticated_user(self._on_auth)
return
self.authorize_redirect("read_stream")
def _on_auth(self, user):
if not user:
raise cyclone.web.HTTPError(500, "Facebook auth failed")
self.set_secure_cookie("user", cyclone.escape.json_encode(user))
self.redirect(self.get_argument("next", "/"))
class AuthLogoutHandler(BaseHandler, cyclone.auth.FacebookMixin):
@cyclone.web.asynchronous
def get(self):
self.clear_cookie("user")
if not self.current_user:
self.redirect(self.get_argument("next", "/"))
return
self.facebook_request(
method="auth.revokeAuthorization",
callback=self._on_deauthorize,
session_key=self.current_user["session_key"])
def _on_deauthorize(self, response):
self.redirect(self.get_argument("next", "/"))
class PostModule(cyclone.web.UIModule):
def render(self, post, actor):
return self.render_string("modules/post.html", post=post, actor=actor)
def main():
reactor.listenTCP(8888, Application())
reactor.run()
if __name__ == "__main__":
log.startLogging(sys.stdout)
main()
python-cyclone-1.1/demos/facebook/static/ 0000755 0001750 0001750 00000000000 12124336260 017520 5 ustar lunar lunar python-cyclone-1.1/demos/facebook/static/facebook.js 0000644 0001750 0001750 00000000000 12124336260 021615 0 ustar lunar lunar python-cyclone-1.1/demos/facebook/static/facebook.css 0000644 0001750 0001750 00000002724 12124336260 022010 0 ustar lunar lunar /*
* Copyright 2009 Facebook
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
body {
background: white;
color: black;
margin: 15px;
}
body,
input,
textarea {
font-family: "Lucida Grande", Tahoma, Verdana, sans-serif;
font-size: 10pt;
}
table {
border-collapse: collapse;
border: 0;
}
td {
border: 0;
padding: 0;
}
img {
border: 0;
}
a {
text-decoration: none;
color: #3b5998;
}
a:hover {
text-decoration: underline;
}
.post {
border-bottom: 1px solid #eeeeee;
min-height: 50px;
padding-bottom: 10px;
margin-top: 10px;
}
.post .picture {
float: left;
}
.post .picture img {
height: 50px;
width: 50px;
}
.post .body {
margin-left: 60px;
}
.post .media img {
border: 1px solid #cccccc;
padding: 3px;
}
.post .media:hover img {
border: 1px solid #3b5998;
}
.post a.actor {
font-weight: bold;
}
.post .meta {
font-size: 11px;
}
.post a.permalink {
color: #777777;
}
#body {
max-width: 700px;
margin: auto;
}
python-cyclone-1.1/demos/helloworld/ 0000755 0001750 0001750 00000000000 12124336260 016633 5 ustar lunar lunar python-cyclone-1.1/demos/helloworld/helloworld.py 0000755 0001750 0001750 00000002102 12124336260 021356 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.web
import sys
from twisted.internet import reactor
from twisted.python import log
class MainHandler(cyclone.web.RequestHandler):
def get(self):
self.write("Hello, world")
if __name__ == "__main__":
application = cyclone.web.Application([
(r"/", MainHandler)
])
log.startLogging(sys.stdout)
reactor.listenTCP(8888, application, interface="127.0.0.1")
reactor.run()
python-cyclone-1.1/demos/helloworld/nginx.conf 0000644 0001750 0001750 00000001021 12124336260 020617 0 ustar lunar lunar upstream backend {
server localhost:9901;
server localhost:9902;
server localhost:9903;
server localhost:9904;
# server unix:/tmp/cyclone.1;
# server unix:/tmp/cyclone.2;
}
server {
listen 80;
server_name localhost;
#access_log /var/log/nginx/access.log;
location / {
proxy_pass http://backend;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
python-cyclone-1.1/demos/helloworld/helloworld_bottle.py 0000755 0001750 0001750 00000001467 12124336260 022744 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
from cyclone.bottle import run, route
@route("/")
def index(web):
web.write("Hello, world")
run(host="127.0.0.1", port=8888, log=sys.stdout)
python-cyclone-1.1/demos/helloworld/helloworld_twistd.py 0000755 0001750 0001750 00000002104 12124336260 022756 0 ustar lunar lunar #!/usr/bin/env twistd -ny
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import cyclone.web
from twisted.application import internet
from twisted.application import service
class MainHandler(cyclone.web.RequestHandler):
def get(self):
self.write("Hello, world")
webapp = cyclone.web.Application([
(r"/", MainHandler)
])
application = service.Application("helloworld_twistd")
server = internet.TCPServer(8888, webapp, interface="127.0.0.1")
server.setServiceParent(application)
python-cyclone-1.1/demos/helloworld/helloworld_simple.py 0000755 0001750 0001750 00000002350 12124336260 022734 0 ustar lunar lunar # coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Start the server:
# cyclone run helloworld_simple.py
#
# For more info:
# cyclone run --help
#
# If this server is reverse proxied by nginx, some headers must be added to
# the request. Check our sample nginx.conf for details, and set xheaders=True
# to make cyclone use those headers.
import cyclone.web
class MainHandler(cyclone.web.RequestHandler):
def get(self):
self.write("Hello, world")
class Application(cyclone.web.Application):
def __init__(self):
cyclone.web.Application.__init__(self, [(r"/", MainHandler)],
xheaders=False)
python-cyclone-1.1/demos/digest_auth/ 0000755 0001750 0001750 00000000000 12124336260 016760 5 ustar lunar lunar python-cyclone-1.1/demos/digest_auth/digest.py 0000644 0001750 0001750 00000014472 12124336260 020621 0 ustar lunar lunar from cyclone.web import *
from hashlib import md5
class DigestAuthMixin(object):
def apply_checksum(self, data):
return md5(data).hexdigest()
def apply_digest(self, secret, data):
return self.apply_checksum(secret + ":" + data)
def A1(self, algorithm, auth_pass):
"""
If 'algorithm' is "MD5" or unset, A1 is:
A1 = unq(username-value) ":" unq(realm-value) ":" passwd
if 'algorithm' is 'MD5-Sess', A1 is:
A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
":" unq(nonce-value) ":" unq(cnonce-value)
"""
username = self.params["username"]
if algorithm == 'MD5' or not algorithm:
return "%s:%s:%s" % (username, self.realm, auth_pass)
elif algorithm == 'MD5-Sess':
return self.apply_checksum('%s:%s:%s:%s:%s' % \
(username,
self.realm,
auth_pass,
self.params['nonce'],
self.params['cnonce']))
def A2(self):
"""
If the "qop" directive's value is "auth" or is unspecified, then A2 is:
A2 = Method ":" digest-uri-value
Else,
A2 = Method ":" digest-uri-value ":" H(entity-body)
"""
if self.params['qop'] == 'auth' or not self.params['qop']:
return self.request.method + ":" + self.request.uri
elif self.params['qop'] == 'auth-int':
#print "UNSUPPORTED 'qop' METHOD\n"
return ":".join([self.request.method,
self.request.uri,
self.apply_checksum(self.request.body)])
else:
print "A2 GOT BAD VALUE FOR 'qop': %s\n" % self.params['qop']
def response(self, auth_pass):
if 'qop' in self.params:
auth_comps = [self.params['nonce'],
self.params['nc'],
self.params['cnonce'],
self.params['qop'],
self.apply_checksum(self.A2())]
return self.apply_digest(self.apply_checksum( \
self.A1(self.params.get('algorithm'),
auth_pass)),
':'.join(auth_comps))
else:
return self.apply_digest(self.apply_checksum( \
self.A1(self.params.get('algorithm'),
auth_pass)),
':'.join([self.params["nonce"],
self.apply_checksum(self.A2())]))
def _parse_header(self, authheader):
try:
n = len("Digest ")
authheader = authheader[n:].strip()
items = authheader.split(", ")
keyvalues = [i.split("=", 1) for i in items]
keyvalues = [(k.strip(), v.strip().replace('"', '')) for k, v in keyvalues]
self.params = dict(keyvalues)
except:
self.params = []
def _create_nonce(self):
return md5("%d:%s" % (time.time(), self.realm)).hexdigest()
def createAuthHeader(self):
self.set_status(401)
nonce = self._create_nonce()
self.set_header("WWW-Authenticate", "Digest algorithm=MD5, realm=%s, qop=auth, nonce=%s" % (self.realm, nonce))
self.finish()
return False
def get_authenticated_user(self, get_creds_callback, realm):
creds = None
expected_response = None
actual_response = None
auth = None
if not hasattr(self,'realm'):
self.realm = realm
try:
auth = self.request.headers.get('Authorization')
if not auth or not auth.startswith('Digest '):
return self.createAuthHeader()
else:
self._parse_header(auth)
required_params = ['username', 'realm', 'nonce', 'uri', 'response', 'qop', 'nc', 'cnonce']
for k in required_params:
if k not in self.params:
print "REQUIRED PARAM %s MISSING\n" % k
return self.createAuthHeader()
elif not self.params[k]:
print "REQUIRED PARAM %s IS NONE OR EMPTY\n" % k
return self.createAuthHeader()
else:
continue
creds = get_creds_callback(self.params['username'])
if not creds:
# the username passed to get_creds_callback didn't
# match any valid users.
self.createAuthHeader()
else:
expected_response = self.response(creds['auth_password'])
actual_response = self.params['response']
print "Expected: %s" % expected_response
print "Actual: %s" % actual_response
if expected_response and actual_response:
if expected_response == actual_response:
self._current_user = self.params['username']
print "Digest Auth user '%s' successful for realm '%s'. URI: '%s', IP: '%s'" % (self.params['username'], self.realm, self.request.uri, self.request.remote_ip)
return True
else:
self.createAuthHeader()
except Exception as out:
print "FELL THROUGH: %s\n" % out
print "AUTH HEADERS: %s" % auth
print "SELF.PARAMS: ",self.params,"\n"
print "CREDS: ", creds
print "EXPECTED RESPONSE: %s" % expected_response
print "ACTUAL RESPONSE: %s" % actual_response
return self.createAuthHeader()
def digest_auth(realm, auth_func):
"""A decorator used to protect methods with HTTP Digest authentication.
"""
def digest_auth_decorator(func):
def func_replacement(self, *args, **kwargs):
# 'self' here is the RequestHandler object, which is inheriting
# from DigestAuthMixin to get 'get_authenticated_user'
if self.get_authenticated_user(auth_func, realm):
return func(self, *args, **kwargs)
return func_replacement
return digest_auth_decorator
python-cyclone-1.1/demos/digest_auth/README.md 0000644 0001750 0001750 00000001663 12124336260 020245 0 ustar lunar lunar # Cyclone auth digest example
This is a port of https://github.com/bkjones/curtain (Apache License)
## Basic usage
### import digest.py at the top of your views file
import digest
### subclass digest.DigestAuthMixin in authenticated views
class MainHandler(digest.DigestAuthMixin, cyclone.web.RequestHandler):
### define a password store. This function is expected to return a hash containing
auth\_username and auth\_password.
def passwordz(username):
creds = {
'auth_username': 'test',
'auth_password': 'foobar'
}
if username == creds['auth_username']:
return creds
### decorate views (get/post) with digest.digest_auth. Passing in authentication realm and password store.
If authenticated, `self.current_user` will be properly set.
@digest.digest_auth('Cyclone', passwordz)
def get(self):
self.write("Hello %s" % (self.current_user))
python-cyclone-1.1/demos/digest_auth/authdemo.py 0000755 0001750 0001750 00000003736 12124336260 021154 0 ustar lunar lunar #!/usr/bin/env python
# coding: utf-8
#
# Copyright 2010 Alexandre Fiori
# based on the original Tornado by Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import hashlib
import time
import cyclone.escape
import cyclone.web
import digest
from twisted.python import log
from twisted.internet import reactor
class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
]
settings = dict(
cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
debug=True,
login_url="/auth/login",
)
cyclone.web.Application.__init__(self, handlers, **settings)
class MainHandler(digest.DigestAuthMixin, cyclone.web.RequestHandler):
# forces this handler to parse only GET / PROPFIND methods
SUPPORTED_METHODS = ("GET", "PROPFIND")
def passwordz(username):
creds = {
'auth_username': 'test',
'auth_password': 'foobar'
}
if username == creds['auth_username']:
return creds
@digest.digest_auth('Cyclone', passwordz)
def get(self):
self.write("Hello %s" % (self.current_user))
@digest.digest_auth('Cyclone', passwordz)
def propfind(self):
self.write("Hello %s" % (self.current_user))
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
reactor.run()
if __name__ == "__main__":
main()
python-cyclone-1.1/demos/fbgraphapi/ 0000755 0001750 0001750 00000000000 12124336260 016563 5 ustar lunar lunar python-cyclone-1.1/demos/fbgraphapi/templates/ 0000755 0001750 0001750 00000000000 12124336260 020561 5 ustar lunar lunar python-cyclone-1.1/demos/fbgraphapi/templates/stream.html 0000644 0001750 0001750 00000001445 12124336260 022746 0 ustar lunar lunar
Tornado Facebook Stream Demo