Skip to content
Browse files

Merge branch 'twisted-plugin'

  • Loading branch information...
2 parents f8bc5d0 + 61a49c7 commit 5c43e3b5841056edf32c2038ea56469510b5505c @fiorix committed Jun 11, 2012
Showing with 939 additions and 564 deletions.
  1. +1 −0 .gitignore
  2. +0 −8 CHANGELOG.txt
  3. +446 −244 README.rst
  4. +31 −23 appskel/README.rst
  5. +1 −9 appskel/frontend/template/index.html
  6. +0 −45 appskel/frontend/template/login.html
  7. +0 −48 appskel/frontend/template/signup.html
  8. +0 −52 appskel/frontend/template/useradmin.html
  9. +14 −0 appskel/modname/__init__.py
  10. +20 −9 appskel/modname/config.py
  11. +22 −8 appskel/modname/utils.py
  12. +14 −0 appskel/modname/views.py
  13. +18 −1 appskel/modname/web.py
  14. +14 −0 appskel/scripts/cookie_secret.py
  15. +4 −7 appskel/scripts/debian-init.d
  16. +4 −7 appskel/scripts/debian-multicore-init.d
  17. +14 −0 appskel/scripts/localefix.py
  18. +5 −0 appskel/start.sh
  19. +0 −31 appskel/twisted/plugins/modname_plugin.py
  20. +1 −1 cyclone/__init__.py
  21. +3 −3 cyclone/app.py
  22. BIN cyclone/appskel.zip
  23. +39 −32 cyclone/redis.py
  24. +6 −0 debian/changelog
  25. +6 −4 debian/control
  26. +0 −1 debian/docs
  27. +29 −0 debian/postinst
  28. +8 −0 debian/postrm
  29. +0 −1 demos/helloworld/helloworld_bottle.py
  30. +31 −0 demos/helloworld/helloworld_simple.py
  31. +2 −1 demos/helloworld/helloworld_twistd.py
  32. +28 −6 demos/locale/README
  33. BIN demos/locale/frontend/locale/es_ES/LC_MESSAGES/mytest.mo
  34. BIN demos/locale/frontend/locale/pt_BR/LC_MESSAGES/mytest.mo
  35. +2 −0 demos/locale/frontend/template/hello.txt
  36. +1 −3 demos/locale/frontend/template/index.html
  37. +12 −0 demos/locale/localedemo.py
  38. +14 −5 demos/locale/mytest_es_ES.po
  39. +14 −5 demos/locale/mytest_pt_BR.po
  40. +10 −0 demos/ssl/README
  41. +29 −0 demos/ssl/helloworld_simple.py
  42. +1 −1 demos/ssl/helloworld_ssl.py
  43. +12 −9 setup.py
  44. +83 −0 twisted/plugins/cyclone_plugin.py
View
1 .gitignore
@@ -5,3 +5,4 @@
build
dist
cyclone.egg-info
+dropin.cache
View
8 CHANGELOG.txt
@@ -1,8 +0,0 @@
-
-0.3 --
-
-WebSocket support (beta)
-support for sending e-mail
-fixed error handler bug
-
-0.1 -- Initial version
View
690 README.rst
@@ -1,327 +1,467 @@
=======
cyclone
=======
-:Info: See `github <http://github.com/fiorix/cyclone>`_ for the latest source.
+:Info: See `github <https://github.com/fiorix/cyclone>`_ for the latest source.
:Author: Alexandre Fiori <fiorix@gmail.com>
+
About
=====
-cyclone is a low-level network toolkit, which provides support for HTTP 1.1 in an API very similar to the one implemented by the `Tornado <http://tornadoweb.org>`_ web server.
+cyclone is a clone of facebook's `Tornado <http://tornadoweb.org>`_, on top of
+`Twisted <http://twistedmatrix.com>`_.
+
+Although cyclone and tornado are very similar, cyclone leverages all
+enterprise class features of Twisted, and more. It is extremely stable, and
+ready for production.
+
+
+Features
+--------
+
+**cyclone is a Twisted protocol**. Thus, it may be used in conjunction with
+any other protocol implemented in Twisted. The same server can deliver HTTP
+content on one port, SSH on another, and it can keep a pool of persistent,
+non-blocking connections to several databases. All in a single process.
+
+Web apps built with cyclone are **fully translatable**. The localization system
+is based on `Gettext <http://www.gnu.org/software/gettext/>`_. It's possible
+to translate strings in the server code, as well as text and HTML templates.
+
+**Secure**. It can deliver HTTP and **HTTPS (SSL)** on the same server, with
+individual request routing mechanism. Also, cyclone supports the standard HTTP
+Authentication, which can be used to implement HTTP Basic, Digest, or any
+other hand crafted authentication system, like `Amazon's S3
+<http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html>`_.
+
+**API friendly**. cyclone is very useful for writing web services, RESTful or
+any other type of web API. Features like HTTP Keep-Alive and XSRF can be
+enabled or disabled per request, which means the server can have different
+behaviour when communicating with browsers, or other custom HTTP clients.
+
+Ships with a full featured, **non-blocking HTTP client**, using
+`TwistedWeb <http://twistedmatrix.com/trac/wiki/TwistedWeb>`_.
+
+**E-mail, the easy way**. With cyclone, the web server can connect to multiple
+SMTP servers, on demand. The e-mail API is simple, support client connections
+with SSL and TLS, and provide you with an easy way to customize messages,
+and attachments.
+
+Supports **multiple protocols**: built-in support for XML-RPC, JSON-RPC,
+WebSocket and SSE. And, many other protocols can be used in cyclone-based web
+servers, like the `Event Socket <http://wiki.freeswitch.org/wiki/Event_Socket>`_
+protocol of `Freeswitch <http://freeswitch.org/>`_, a highly scalable soft
+switch, telephony platform.
+
+**Storage engines**: cyclone ships with built-in support for inline SQLite,
+and `Redis <http://redis.io/>`_. MongoDB and many other NoSQL are supported
+with 3rd party libraries. All other RDBMs supported by Python are available as
+well, like MySQL and PostgreSQL, via `twisted.enterprise.adbapi
+<http://twistedmatrix.com/documents/current/core/howto/rdbms.html>`_.
+Connection pools can persist, and be efficiently used by all requests. It can
+also auto-reconnect automatically, making it totally fault-tolerant on database
+errors and disconnections.
+
+**For the simple, and the complex**: cyclone-based web apps can be written as
+`Bottle <http://bottlepy.org/>`_, or Tornado. A 10-line script can handle
+thousands of connections per second, with very low CPU and memory footprint.
+For more complex applications, cyclone offers an app template out of the box,
+with a configuration file, database support, translation, and deployment
+scheme, fully integrated with Debian GNU/Linux. Via ``twistd``, cyclone-based
+apps can be easily deployed in any operating system, with customized log and
+pid files, reactor, permissions, and many other settings.
+
+**Documented**, here and there, mostly by sample code. Features are either
+documented here, or in the demos. Check out `the demos
+<https://github.com/fiorix/cyclone/tree/master/demos>`_.
+For some other stuff, we use the Tornado docs. Like `HTML templates
+<http://www.tornadoweb.org/documentation/template.html>`_, `Escaping and
+string manipulation <http://www.tornadoweb.org/documentation/escape.html>`_,
+`Locale <http://www.tornadoweb.org/documentation/locale.html>`_, and
+`OpenID and Oauth <http://www.tornadoweb.org/documentation/auth.html>`_.
-Key differences between cyclone and tornado
--------------------------------------------
-- cyclone is a `Twisted <http://twistedmatrix.com>`_ protocol, therefore it may be used in conjunction with any other protocol implemented in Twisted.
-- Localization is based on the standard `Gettext <http://www.gnu.org/software/gettext/>`_ instead of the CSV implementation in the original tornado. The gettext support has been merged back into Tornado.
-- Asynchronous HTTP client based on `TwistedWeb <http://twistedmatrix.com/trac/wiki/TwistedWeb>`_. It's not compatible with with one provided by Tornado - which is based on `PyCurl <http://pycurl.sourceforge.net/>`_. (The HTTP server code is NOT based on TwistedWeb, for several reasons)
-- Support for sending e-mails based on `TwistedMail <http://twistedmatrix.com/trac/wiki/TwistedMail>`_, with authentication and TLS, plus an easy way to create plain text or HTML messages, and attachments. (see the `e-mail demo <http://github.com/fiorix/cyclone/tree/master/demos/email>`_)
-- Support for HTTP Authentication. (see the `authentication demo <http://github.com/fiorix/cyclone/tree/master/demos/httpauth/>`_)
-- Support for Bottle-like API, Twistd application and Twistd plugin. (see the `hello world demos <https://github.com/fiorix/cyclone/tree/master/demos/helloworld>`_)
-- Built-in support for XMLRPC and JsonRPC (`rpc demo <http://github.com/fiorix/cyclone/tree/master/demos/rpc/>`_), Server Sent Events (`sse demo <https://github.com/fiorix/cyclone/tree/master/demos/sse>`_) and WebSocket (`websocket demo <http://github.com/fiorix/cyclone/tree/master/demos/websocket/>`_).
-- Built-in support for `Redis <http://code.google.com/p/redis/>`_, based on `txredisapi <http://github.com/fiorix/txredisapi>`_. We usually need an in-memory caching server like memcache for web applications. However, we prefer redis over memcache because it supports more operations like pubsub, various data types like sets, hashes (python dict), and persistent storage. (see the `redis demo <http://github.com/fiorix/cyclone/tree/master/demos/redis/>`_ for details.)
-- Built-in support for inline SQLite in an API similar to the ``twisted.enterprise.adbapi``.
-- XSRF control per RequestHandler. Basically, adding ``no_xsrf = True`` in a RequestHandler cause it ignore the ``_xsrf`` cookie. This is useful when exposing handlers to HTTP clients other than browsers in the same server that is serving normal HTML content with the ``_xsrf`` cookie and form element.
+Benchmarks
+----------
-Advantages of being a Twisted Protocol
---------------------------------------
+Check out the `benchmarks <http://wiki.github.com/fiorix/cyclone/benchmarks>`_
+page.
-- Easy deployment of applications, using `twistd <http://twistedmatrix.com/documents/current/core/howto/basics.html>`_.
-- RDBM (MySQL, PostgreSQL, etc) support via: `twisted.enterprise.adbapi <http://twistedmatrix.com/documents/current/core/howto/rdbms.html>`_.
-- NoSQL support for MongoDB (`TxMongo <http://github.com/fiorix/mongo-async-python-driver>`_) and Redis (`TxRedisAPI <http://github.com/fiorix/txredisapi>`_).
-- May combine many more functionality within the webserver: sending emails, communicating with message brokers, etc...
-Benchmarks
+Installing
----------
-Check out the `benchmarks <http://wiki.github.com/fiorix/cyclone/benchmarks>`_ page.
+cyclone can be installed from ``pip`` or ``easy_install``::
+ $ pip install cyclone
-Development and Deployment
-==========================
+It has no external dependencies other than Twisted 10 or newer, running
+on Python 2.6 or 2.7, or PyPy 1.8 or 1.9.
-Twisted Plugin is the recommended way to go for production. Create new projects
-right away off of the generic application skeleton shipped with cyclone::
+It runs on Python 2.5 too, but requires the ``simplejson`` module. Also, it
+runs on PyPy 1.8 as long as there's no SSL.
- $ python -m cyclone.app --help
+On most Linux distributions, such as Debian, twisted is splitted in several
+packages. In this case, you'll need at least ``python-twisted-core``,
+``python-twisted-web``, and optionally ``python-twisted-mail`` - if you want to
+be able to send e-mails straight from cyclone apps.
- use: cyclone/app.py [options]
- Options:
- -h --help Show this help.
- -g --git Use git's name and email settings, and create a git repo on target
- -p --project=NAME Create new cyclone project.
- -m --modname=NAME Use another name for the module [default: project_name]
- -v --version=VERSION Set project version [default: 0.1]
- -s --set-pkg-version Set version on package name [default: False]
- -t --target=PATH Set path where project is created [default: ./]
+Source code is available on `https://github.com/fiorix/cyclone
+<https://github.com/fiorix/cyclone>`_, and ships with very useful and
+comprehensive demo applications.
-Example::
+Check out the `demos <https://github.com/fiorix/cyclone/tree/master/demos>`_.
- python -m cyclone.app -p foobar
- cd foobar
- twistd -n foobar
-Check out the README.rst in the new project's directory for detailed information.
-It ships with debian init scripts for single or multiple instances (one per cpu core)
-to help make deployment as simple as possible.
+Running, and deploying
+----------------------
+cyclone apps can be run by ``twistd``, and it's the easiest way to start
+playing with the framework.
+
+This is ``hello.py``::
+
+ import cyclone.web
+
+
+ class MainHandler(cyclone.web.RequestHandler):
+ def get(self):
+ self.write("hello, world")
-Tips and Tricks
-===============
-As a clone, the API implemented in cyclone is almost the same of Tornado. Therefore you may use the `Tornado Documentation <http://www.tornadoweb.org/documentation>`_ for stuff like templates and so on.
+ Application = lambda: cyclone.web.Application([(r"/", MainHandler)])
-The snippets below will show some tips and tricks regarding the few differences between the two.
+A dev server can be started like this::
-Hello World
------------
+ $ twistd -n cyclone -r hello.Application
+ Log opened.
+ reactor class: twisted.internet.selectreactor.SelectReactor.
+ cyclone.web.Application starting on 8888
+
+Due to the power of ``twistd``, cyclone apps can be easily deployed in
+production, with all the basic features of standard daemons::
+
+ $ twistd --uid=www-data --gid=www-data --reactor=epoll \
+ --logfile=/var/log/hello.log --pidfile=/var/run/hello.log \
+ cyclone --port=80 --listen=0.0.0.0 --app=hello.Application
+
+Process permissions are properly set, log files rotate automatically,
+syslog is also an option, pid files are generated so other subsystems can
+use it on start/stop scripts.
+
+Setting up SSL on the same server is just a matter or creating a certificate
+and adding ``--ssl-app=hello.Application`` to the command line. It could easily
+point to yet another ``Application`` class, with a completely different URL
+routing. Check out the `SSL demo
+<https://github.com/fiorix/cyclone/tree/master/demos/ssl>`_.
+
+Run ``twistd --help`` for more details. Here's a complete list of options
+supported by the cyclone twisted plugin::
+
+ $ twistd cyclone --help
+ Usage: twistd [options] cyclone [options]
+ Options:
+ -p, --port= tcp port to listen on [default: 8888]
+ -l, --listen= interface to listen on [default: 127.0.0.1]
+ -r, --app= cyclone application to run
+ -c, --appopts= arguments to your application
+ --ssl-port= port to listen on for ssl [default: 8443]
+ --ssl-listen= interface to listen on for ssl [default: 127.0.0.1]
+ --ssl-cert= ssl certificate [default: server.crt]
+ --ssl-key= ssl server key [default: server.key]
+ --ssl-app= ssl application (same as --app)
+ --ssl-appopts= arguments to the ssl application
+ --version
+ --help Display this help and exit.
+
+
+Project template
+----------------
+
+cyclone ships with a full featured project template. It helps on avoiding the
+repetitive process of creating a basic project structure, like parsing
+configuration files, setting up database connections, and translation of code
+and HTML templates.
::
- #!/usr/bin/env python
- # coding: utf-8
+ $ python -m cyclone.app --help
- import cyclone.web
- import sys
- from twisted.internet import reactor
- from twisted.python import log
+ use: cyclone.app [options]
+ Options:
+ -h --help Show this help.
+ -p --project=NAME Create new cyclone project.
+ -g --git Use in conjunction with -p to make it a git repository.
+ -m --modname=NAME Use another name for the module [default: project_name]
+ -v --version=VERSION Set project version [default: 0.1]
+ -s --set-pkg-version Set version on package name [default: False]
+ -t --target=PATH Set path where project is created [default: ./]
- class MainHandler(cyclone.web.RequestHandler):
- def get(self):
- self.write("Hello, world")
+Creating new projects can be as simple as running this::
- def main():
- log.startLogging(sys.stdout)
- application = cyclone.web.Application([
- (r"/", MainHandler)
- ])
+ $ python -m cyclone.app -p foobar
- reactor.listenTCP(8888, application, interface="127.0.0.1")
- reactor.run()
+Check README.rst in the new project directory for detailed information on how
+to use it.
- if __name__ == "__main__":
- main()
+The template ships with Debian init scripts for running ``twistd`` as single,
+or multiple instances (one per CPU core) to help make deployments as simple as
+possible.
-Twisted Application and Plugin
-------------------------------
+Tips and Tricks
+===============
-The advantage of being a Twisted Application is that you don't need to care about basic daemon features like forking, creating pid files, changing application's user and group permissions, and selecting the proper reactor within the code.
+As a clone, the API implemented in cyclone is almost the same of Tornado.
+Therefore you may use `Tornado Documentation
+<http://www.tornadoweb.org/documentation>`_ for things like auth, and the
+template engine.
-Instead, the application may be run by ``twistd``, as follows::
+The snippets below will show some tips and tricks regarding the few differences
+between the two.
- for testing:
- /usr/bin/twistd --nodaemon --python=foobar.tac
- for production:
- /usr/bin/twistd --pidfile=/var/run/foobar.pid \
- --logfile=/var/log/foobar.log \
- --uid=nobody --gid=nobody \
- --reactor=epoll \
- --python=foobar.tac
+Deferreds
+---------
-Following is the *Hello World* as a twisted application::
+First things first: you don't need to care about deferreds at all to start
+playing with cyclone. `http://cyclone.io/ssedemo <http://cyclone.io/ssedemo>`_
+is an example.
- # coding: utf-8
- # twisted application: foobar.tac
+However, `Deferreds
+<http://twistedmatrix.com/documents/current/core/howto/defer.html>`_ might
+help take your app to a whole new level.
+
+cyclone uses deferreds extensively, to provide persistent database connections,
+and, generally speaking, to allow web apps to easily communicate with other
+subsystems on demand, while handling HTTP requests.
+
+Here's an example, ``crawler.py``::
+
+ # run: twistd -n cyclone -r crawler.Application
import cyclone.web
- from twisted.application import service, internet
+ from twisted.internet import defer
+ from twisted.web.client import getPage
+
- class IndexHandler(cyclone.web.RequestHandler):
+ class MainHandler(cyclone.web.RequestHandler):
+ @defer.inlineCallbacks
def get(self):
- self.write("hello world")
+ response = yield getPage("http://freegeoip.net/xml/")
+ self.set_header("Content-Type", "text/plain")
+ self.write(response)
- foobar = cyclone.web.Application([(r"/", IndexHandler)])
- application = service.Application("foobar")
- internet.TCPServer(8888, foobar(),
- interface="127.0.0.1").setServiceParent(application)
+ Application = lambda: cyclone.web.Application([("/", MainHandler)], debug=True)
+The example above is an app that makes a new request to ``freegeoip.net`` on
+each request it takes, and respond to this request with whatever it gets from
+freegeoip. All without blocking the server.
-Authenticated and Asynchronous decorators
------------------------------------------
+The exact same concept is used to communicate with databases. Basically, using
+``inlineCallbacks`` eliminates the nightmare of dealing with chained deferreds
+and their responses in different callbacks. This way is simple and
+straightforward.
-Tornado provides decorator functions for asynchronous and authenticated
-methods. Obviously, they're also implemented in cyclone, and yet more
-powerful when combined with a famous Twisted decorator: ``defer.inlineCallbacks``.
+Here is another example, ``delayed.py``::
-The ``cyclone.web.authenticated`` decorator may be combined with ``defer.inlineCallbacks``,
-however, there's a basic rule to use them together. Considering that the authenticated
-decorator will check user credentials, and, depending on the result, it will
-continue processing the request OR redirect the request to the login page,
-it has to be used *before* the ``defer.inlineCallbacks`` to function properly::
+ # run: twistd -n cyclone -r delayed.Application
- class IndexHandler(cyclone.web.RequestHandler):
- @cyclone.web.authenticated
+ import cyclone.web
+ from twisted.internet import defer
+ from twisted.internet import reactor
+
+
+ def sleep(n):
+ d = defer.Deferred()
+ reactor.callLater(5, lambda: d.callback(None))
+ return d
+
+
+ class MainHandler(cyclone.web.RequestHandler):
@defer.inlineCallbacks
def get(self):
- result = yield self.do_download()
- self.write(result)
+ yield sleep(5)
+ self.write("hello, world")
-The ``cyclone.web.asynchronous`` decorator should be used with
-asynchronous handers that don't use ``defer.inlineCallbacks``. This
-decorator will keep the request open until you explicitly call
-``self.finish()`` later on, which is necessary if your handler needs
-to continue writing to the request::
- class Indexhandler(cyclone.web.RequestHandler):
- @cyclone.web.asynchronous
- def get(self):
- download_deferred = self.do_download()
- download_deferred.addCallback(self.process_download)
- return d
+ Application = lambda: cyclone.web.Application([("/", MainHandler)], debug=True)
- def process_download(self, result):
- self.finish(result)
+There are other useful examples in the `demos
+<https://github.com/fiorix/cyclone/tree/master/demos/ssl>`_ directory. Take a
+look at ``demos/email``, ``demos/redis``, and ``demos/httpauth``.
-If you're looking for the Cyclone equivalent of the ``tornado.gen.engine``
-decorator, this is Tornado's version of ``defer.inlineCallbacks``.
-Localization
-------------
+The @asynchronous decorator
+---------------------------
-The ``cyclone.locale`` provides an API based on the Python ``gettext`` module.
+By default, cyclone will terminate the request after it is processed by the
+``RequestHandler``. Consider this code::
-Because of that, there is *one* extra option that may be passed to ``cyclone.locale.load_gettext_translations(path, domain="cyclone")``, which the is the gettext's domain. The default domain is *cyclone*.
+ class MainHandler(cyclone.web.RequestHandler):
+ def get(self):
+ self.write("hello, world")
+
+The above request is always terminated after ``get`` returns. Even if ``get``
+returns a deferred, the request is automatically terminated after the deferred
+is fired.
-Following is a step-by-step guide to implement localization in any cyclone application:
+The ``cyclone.web.asynchronous`` decorator can be used to keep the request
+open until ``self.finish()`` is explicitly called. The request will be in a
+stale state, allowing for sending late, and incremental (chunked) responses.
-1. Create a python script or twisted application with translatable strings::
+Here's an example, ``clock.py``::
- # coding: utf-8
- # twisted application: foobar.tac
+ # run: twistd -n cyclone -r clock.Application
import cyclone.web
- import cyclone.locale
- from twisted.application import service, internet
+ import time
+ from twisted.internet import task
+ from twisted.internet import reactor
- class BaseHandler(cyclone.web.RequestHandler):
- def get_user_locale(self):
- lang = self.get_cookie("lang")
- return cyclone.locale.get(lang)
- class IndexHandler(BaseHandler):
- def get(self):
- self.render("index.html")
+ class MessagesMixin(object):
+ clients = []
- def post(self):
- _ = self.locale.translate
- name = self.get_argument("name")
- self.write(_("the name is: %s" % name))
+ @classmethod
+ def setup(self):
+ task.LoopingCall(MessagesMixin.broadcast).start(1)
+
+ @classmethod
+ def broadcast(self):
+ for req in MessagesMixin.clients:
+ req.write("%s\r\n" % time.ctime())
+ req.flush()
+
+
+ class MainHandler(cyclone.web.RequestHandler, MessagesMixin):
+ @cyclone.web.asynchronous
+ def get(self):
+ self.set_header("Content-Type", "text/plain")
+ self.clients.append(self)
+ d = self.notifyFinish()
+ d.addCallback(lambda *ign: self.clients.remove(self))
- class LangHandler(cyclone.web.RequestHandler):
- def get(self, lang):
- if lang in cyclone.locale.get_supported_locales():
- self.set_cookie("lang", lang)
- self.redirect("/")
class Application(cyclone.web.Application):
def __init__(self):
- handlers = [
- (r"/", IndexHandler),
- (r"/lang/(.+)", LangHandler),
- ]
+ reactor.callWhenRunning(MessagesMixin.setup)
+ cyclone.web.Application.__init__(self, [("/", MainHandler)])
- settings = {
- "static_path": "./static",
- "template_path": "./template",
- }
+This server will never terminate client connections. Instead, it'll send one
+message per second, to all clients, forever.
- cyclone.locale.load_gettext_translations("./locale", "foobar")
- cyclone.web.Application.__init__(self, handlers, **settings)
+Whenever the client disconnects, it's automatically removed from the list of
+connected clients - ``notifyFinish()`` returns a deferred, which is fired
+when the connection is terminated.
- application = service.Application("foobar")
- internet.TCPServer(8888, Application(),
- interface="127.0.0.1").setServiceParent(application)
-2. Create a file in ``./template/index.html`` with translatable strings::
+Mixing @inlineCallbacks, @asynchronous and @authenticated
+---------------------------------------------------------
- <html>
- <body>
- <form action="/" method="post">
- <p>{{ _("write someone's name:") }}</p>
- <input type="text" name="name">
- <input type="submit" value="{{ _('send') }}">
- </form>
+A quick refresh: ``@inlineCallbacks`` turns decorated functions into
+deferreds so they can cooperatively call functions that returns deferreds
+and handle their results inline, making the code much simpler.
+``@asynchronous`` is used to keep the connection open until explicitly
+terminated. And ``@authenticated`` is used to require the client to be logged
+in, usually via a control Cookie.
- <br>
- <p>{{ _("change language:") }}</p>
- <p><a href="/lang/en_US">English (US)</a></p>
- <p><a href="/lang/pt_BR">Portuguese (BR)</a></p>
- </body>
- </html>
+All can be mixed up, but some care has to be taken.
-3. Generate PO translatable file from the source code, using ``xgettext``:
+When multiple decorators are applied to a request method, ``@authenticated``
+must always be the first (top of other decorators). The reason for this, is
+because if authentication fails the request shouln't be processed.
- You will notice that ``xgettext`` cannot parse HTML properly. It was
- first designed to parse C files, and now it supports many other
- languages including Python.
+For the other two, ``inlineCallbacks`` and ``@authenticated``, sequence doesn't
+really matter.
- In order to parse lines like ``<input type="submit" value="{{ _('send') }}">``,
- you'll need an extra script to pre-process the files.
-
- Here's what you can use as ``fix.py``::
+::
- #!/usr/bin/env python
- # coding: utf-8
- # fix.py
+ class MainHandler(cyclone.web.RequestHandler):
+ @cyclone.web.authenticated
+ @cyclone.web.asynchronous
+ @defer.inlineCallbacks
+ def get(self):
+ ...
- import re, sys
+ @cyclone.web.authenticated
+ @defer.inlineCallbacks
+ @cyclone.web.asynchronous
+ def post(self):
+ ...
- if __name__ == "__main__":
- try:
- filename = sys.argv[1]
- assert filename != "-"
- fd = open(filename)
- except:
- fd = sys.stdin
- line_re = re.compile(r"""['"]{{|}}['"] """)
- for line in fd:
- line = line_re.sub(r"", line)
- sys.stdout.write(line)
- fd.close()
+Localization
+------------
- Then, call ``xgettext`` to generate the PO translatable file::
+``cyclone.locale`` uses ``gettext`` to provide translation to strings in the
+server code, and any other text or HTML templates.
- cat foobar.tac template/index.html | python fix.py | \
- xgettext --language=Python --from-code=utf-8 --keyword=_:1,2 -d foobar
+It must be initialized with a domain, which is where all translation messages
+are stored. Refer to the ``gettext`` manual for details on usage, domains, etc.
- This will create a file named ``foobar.po``, which needs to be
- translated, then compiled into an MO file::
+The default domain is *cyclone*, and must be changed to the name of your app
+or module. All translation files are named after the domain.
- vi foobar.po
- (translate everything, :wq)
+There is a complete example of internationalized web application in
+`demos/locale <https://github.com/fiorix/cyclone/tree/master/demos/locale>`_.
- mkdir -p ./locale/pt_BR/LC_MESSAGES/
- msgfmt foobar.po -o ./locale/pt_BR/LC_MESSAGES/foobar.mo
+Also, the project template that ships with cyclone is already prepared for
+full translation. Give it a try::
-4. Finally, test the internationalized application::
+ $ python -m cyclone.app -p foobar
- twistd -ny foobar.tac
+Then check the contents of ``foobar/``.
-There is also a complete example with pluralization in `demos/locale <http://github.com/fiorix/cyclone/tree/master/demos/locale>`_.
-More options and tricks
------------------------
+Other important things
+----------------------
- Keep-Alive
Because of the HTTP 1.1 support, sockets aren't always closed when you call
- ``self.finish()`` in a RequestHandler. cyclone lets you enforce that by setting
- the ``no_keep_alive`` attribute attribute in some of your RequestHandlers::
+ ``self.finish()`` in a ``RequestHandler``. cyclone lets you enforce that by
+ setting the ``no_keep_alive`` class attribute attribute::
class IndexHandler(cyclone.web.RequestHandler):
no_keep_alive = True
def get(self):
...
+ With this, cyclone will always close the socket after ``self.finish()`` is
+ called on all methods (get, post, etc) of this ``RequestHandler`` class.
+
+- XSRF
+
+ By default, XSRF is either enabled or disabled for the entire server,
+ because it's set in Application's settings::
+
+ cyclone.web.Application(handlers, xsrf_cookies=True)
+
+ That might be a problem if the application is also serving an API, like
+ a RESTful API supposed to work with HTTP clients other than the browser.
+
+ For those endpoints, it's possible to disable XSRF::
+
+ class WebLoginHandler(cyclone.web.RequestHandler):
+ def post(self):
+ u = self.get_argument("username")
+ ...
+
+ class APILoginHandler(cyclone.web.RequestHandler):
+ no_xsrf = True
+
+ def post(self):
+ u = self.get_argument("username")
+ ...
+
- Socket closed notification
- One of the great features of TwistedWeb is the ``request.notifyFinish()``,
- which is also available in cyclone.
- This method returns a deferred which is fired when the request socket
+ One of the great features of ``TwistedWeb`` is the
+ ``request.notifyFinish()``, which is also available in cyclone.
+
+ This method returns a deferred, which is fired when the request socket
is closed, by either ``self.finish()``, someone closing their browser
while receiving data, or closing the connection of a Comet request::
@@ -331,35 +471,66 @@ More options and tricks
d = self.notifyFinish()
d.addCallback(remove_from_comet_handlers_list)
-- HTTP X-Headers
+ def on_finish(self):
+ # alternative to notifyFinish
+ pass
- When running a cyclone-based application behind `Nginx <http://nginx.org/en/>`_,
- it's very important to make it automatically use X-Real-Ip and X-Scheme HTTP
- headers. In order to make cyclone recognize those headers, the option ``xheaders=True``
- must be set in the Application settings::
+ This was implemented before Tornado added support to ``on_finish``.
+ Currently, both methods are supported in cyclone.
- class Application(cyclone.web.Application):
- def __init__(self):
- handlers = [
- (r"/", IndexHandler),
- ]
+- HTTP X-Headers
- settings = {
- "xheaders": True
- "static_path": "./static",
- }
+ When running a cyclone-based application behind `Nginx
+ <http://nginx.org/en/>`_, it's very important to make it automatically use
+ X-Real-Ip and X-Scheme HTTP headers. In order to make cyclone recognize
+ those headers, the option ``xheaders=True`` must be set in the Application
+ settings::
- cyclone.web.Application.__init__(self, handlers, **settings)
+ cyclone.web.Application(handlers, xheaders=True)
- Cookie-Secret generation
- What I use to generate the "cookie_secrect" key used in cyclone.web.Application's
- settings is something pretty simple, like this::
+ The following code can be used to generate random cookie secrets::
>>> import uuid, base64
>>> base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
'FoQv5hgLTYCb9aKiBagpJJYtLJInWUcXilg3/vPkUnI='
+- SSL
+
+ cyclone can serve SSL or sit behind a termination proxy (e.g. Nginx).
+ Make sure that you bind the right port with listenSSL, passing the certs::
+
+ 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, world")
+
+
+ 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()
+
+ This example plus a script to generate certificates sits under `demos/ssl
+ <https://github.com/fiorix/cyclone/tree/master/demos/ssl>`_.
FAQ
---
@@ -375,7 +546,7 @@ FAQ
- How do I access raw POST data?
- Both raw POST data and GET/DELETE un-parsed query string are available::
+ Both raw POST data and GET/DELETE un-parsed query strings are available::
class MyHandler(cyclone.web.RequestHandler):
def get(self):
@@ -384,20 +555,22 @@ FAQ
def post(self):
raw = self.request.body
-- Where is the request information, like remote IP address, HTTP method, URI and version?
+- Where is the request information like remote IP address, etc?
- Everything is available as request attributes::
+ Everything is available as request attributes, like protocol, HTTP method,
+ URI and version::
class MyHandler(cyclone.web.RequestHandler):
def get(self):
+ proto = self.request.protocol
remote_ip = self.request.remote_ip
method = self.request.method
uri = self.request.uri
version = self.request.version
- How do I set my own headers for the reply?
- Guess what, use self.set_header(name, value)::
+ Guess what, use ``self.set_header(name, value)``::
class MyHandler(cyclone.web.RequestHandler):
def get(self):
@@ -406,14 +579,19 @@ FAQ
- What HTTP methods are supported in RequestHandler?
- Well, almost all of them. HEAD, GET, POST, DELETE, PUT and OPTIONS are supported.
- TRACE is disabled by default, because it may get you in trouble. CONNECT has nothing
- to do with web servers, it's for proxies.
+ Well, almost all of them. HEAD, GET, POST, DELETE, PUT and OPTIONS are
+ supported. TRACE is disabled by default, because it may get you in trouble.
+ CONNECT has nothing to do with web servers, it's for proxies.
- For more information on HTTP 1.1 methods, please refer to the `RFC 2612 Fielding, et al. <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html>`_.
- For information regarding TRACE vulnerabilities, please check the following links:
- `What is HTTP TRACE? <http://www.cgisecurity.com/questions/httptrace.shtml>`_ and
- `Apache Week, security issues <http://www.apacheweek.com/issues/03-01-24#news>`_.
+ For more information on HTTP 1.1 methods, please refer to the `RFC 2612
+ Fielding, et al. <http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html>`_.
+
+ For information regarding TRACE vulnerabilities, please check the following
+ links:
+ `What is HTTP TRACE?
+ <http://www.cgisecurity.com/questions/httptrace.shtml>`_ and
+ `Apache Week, security issues
+ <http://www.apacheweek.com/issues/03-01-24#news>`_.
Supporting different HTTP methods in the same RequestHandler is easy::
@@ -424,7 +602,31 @@ FAQ
def head(self):
pass
- ...
+ def post(self):
+ pass
+
+ def delete(self):
+ pass
+
+- How to handle file uploads?
+
+ They are available inside the request object as ``self.request.files``.
+ Make sure your HTML form encoding is ``multipart/form-data``::
+
+ class MyHandler(cyclone.web.RequestHandler):
+ def post(self):
+ photos = self.request.files.get("photos")
+
+ # Because it's possible to upload several files under the
+ # same form name, we fetch the first uploaded photo.
+ first_photo = photos[0]
+
+ # first_photo.filename: original filename
+ # first_photo.content_type: parsed content type (not mime-type)
+ # first_photo.body: file contents
+
+ There's an example in `demos/upload
+ <https://github.com/fiorix/cyclone/tree/master/demos/upload>`_.
Credits
@@ -437,7 +639,7 @@ Thanks to (in no particular order):
- Gleicon Moraes
- - Testing and using it in the `RestMQ <http://github.com/gleicon/restmq>`_ web service
+ - Testing and using it in the `RestMQ <https://github.com/gleicon/restmq>`_ web service
- Vanderson Mota
View
54 appskel/README.rst
@@ -10,13 +10,14 @@ About
This file has been created automatically by cyclone-tool for $project_name.
It contains the following files:
+- ``start.sh``: simple shell script to start the server
- ``$modname.conf``: configuration file for the web server
-- ``twisted/plugins/${modname}_plugin.py``: twisted plugin for running the web server
- ``$modname/__init__.py``: information such as author and version of this package
- ``$modname/web.py``: map of url handlers and main class of the web server
- ``$modname/config.py``: configuration parser for ``$modname.conf``
- ``$modname/views.py``: code of url handlers for the web server
- ``scripts/debian-init.d``: generic debian start/stop init script
+- ``scripts/debian-multicore-init.d``: run one instance per core on debian
- ``scripts/localefix.py``: script to fix html text before running ``xgettext``
- ``scripts/cookie_secret.py``: script for generating new secret key for the web server
@@ -25,16 +26,15 @@ Running
For development and testing::
- twistd -n $modname [--help]
+ twistd -n cyclone --help
+ twistd -n cyclone -r $modname.web.Application [--help]
For production::
- twistd --logfile=/var/log/$project.log --pidfile=/var/run/$project.pid $modname
-
-Some systems require that this directory is in PYTHONPATH prior to using twistd
-plugins. If that is the case, then make sure you set it like this::
-
- export PYTHONPATH=`pwd`
+ twistd cyclone \
+ --logfile=/var/log/$project.log \
+ --pidfile=/var/run/$project.pid \
+ -r $modname.web.Application
Convert this document to HTML
@@ -63,40 +63,48 @@ This section is dedicated to explaining how to customize your brand new package.
Databases
---------
-cyclone provides built-in support for Redis and SQLite databases. Because cyclone
-is based on Twisted, it also supports any RDBM supported by the
-``twisted.enterprise.adbapi`` module, like MySQL or PostgreSQL.
+cyclone provides built-in support for SQLite and Redis databases.
+It also supports any RDBM supported by the ``twisted.enterprise.adbapi`` module,
+like MySQL or PostgreSQL.
+
+The default configuration file ``$modname.conf`` ships with pre-configured
+settings for SQLite, Redis and MySQL.
-The default configuration file ``$modname.conf`` is already configured for using
-both MySQL and Redis. The code for loading all the database settings is in
-``$modname/config.py``. Feel free to comment or even remove such code and
-configuration entries. It shouldn't break the web server.
+The code for loading all the database settings is in ``$modname/config.py``.
+Feel free to comment or even remove such code, and configuration entries. It
+shouldn't break the web server.
+
+Take a look at ``$modname/utils.py``, which is where persistent database
+connections are initialized.
Internationalization
--------------------
-cyclone uses the standard ``gettext`` library for dealing with string translation.
-Make sure you have the ``gettext`` package installed. If you don't, you won't be
-able to translate your software.
+cyclone uses the standard ``gettext`` library for dealing with string
+translation.
+
+Make sure you have the ``gettext`` package installed. If you don't, you won't
+be able to translate your software.
-For installing the ``gettext`` package on Debian and Ubuntu systems, do the following::
+For installing the ``gettext`` package on Debian and Ubuntu systems, do this:
apt-get install gettext
For Mac OS X, I'd suggest using `HomeBrew <http://mxcl.github.com/homebrew>`_.
-If you already use HomeBrew, do the following::
+If you already use HomeBrew, run:
brew install gettext
brew link gettext
For generating translatable files for HTML and Python code of your software,
-do the following::
+run this::
cat frontend/template/*.html $modname/*.py | python scripts/localefix.py | \
xgettext - --language=Python --from-code=utf-8 --keyword=_:1,2 -d $modname
-Then translate $modname.po, compile and copy to the appropriate locale directory::
+Then translate $modname.po, compile and copy to the appropriate locale
+directory::
(pt_BR is used as example here)
vi $modname.po
@@ -110,7 +118,7 @@ already compiled.
Cookie Secret
-------------
-The current cookie secret key in ``$modname.conf`` was created during the
+The current cookie secret key in ``$modname.conf`` was generated during the
creation of this package. However, if you need a new one, you may run the
``scripts/cookie_secret.py`` script to generate a random key.
View
10 appskel/frontend/template/index.html
@@ -5,17 +5,9 @@
<title>{{_("cyclone web server")}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
- <!--[if lt IE 9]>
- <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
- <![endif]-->
-
- <!-- Le styles -->
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
<style>
- body {
- padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
- }
+ body {padding-top: 60px;}
</style>
</head>
View
45 appskel/frontend/template/login.html
@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8">
- <title>{{_("cyclone web server")}}</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
-
- <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
- <!--[if lt IE 9]>
- <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
- <![endif]-->
-
- <!-- Le styles -->
- <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
- <style>
- body {
- padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
- }
- </style>
-
- </head>
-
- <body>
- <div class="container">
- <div class="content">
- <div class="row">
- <div class="login-form">
- <h2>Login</h2>
- <form action="/login" method="POST">
- <fieldset>
- <div class="clearfix">
- <input type="text" placeholder="Email">
- </div>
- <div class="clearfix">
- <input type="password" placeholder="Password">
- </div>
- <button class="btn primary" type="submit">Log in</button>
- </fieldset>
- </form>
- </div>
- </div>
- </div>
- </div>
- </body>
-</html>
View
48 appskel/frontend/template/signup.html
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8">
- <title>{{_("cyclone web server")}}</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
-
- <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
- <!--[if lt IE 9]>
- <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
- <![endif]-->
-
- <!-- Le styles -->
- <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
- <style>
- body {
- padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
- }
- </style>
-
- </head>
-
- <body>
- <div class="container">
- <div class="content">
- <div class="row">
- <div class="login-form">
- <h2>Sign Up</h2>
- <form action="/login" method="POST">
- <fieldset>
- <div class="clearfix">
- <input type="text" placeholder="Email">
- </div>
- <div class="clearfix">
- <input type="password" placeholder="Password">
- </div>
- <div class="clearfix">
- <input type="password" placeholder="Confirm Password">
- </div>
- <button class="btn primary" type="submit">Sign Up</button>
- </fieldset>
- </form>
- </div>
- </div>
- </div>
- </div>
- </body>
-</html>
View
52 appskel/frontend/template/useradmin.html
@@ -1,52 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8">
- <title>{{_("cyclone web server")}}</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
-
- <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
- <!--[if lt IE 9]>
- <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
- <![endif]-->
-
- <!-- Le styles -->
- <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
- <style>
- body {
- padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
- }
- </style>
-
- <script>
- edit = function(id){console.log("edit "+ id); }
- remove = function(id){console.log("delete" + id); }
- </script>
-
- </head>
-
- <body>
- <div class="container">
- <div class="content">
- <div class="row">
- <h2>User Administration</h2>
- <table class="table table-striped table-condensed">
- <thead>
- <tr><td>id</td><td>username</td><td>Edit</td><td>Remove</td></tr>
- </thead>
- <tbody>
- {% for user, id in users %}
- <tr>
- <td>{{id}}</td>
- <td>{{user}}</td>
- <td><i class="icon-edit" onclick="edit(id);"></i></td>
- <td><i class="icon-remove" onclick="remove(id);"></i></td>
- </tr>
- {% end %}
- </tbody>
- </table>
- </div>
- </div>
- </div>
-</body>
-</html>
View
14 appskel/modname/__init__.py
@@ -1,4 +1,18 @@
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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.
__author__ = "$name <$email>"
__version__ = "$version"
View
29 appskel/modname/config.py
@@ -1,7 +1,21 @@
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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 ConfigParser
import os
+import ConfigParser
from cyclone.util import ObjectDict
@@ -16,6 +30,7 @@ def parse_config(filename):
cfg = ConfigParser.RawConfigParser()
with open(filename) as fp:
cfg.readfp(fp)
+ fp.close()
settings = {}
@@ -37,9 +52,8 @@ def parse_config(filename):
# sqlite support
if xget(cfg.getboolean, "sqlite", "enabled", False):
- settings["sqlite_settings"] = ObjectDict(
- database=cfg.get("sqlite", "database")
- )
+ settings["sqlite_settings"] = ObjectDict(database=cfg.get("sqlite",
+ "database"))
else:
settings["sqlite_settings"] = None
@@ -49,8 +63,7 @@ def parse_config(filename):
host=cfg.get("redis", "host"),
port=cfg.getint("redis", "port"),
dbid=cfg.getint("redis", "dbid"),
- poolsize=cfg.getint("redis", "poolsize"),
- )
+ poolsize=cfg.getint("redis", "poolsize"))
else:
settings["redis_settings"] = None
@@ -63,10 +76,8 @@ def parse_config(filename):
password=xget(cfg.get, "mysql", "password"),
database=xget(cfg.get, "mysql", "database"),
poolsize=xget(cfg.getint, "mysql", "poolsize", 10),
- debug=xget(cfg.getboolean, "mysql", "debug", False),
- )
+ debug=xget(cfg.getboolean, "mysql", "debug", False))
else:
settings["mysql_settings"] = None
- # it must always return a dict
return settings
View
30 appskel/modname/utils.py
@@ -1,4 +1,18 @@
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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
@@ -27,19 +41,19 @@ class DatabaseMixin(object):
@classmethod
def setup(self, settings):
- conf = settings.get("mysql_settings")
+ conf = settings.get("sqlite_settings")
if conf:
- DatabaseMixin.mysql = adbapi.ConnectionPool("MySQLdb",
- host=conf.host, port=conf.port, db=conf.database,
- user=conf.username, passwd=conf.password,
- cp_min=1, cp_max=conf.poolsize,
- cp_reconnect=True, cp_noisy=conf.debug)
+ DatabaseMixin.sqlite = cyclone.sqlite.InlineSQLite(conf.database)
conf = settings.get("redis_settings")
if conf:
DatabaseMixin.redis = cyclone.redis.lazyConnectionPool(
conf.host, conf.port, conf.dbid, conf.poolsize)
- conf = settings.get("sqlite_settings")
+ conf = settings.get("mysql_settings")
if conf:
- DatabaseMixin.sqlite = cyclone.sqlite.InlineSQLite(conf.database)
+ DatabaseMixin.mysql = adbapi.ConnectionPool("MySQLdb",
+ host=conf.host, port=conf.port, db=conf.database,
+ user=conf.username, passwd=conf.password,
+ cp_min=1, cp_max=conf.poolsize,
+ cp_reconnect=True, cp_noisy=conf.debug)
View
14 appskel/modname/views.py
@@ -1,4 +1,18 @@
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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.locale
View
19 appskel/modname/web.py
@@ -1,14 +1,29 @@
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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.locale
import cyclone.web
from $modname import views
+from $modname import config
from $modname.utils import DatabaseMixin
class Application(cyclone.web.Application):
- def __init__(self, settings):
+ def __init__(self, config_file):
handlers = [
(r"/", views.IndexHandler),
(r"/lang/(.+)", views.LangHandler),
@@ -17,6 +32,8 @@ def __init__(self, settings):
(r"/sample/sqlite", views.SampleSQLiteHandler),
]
+ settings = config.parse_config(config_file)
+
# Initialize locales
locales = settings.get("locale_path")
if locales:
View
14 appskel/scripts/cookie_secret.py
@@ -1,5 +1,19 @@
#!/usr/bin/env python
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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 uuid
View
11 appskel/scripts/debian-init.d
@@ -1,12 +1,12 @@
#!/bin/sh
### BEGIN INIT INFO
-# Provides: foobar
+# Provides: cyclone
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
-# Short-Description: Starts a service for the Twisted plugin 'foobar'
+# Short-Description: Starts a service on the cyclone web server
# Description: Foobar
### END INIT INFO
@@ -21,14 +21,11 @@ LISTEN="127.0.0.1"
CONFIG=$SERVICE_DIR/$SERVICE_NAME.conf
PIDFILE=/var/run/$SERVICE_NAME.pid
LOGFILE=/var/log/$SERVICE_NAME.log
+APP=${SERVICE_NAME}.web.Application
USER=www-data
GROUP=www-data
-DAEMON_OPTS="-u $USER -g $GROUP --pidfile=$PIDFILE --logfile=$LOGFILE $SERVICE_NAME -p $PORT -l $LISTEN -c $CONFIG"
-
-# Set python path so twistd can find the plugin
-# See: http://twistedmatrix.com/projects/core/documentation/howto/plugin.html
-export PYTHONPATH=$SERVICE_DIR
+DAEMON_OPTS="-u $USER -g $GROUP --pidfile=$PIDFILE --logfile=$LOGFILE cyclone --port $PORT --listen $LISTEN --app $APP -c $CONFIG"
if [ ! -x $DAEMON ]; then
echo "ERROR: Can't execute $DAEMON."
View
11 appskel/scripts/debian-multicore-init.d
@@ -1,12 +1,12 @@
#!/bin/bash
### BEGIN INIT INFO
-# Provides: foobar
+# Provides: cyclone
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
-# Short-Description: Starts a service for the Twisted plugin 'foobar'
+# Short-Description: Starts a service on the cyclone web server
# Description: Foobar
### END INIT INFO
@@ -20,17 +20,14 @@ INSTANCES=4
START_PORT=9901
LISTEN="127.0.0.1"
CONFIG=$SERVICE_DIR/$SERVICE_NAME.conf
+APP=${SERVICE_NAME}.web.Application
USER=www-data
GROUP=www-data
# Check out the start_service function for other customization options
# such as setting CPU affinity.
-# Set python path so twistd can find the plugin
-# See: http://twistedmatrix.com/projects/core/documentation/howto/plugin.html
-export PYTHONPATH=$SERVICE_DIR
-
if [ ! -x $DAEMON ]; then
echo "ERROR: Can't execute $DAEMON."
exit 1
@@ -48,7 +45,7 @@ start_service() {
PORT=$[START_PORT]
PIDFILE=/var/run/$SERVICE_NAME.$PORT.pid
LOGFILE=/var/log/$SERVICE_NAME.$PORT.log
- DAEMON_OPTS="-u $USER -g $GROUP --pidfile=$PIDFILE --logfile=$LOGFILE $SERVICE_NAME -p $PORT -l $LISTEN -c $CONFIG"
+ DAEMON_OPTS="-u $USER -g $GROUP --pidfile=$PIDFILE --logfile=$LOGFILE cyclone --port $PORT --listen $LISTEN --app $APP -c $CONFIG"
START_PORT=$[PORT+1]
start-stop-daemon -Sq -p $PIDFILE -x $DAEMON -- $DAEMON_OPTS
View
14 appskel/scripts/localefix.py
@@ -1,5 +1,19 @@
#!/usr/bin/env python
# coding: utf-8
+#
+# Copyright 2012 Alexandre Fiori
+#
+# 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 re
import sys
View
5 appskel/start.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+export PYTHONPATH=`dirname $$0`
+twistd -n cyclone -p 8888 -l 0.0.0.0 \
+ -r $modname.web.Application -c $modname.conf $$*
View
31 appskel/twisted/plugins/modname_plugin.py
@@ -1,31 +0,0 @@
-# coding: utf-8
-
-import $modname.config
-import $modname.web
-
-from twisted.python import usage
-from twisted.plugin import IPlugin
-from twisted.application import service, internet
-from zope.interface import implements
-
-class Options(usage.Options):
- optParameters = [
- ["port", "p", 8888, "TCP port to listen on", int],
- ["listen", "l", "127.0.0.1", "Network interface to listen on"],
- ["config", "c", "$modname.conf", "Configuration file with server settings"],
- ]
-
-class ServiceMaker(object):
- implements(service.IServiceMaker, IPlugin)
- tapname = "$modname"
- description = "cyclone-based web server"
- options = Options
-
- def makeService(self, options):
- port = options["port"]
- interface = options["listen"]
- settings = $modname.config.parse_config(options["config"])
- return internet.TCPServer(port, $modname.web.Application(settings),
- interface=interface)
-
-serviceMaker = ServiceMaker()
View
2 cyclone/__init__.py
@@ -16,4 +16,4 @@
# under the License.
__author__ = "Alexandre Fiori"
-__version__ = version = "1.0-rc5"
+__version__ = version = "1.0-rc6"
View
6 cyclone/app.py
@@ -37,7 +37,7 @@ def new_project(**kwargs):
else:
ext = n.rsplit(".", 1)[-1]
fd = open(os.path.join(dst, mod), "w", 0644)
- if ext in ("conf", "html", "py", "rst"):
+ if ext in ("conf", "html", "py", "rst", "sh"):
fd.write(string.Template(zf.read(n)).substitute(kwargs))
else:
fd.write(zf.read(n))
@@ -51,7 +51,7 @@ def new_project(**kwargs):
def usage(version, target):
print("""
-use: %s [options]
+use: cyclone.app [options]
Options:
-h --help Show this help.
-p --project=NAME Create new cyclone project.
@@ -60,7 +60,7 @@ def usage(version, target):
-v --version=VERSION Set project version [default: %s]
-s --set-pkg-version Set version on package name [default: False]
-t --target=PATH Set path where project is created [default: %s]
- """ % (sys.argv[0], version, target))
+ """ % (version, target))
sys.exit(0)
View
BIN cyclone/appskel.zip
Binary file not shown.
View
71 cyclone/redis.py
@@ -26,6 +26,7 @@
import functools
import operator
import re
+import types
import warnings
import zlib
@@ -109,20 +110,21 @@ def __init__(self, charset="utf-8", errors="strict"):
@defer.inlineCallbacks
def connectionMade(self):
- try:
- response = yield self.select(self.factory.dbid)
- if isinstance(response, ResponseError):
- raise response
- except Exception, e:
- self.factory.continueTrying = False
- self.transport.loseConnection()
-
- msg = "Redis error: could not set dbid=%s: %s" % \
- (self.factory.dbid, str(e))
- self.factory.connectionError(msg)
- if self.factory.isLazy:
- log.msg(msg)
- defer.returnValue(None)
+ if self.factory.dbid is not None:
+ try:
+ response = yield self.select(self.factory.dbid)
+ if isinstance(response, ResponseError):
+ raise response
+ except Exception, e:
+ self.factory.continueTrying = False
+ self.transport.loseConnection()
+
+ msg = "Redis error: could not set dbid=%s: %s" % \
+ (self.factory.dbid, str(e))
+ self.factory.connectionError(msg)
+ if self.factory.isLazy:
+ log.msg(msg)
+ defer.returnValue(None)
self.connected = 1
self.factory.addConnection(self)
@@ -205,7 +207,11 @@ def rawDataReceived(self, data):
if idx == -1:
break
data_len = int(rest[1:idx], 10)
- if len(rest) >= (idx + 5 + data_len):
+ if data_len == -1:
+ rest = rest[5:]
+ self.bulkDataReceived(None)
+ continue
+ elif len(rest) >= (idx + 5 + data_len):
data_start = idx + 2
data_end = data_start + data_len
data = rest[data_start: data_end]
@@ -1509,7 +1515,7 @@ def __init__(self, dbid, poolsize, isLazy=False,
raise ValueError("Redis poolsize must be an integer, not %s" % \
repr(poolsize))
- if not isinstance(dbid, int):
+ if not isinstance(dbid, (int, types.NoneType)):
raise ValueError("Redis dbid must be an integer, not %s" % \
repr(dbid))
@@ -1596,37 +1602,37 @@ def makeShardedConnection(hosts, dbid, poolsize, reconnect, isLazy):
return deferred
-def Connection(host="localhost", port=6379, dbid=0, reconnect=True):
+def Connection(host="localhost", port=6379, dbid=None, reconnect=True):
return makeConnection(host, port, dbid, 1, reconnect, False)
-def lazyConnection(host="localhost", port=6379, dbid=0, reconnect=True):
+def lazyConnection(host="localhost", port=6379, dbid=None, reconnect=True):
return makeConnection(host, port, dbid, 1, reconnect, True)
-def ConnectionPool(host="localhost", port=6379, dbid=0,
+def ConnectionPool(host="localhost", port=6379, dbid=None,
poolsize=10, reconnect=True):
return makeConnection(host, port, dbid, poolsize, reconnect, False)
-def lazyConnectionPool(host="localhost", port=6379, dbid=0,
+def lazyConnectionPool(host="localhost", port=6379, dbid=None,
poolsize=10, reconnect=True):
return makeConnection(host, port, dbid, poolsize, reconnect, True)
-def ShardedConnection(hosts, dbid=0, reconnect=True):
+def ShardedConnection(hosts, dbid=None, reconnect=True):
return makeShardedConnection(hosts, dbid, 1, reconnect, False)
-def lazyShardedConnection(hosts, dbid=0, reconnect=True):
+def lazyShardedConnection(hosts, dbid=None, reconnect=True):
return makeShardedConnection(hosts, dbid, 1, reconnect, True)
-def ShardedConnectionPool(hosts, dbid=0, poolsize=10, reconnect=True):
+def ShardedConnectionPool(hosts, dbid=None, poolsize=10, reconnect=True):
return makeShardedConnection(hosts, dbid, poolsize, reconnect, False)
-def lazyShardedConnectionPool(hosts, dbid=0, poolsize=10, reconnect=True):
+def lazyShardedConnectionPool(hosts, dbid=None, poolsize=10, reconnect=True):
return makeShardedConnection(hosts, dbid, poolsize, reconnect, True)
@@ -1660,37 +1666,38 @@ def makeShardedUnixConnection(paths, dbid, poolsize, reconnect, isLazy):
return deferred
-def UnixConnection(path="/tmp/redis.sock", dbid=0, reconnect=True):
+def UnixConnection(path="/tmp/redis.sock", dbid=None, reconnect=True):
return makeUnixConnection(path, dbid, 1, reconnect, False)
-def lazyUnixConnection(path="/tmp/redis.sock", dbid=0, reconnect=True):
+def lazyUnixConnection(path="/tmp/redis.sock", dbid=None, reconnect=True):
return makeUnixConnection(path, dbid, 1, reconnect, True)
-def UnixConnectionPool(path="/tmp/redis.sock", dbid=0, poolsize=10,
+def UnixConnectionPool(path="/tmp/redis.sock", dbid=None, poolsize=10,
reconnect=True):
return makeUnixConnection(path, dbid, poolsize, reconnect, False)
-def lazyUnixConnectionPool(path="/tmp/redis.sock", dbid=0, poolsize=10,
+def lazyUnixConnectionPool(path="/tmp/redis.sock", dbid=None, poolsize=10,
reconnect=True):
return makeUnixConnection(path, dbid, poolsize, reconnect, True)
-def ShardedUnixConnection(paths, dbid=0, reconnect=True):
+def ShardedUnixConnection(paths, dbid=None, reconnect=True):
return makeShardedUnixConnection(paths, dbid, 1, reconnect, False)
-def lazyShardedUnixConnection(paths, dbid=0, reconnect=True):
+def lazyShardedUnixConnection(paths, dbid=None, reconnect=True):
return makeShardedUnixConnection(paths, dbid, 1, reconnect, True)
-def ShardedUnixConnectionPool(paths, dbid=0, poolsize=10, reconnect=True):
+def ShardedUnixConnectionPool(paths, dbid=None, poolsize=10, reconnect=True):
return makeShardedUnixConnection(paths, dbid, poolsize, reconnect, False)
-def lazyShardedUnixConnectionPool(paths, dbid=0, poolsize=10, reconnect=True):
+def lazyShardedUnixConnectionPool(paths, dbid=None, poolsize=10,
+ reconnect=True):
return makeShardedUnixConnection(paths, dbid, poolsize, reconnect, True)
View
6 debian/changelog
@@ -1,3 +1,9 @@
+python-cyclone (1.0-rc6) unstable; urgency=low
+
+ * Version synch with git repo
+
+ -- Alexandre Fiori <fiorix@gmail.com> Sun, 10 Jun 2012 22:33:13 -0400
+
python-cyclone (0.1-1) unstable; urgency=low
* Initial debian package
View
10 debian/control
@@ -9,7 +9,9 @@ Vcs-Git: git://github.com/fiorix/cyclone.git
Package: python-cyclone
Architecture: any
-Depends: python-twisted
-Description: Twisted toolkit based on tornado
- Cyclone is a low-level network toolkit, which provides support for HTTP 1.1 in an API very similar
- to the one implemented by the Tornado web server. Cyclone is a Twisted Protocol.
+Depends: python-twisted-core, python-twisted-web, python-twisted-mail
+Description: cyclone is a clone of facebook's tornado, on top of twisted.
+ cyclone is a web framework based on tornado, but designed to run on top of
+ the most popular and stable event-driven framework for python, twisted.
+ It implements HTTP 1.1 and supports many protocols, such as WebSocket and SSE,
+ as well as SSL.
View
1 debian/docs
@@ -1,2 +1 @@
-CHANGELOG.txt
README.rst
View
29 debian/postinst
@@ -0,0 +1,29 @@
+#! /bin/sh
+
+set -e
+
+#DEBHELPER#
+
+rebuild_cache()
+{
+ # remove all cache files, then rebuild for the installed python versions
+ rm -f /usr/lib/python[23].?/*-packages/twisted/plugins/dropin.cache
+ for p in $(pyversions -i); do
+ $p -c 'from twisted.plugin import IPlugin, getPlugins; list(getPlugins(IPlugin))' \
+ >/dev/null 2>&1 || true
+ ln -sf /usr/share/pyshared/twisted/plugins/cyclone_plugin.py /usr/lib/${p}/dist-packages/twisted/plugins/
+ done
+}
+
+case "$1" in
+ triggered)
+ if [ "$2" = twisted-plugins-cache ]; then
+ rebuild_cache
+ fi
+ ;;
+ configure)
+ rebuild_cache
+ ;;
+esac
+
+exit 0
View
8 debian/postrm
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+set -e
+for p in $(pyversions -i); do
+ rm -f /usr/lib/${p}/dist-packages/twisted/plugins/cyclone* &> /dev/null
+done
+
+exit 0
View
1 demos/helloworld/helloworld_bottle.py
@@ -17,7 +17,6 @@
# under the License.
import sys
-
from cyclone.bottle import run, route
View
31 demos/helloworld/helloworld_simple.py
@@ -0,0 +1,31 @@
+# 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 -r helloworld_simple.Application
+# For more info: twistd -n cyclone --help
+
+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)])
View
3 demos/helloworld/helloworld_twistd.py
@@ -17,7 +17,8 @@
# under the License.
import cyclone.web
-from twisted.application import service, internet
+from twisted.application import internet
+from twisted.application import service
class MainHandler(cyclone.web.RequestHandler):
View
34 demos/locale/README
@@ -1,22 +1,44 @@
Generate pot translation file using "mytest" as locale domain:
- xgettext --language=Python --keyword=_:1,2 -d mytest templates/index.html
+ xgettext --language=Python --keyword=_:1,2 -d mytest localedemo.py frontend/template/*
- If xgettext fails parsing html elements like:
- <input type="submit" value="{{ _('bla') }}" />
+When using translatable strings in templates, variable arguments must be
+placed outside of the _() function. Example:
+ _("foobar %s" % x) # wrong
+ _("foobar %s") % x # correct
- Try --language=Php or something.
+If xgettext fails parsing html elements like this:
+ <input type="submit" value="{{ _('bla') }}" />
+
+Try --language=Php, or pipe it to localefix.py:
+
+ #!/usr/bin/env python
+ # localefix.py
+ import re
+ import sys
+
+ if __name__ == "__main__":
+ try:
+ filename = sys.argv[1]
+ assert filename != "-"
+ fd = open(filename)
+ except:
+ fd = sys.stdin
+
+ line_re = re.compile(r'="([^"]+)"')
+ for line in fd:
+ line = line_re.sub(r"=\\1", line)
+ sys.stdout.write(line)
+ fd.close()
Merge against existing pot file:
msgmerge old.po mytest.po > new.po
mv new.po mytest.po
-
Compile:
msgfmt mytest.po -o locale/{lang}/LC_MESSAGES/mytest.mo
Compile files in this demo:
msgfmt mytest_pt_BR.po -o frontend/locale/pt_BR/LC_MESSAGES/mytest.mo
msgfmt mytest_es_ES.po -o frontend/locale/es_ES/LC_MESSAGES/mytest.mo
-
View
BIN demos/locale/frontend/locale/es_ES/LC_MESSAGES/mytest.mo
Binary file not shown.
View
BIN demos/locale/frontend/locale/pt_BR/LC_MESSAGES/mytest.mo
Binary file not shown.
View
2 demos/locale/frontend/template/hello.txt
@@ -0,0 +1,2 @@
+Simple text file.
+{{ _("%s, translated") % msg }}
View
4 demos/locale/frontend/template/index.html
@@ -13,9 +13,7 @@
<!-- Le styles -->
<link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
<style>
- body {
- padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
- }
+ body {padding-top: 60px;}
</style>
</head>
View
12 demos/locale/localedemo.py
@@ -29,6 +29,7 @@ class Application(cyclone.web.Application):
def __init__(self):
handlers = [
(r"/", IndexHandler),
+ (r"/hello", HelloHandler),
]
settings = dict(
debug=True,
@@ -69,6 +70,17 @@ def post(self):
self.redirect("/?apples=%d" % self._apples())
+class HelloHandler(BaseHandler):
+ def get(self):
+ # Test with es_ES or pt_BR:
+ # curl -D - -H "Cookie: lang=es_ES" http://localhost:8888/hello
+ _ = self.locale.translate
+ msg = _("hello world")
+ text = self.render_string("hello.txt", msg=msg)
+ self.set_header("Content-Type", "text/plain")
+ self.write(text)
+
+
def main():
log.startLogging(sys.stdout)
reactor.listenTCP(8888, Application(), interface="127.0.0.1")
View
19 demos/locale/mytest_es_ES.po
@@ -17,23 +17,32 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=n != 1;\n"
-#: templates/index.html:8
+#: localedemo.py:76
+msgid "hello world"
+msgstr "hola mundo"
+
+#: frontend/template/hello.txt:2
+#, python-format
+msgid "%s, translated"
+msgstr "%s, traducido"
+
+#: frontend/template/index.html:5 frontend/template/index.html:25
msgid "cyclone locale demo"
msgstr "demonstración de internacionalización de cyclone"
-#: templates/index.html:9
+#: frontend/template/index.html:29
msgid "Please select your language:"
msgstr "Por favor, seleccione su idioma:"
-#: templates/index.html:16
+#: frontend/template/index.html:35
msgid "How many apples?"
msgstr "Cuantas manzanas?"
-#: templates/index.html:20
+#: frontend/template/index.html:39
msgid "Submit"
msgstr "Enviar"
-#: templates/index.html:19
+#: frontend/template/index.html:42
#, python-format
msgid "One apple"
msgid_plural "%(count)d apples"
View
19 demos/locale/mytest_pt_BR.po
@@ -17,23 +17,32 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=n != 1;\n"
-#: templates/index.html:8
+#: 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"
-#: templates/index.html:9
+#: frontend/template/index.html:29
msgid "Please select your language:"
msgstr "Por favor, selecione sua língua:"
-#: templates/index.html:16
+#: frontend/template/index.html:35
msgid "How many apples?"
msgstr "Quantas maçãs?"
-#: templates/index.html:20
+#: frontend/template/index.html:39
msgid "Submit"
msgstr "Enviar"
-#: templates/index.html:19
+#: frontend/template/index.html:42
#, python-format
msgid "One apple"
msgid_plural "%(count)d apples"
View
10 demos/ssl/README
@@ -0,0 +1,10 @@
+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
View
29 demos/ssl/helloworld_simple.py
@@ -0,0 +1,29 @@
+# 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)])
View
2 demos/ssl/helloworld_ssl.py
@@ -25,7 +25,7 @@