summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2011-03-11 16:53:58 +0100
committerMarcel Hellkamp <marc@gsites.de>2011-03-11 16:53:58 +0100
commit86221c934ce09d7a5d51c5554a77ac8c284df4e8 (patch)
treeb1a7c58731d8c0e678542662dc5bf0961cc1b0f4
parentdce88912e47cb1a97d052611fbc1a07aa786e8ca (diff)
parent9562d904c4da864ca64ce117442d8dda59413fdd (diff)
downloadbottle-plugins2.tar.gz
Merge branch 'master' into plugins2plugins2
-rw-r--r--README.rst4
-rwxr-xr-xapidoc/api.rst2
-rw-r--r--apidoc/sphinx/conf.py2
-rwxr-xr-xapidoc/sphinx/static/bottle.css_t36
-rwxr-xr-x[-rw-r--r--]apidoc/sphinx/static/default.js168
-rwxr-xr-xapidoc/sphinx/templates/layout.html3
-rw-r--r--apidoc/sphinx/templates/sidebar-intro.html12
-rwxr-xr-xapidoc/tutorial.rst200
-rwxr-xr-xbottle.py18
-rwxr-xr-xsetup.py2
10 files changed, 292 insertions, 155 deletions
diff --git a/README.rst b/README.rst
index 3574b41..016a108 100644
--- a/README.rst
+++ b/README.rst
@@ -11,7 +11,7 @@ a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
template engines - all in a single file and with no dependencies other than the
Python Standard Library.
-Homepage and documentation: http://bottle.paws.de/
+Homepage and documentation: http://bottlepy.org/
Installation and Dependencies
@@ -37,7 +37,7 @@ Example
Licence (MIT)
-------------
- Copyright (c) 2010, Marcel Hellkamp.
+ Copyright (c) 2011, Marcel Hellkamp.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/apidoc/api.rst b/apidoc/api.rst
index bec2281..a0ac795 100755
--- a/apidoc/api.rst
+++ b/apidoc/api.rst
@@ -5,7 +5,7 @@ API Reference
.. module:: bottle
:platform: Unix, Windows
:synopsis: WSGI micro framework
-.. moduleauthor:: Marcel Hellkamp <marc@paws.de>
+.. moduleauthor:: Marcel Hellkamp <marc@gsites.de>
This is a mostly auto-generated API. If you are new to bottle, you might find the
narrative :doc:`tutorial` more helpful.
diff --git a/apidoc/sphinx/conf.py b/apidoc/sphinx/conf.py
index c916298..f844c10 100644
--- a/apidoc/sphinx/conf.py
+++ b/apidoc/sphinx/conf.py
@@ -24,7 +24,7 @@ import bottle
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['templates']
diff --git a/apidoc/sphinx/static/bottle.css_t b/apidoc/sphinx/static/bottle.css_t
index 1d39526..da8f58c 100755
--- a/apidoc/sphinx/static/bottle.css_t
+++ b/apidoc/sphinx/static/bottle.css_t
@@ -1,5 +1,15 @@
{% set page_width = '940px' %}
{% set sidebar_width = '230px' %}
+{% set color_body = '#E8EFEF' %}
+{% set color_border = '#697983' %}
+{% set color_document = '#ffffff' %}
+{% set textcolor_body = '#000' %}
+{% set textcolor_border = '#fff' %}
+{% set textcolor_document = '#111' %}
+{% set linkcolor_body = '#002a32' %}
+{% set linkcolor_border = 'white' %}
+{% set linkcolor_document = '#005566' %}
+
@import url("basic.css");
@@ -40,21 +50,21 @@ div.related {
/* Design and Colors */
body {
- background: #eee url("_static/logo_bg.png") no-repeat scroll 0 0;
+ background-color: {{ color_body }};
font-family: 'Veranda', sans-serif;
font-size: 16px;
}
div.body {
- border: 1px solid #bbb;
- background-color: #fff;
+ border: 1px solid {{ color_border }};
+ background-color: {{ color_document }};
padding: 0 15px 15px 15px;
- color: #222;
+ color: {{ textcolor_document }};
line-height: 1.4em;
}
a {
- color:#005566;
+ color: {{ linkcolor_document }};
text-decoration: none;
}
@@ -135,9 +145,10 @@ div.sphinxsidebar ul {
}
div.sphinxsidebar h3 {
- background-color: #ddd;
+ background-color: {{ color_border }};
+ color: {{ textcolor_border }};
margin: 0 -15px 0 -5px;
- padding: 2px 5px;
+ padding: 3px 10px 1px 10px;
border-top-left-radius: 10px;
-moz-border-radius-topleft: 10px;
-webkit-border-top-left-radius: 10px;
@@ -147,11 +158,11 @@ div.sphinxsidebar h3 {
}
div.sphinxsidebar a {
- color: #002a32;
+ color: {{ linkcolor_body }};
}
div.sphinxsidebar h3 a {
- color: #000;
+ color: {{ linkcolor_border }};
}
div.sphinxsidebar ul ul {
@@ -195,6 +206,11 @@ div.footer {
-moz-border-radius-bottomleft: 10px;
-webkit-border-bottom-left-radius: 10px;
padding: 2px 10px 2px 10px;
- background: #ddd;
+ background: {{ color_border }};
+ color: {{ linkcolor_border }};
display: block;
}
+
+.sidelegend a:hover {
+ text-decoration: none;
+} \ No newline at end of file
diff --git a/apidoc/sphinx/static/default.js b/apidoc/sphinx/static/default.js
index dfbf7b6..cfe9dea 100644..100755
--- a/apidoc/sphinx/static/default.js
+++ b/apidoc/sphinx/static/default.js
@@ -1,51 +1,137 @@
-$(document).ready(function() {
- var peak = 20; // Number of visible pixels
+// Awesome scrollbar navigaion
+
+POI = function(anchor, title, options) {
+ // Create a Point-Of-Interest, a named position within the document.
+ // @param anchor is the point of interest (HTML or jquery node). This MUST
+ // have an ID attribute.
+ // @param title is the name of the POI (string)
+ POI.all.push(this);
+
+ //: Number of pixels the handle should be visible
+ options = options || {};
+ this.peak = options.peak || POI.peak;
+ this.delay = options.delay || POI.delay;
+ this.css = options.css || POI.css;
+
+ this.pinned = false;
+ this.visible = false;
+ this.hide_timeout = null;
- var hPos = function(pos) {
+ this.anchor = $(anchor);
+ this.id = this.anchor.attr('id');
+ this.title = title || $(anchor).text();
+ this.node = $('<div>').addClass(this.css).appendTo('body');
+ this.link = $('<a>').text(this.title)
+ .attr('href', '#'+this.id)
+ .appendTo(this.node);
+ this.node.css('right', '-'+(this.node.outerWidth()-this.peak)+'px');
+ this.refresh();
+ this.node.mouseenter(function() { POI.show(); });
+ this.node.mouseleave(function() { POI.hide(POI.delay); });
+}
+
+POI.prototype.refresh = function() {
+ // Re-arrange the anchors
var dsize = $(document).height();
var wsize = $(window).height();
- return Math.round(wsize*(pos/dsize))
- }
-
- var timeoutId;
-
- $('h1 > a.headerlink, h2 > a.headerlink').each(function(index){
- var lnk = $(this);
- var pos = lnk.offset().top
- var title = lnk.parent().text().replace('¶','')
- var node = $('<div>')
- .addClass('sidelegend')
- .css('top', hPos(pos)+'px')
- .appendTo('body')
- node.append($('<a>').text(title).attr('href', lnk.attr('href')))
- node.css('right', '-'+(node.outerWidth()-peak)+'px')
- node.mouseenter(function(){
- if(timeoutId) {
- window.clearTimeout(timeoutId);
- timeoutId = null;
- }
- $('div.sidelegend').animate({'right': '0px'}, 250)
+ var pos = this.anchor.offset().top;
+ var hpos = Math.round(wsize*(pos/dsize));
+ this.node.css('top', hpos+'px');
+}
+
+POI.prototype.show = function() {
+ // Show the handle
+ if(this.visible) return;
+ this.node.stop(true).animate({'right': '0px'}, 250);
+ this.visible = true;
+}
+
+POI.prototype.hide = function() {
+ // Hide the handle
+ if(this.pinned) return;
+ if(! this.visible) return;
+ this.node.stop(true).animate({
+ 'right': '-'+(this.node.outerWidth()-this.peak)+'px'
+ }, 250);
+ this.visible = false;
+}
+
+
+
+// Static attributes and methods.
+
+POI.all = Array();
+POI.peak = 20;
+POI.delay = 2000;
+POI.css = 'sidelegend';
+POI.hide_timeout = null;
+
+POI.refresh = function() {
+ // Refresh all at once
+ jQuery.each(POI.all, function() {
+ this.refresh();
})
- node.mouseleave(function(){
- timeoutId = window.setTimeout(function(){
- $('div.sidelegend').each(function(){
- var n = $(this)
- n.animate({'right': '-'+(n.outerWidth()-peak)+'px'}, 250)
- })
- timeoutId = null;
- }, 1000)
+}
+
+POI.show = function() {
+ // Show all at once
+ if(POI.hide_timeout) window.clearTimeout(POI.hide_timeout);
+ POI.hide_timeout = null;
+ jQuery.each(POI.all, function() {
+ this.show();
})
+}
+
+POI.hide = function(delay) {
+ // Hide all at once after a specific delay
+ if(POI.hide_timeout) window.clearTimeout(POI.hide_timeout);
+ if(delay) {
+ POI.hide_timeout = window.setTimeout(function() {
+ POI.hide_timeout = null;
+ POI.hide();
+ }, delay)
+ } else {
+ jQuery.each(POI.all, function() {
+ this.hide();
+ })
+ }
+}
- $(document).resize(function(){
- var dsize = $(document).height();
- var wsize = $(window).height();
- node.css('top', hPos(pos)+'px')
+POI.whereami = function() {
+ // Show and pin the currently viewed POI
+ var position = $(window).scrollTop() + $(window).height() / 2;
+ var last = null;
+ jQuery.each(POI.all, function() {
+ if(position < this.anchor.offset().top) return false;
+ last = this;
+ })
+ if(last) {
+ last.pinned = true;
+ last.show();
+ }
+ jQuery.each(POI.all, function() {
+ if(this != last) {
+ this.pinned = false;
+ this.hide();
+ }
})
+}
+
- $(window).resize(function(){
- var dsize = $(document).height();
- var wsize = $(window).height();
- node.css('top', hPos(pos)+'px')
+
+$(document).resize(POI.refresh);
+$(window).resize(POI.refresh);
+$(window).scroll(POI.whereami);
+
+// Global events that affect all POIs
+$(document).ready(function() {
+ $('.section > h1 > a.headerlink, .section > h2 > a.headerlink').each(function(index){
+ var lnk = $(this);
+ var title = lnk.parent().text().replace('¶','')
+ var anchor = lnk.parent().parent()
+ new POI(anchor, title)
})
- })
+ POI.whereami();
})
+
+
diff --git a/apidoc/sphinx/templates/layout.html b/apidoc/sphinx/templates/layout.html
index 8582f7b..cb15b53 100755
--- a/apidoc/sphinx/templates/layout.html
+++ b/apidoc/sphinx/templates/layout.html
@@ -4,6 +4,9 @@
<link rel="shortcut icon" type="image/x-icon" href="{{ pathto('_static/favicon.ico', 1) }}" />
<link rel="image_src" type="image/png" href="{{ pathto('_static/logo_reddit.png', 1) }}" />
<script type="application/javascript" src="{{ pathto('_static/default.js', 1) }}"></script>
+ {% if pagename == 'index' -%}
+ <meta name="description" content="Bottle is a fast, simple and lightweight WSGI micro web-framework for Python." />
+ {% endif %}
{{ super() }}
{% endblock %}
diff --git a/apidoc/sphinx/templates/sidebar-intro.html b/apidoc/sphinx/templates/sidebar-intro.html
index e36278e..3404d77 100644
--- a/apidoc/sphinx/templates/sidebar-intro.html
+++ b/apidoc/sphinx/templates/sidebar-intro.html
@@ -3,26 +3,24 @@
</p>
<h3>Some Links</h3>
<ul>
- <li><a href="http://bottle.paws.de/">The Bottle Website</a></li>
- <li><a target="_blank" href="https://github.com/defnull/bottle/issues">Bottle Issue Tracker</a></li>
+ <li><a target="_blank" href="https://github.com/defnull/bottle/issues">Issue Tracker</a></li>
<li><a target="_blank" href="http://pypi.python.org/pypi/bottle">Bottle @ PyPI</a></li>
<li><a target="_blank" href="https://github.com/defnull/bottle">Bottle @ GitHub</a></li>
<li><a target="_blank" href="http://groups.google.de/group/bottlepy">Bottle @ Google Groups</a></li>
<li><a target="_blank" href="http://twitter.com/bottlepy">Bottle @ Twitter</a></li>
- <li><a target="_blank" href="http://flattr.com/thing/21888/Bottle-A-Python-Web-Framework">Bottle @ Flattr</a></li>
</ul>
<h3>Installation</h3>
<p>Install Bottle with <code>pip&nbsp;install&nbsp;bottle</code> or download the source package at <a href="http://pypi.python.org/pypi/bottle">PyPI</a>.</p>
<h3>Documentation</h3>
<p>
- You can download this documentation as <a href="http://bottle.paws.de/docs/bottle-docs.pdf">PDF</a> or <a href="http://bottle.paws.de/docs/bottle-docs.zip">HTML (zip)</a> for offline use.
+ Download this documentation as <a href="http://bottlepy.org/docs/bottle-docs.pdf">PDF</a> or <a href="http://bottlepy.org/docs/bottle-docs.zip">HTML (zip)</a> for offline use.
</p>
<h3>Sources</h3>
-<p>You can browse the sources at <a href="https://github.com/defnull/bottle">GitHub</a>.</p>
+<p>Browse the sources at <a href="https://github.com/defnull/bottle">GitHub</a>.</p>
<h3>Other Releases</h3>
<ul>
- <li><a href="http://bottle.paws.de/docs/dev/">Bottle dev (in development)</a></li>
- <li><a href="http://bottle.paws.de/docs/0.8/">Bottle 0.8 (stable)</a></li>
+ <li><a href="http://bottlepy.org/docs/dev/">Bottle dev (in development)</a></li>
+ <li><a href="http://bottlepy.org/docs/0.8/">Bottle 0.8 (stable)</a></li>
</ul>
diff --git a/apidoc/tutorial.rst b/apidoc/tutorial.rst
index bd83389..e935315 100755
--- a/apidoc/tutorial.rst
+++ b/apidoc/tutorial.rst
@@ -4,7 +4,6 @@
.. _Apache: http://www.apache.org/
.. _cherrypy: http://www.cherrypy.org/
.. _decorator: http://docs.python.org/glossary.html#term-decorator
-.. _fapws3: http://github.com/william-os4y/fapws3
.. _flup: http://trac.saddi.com/flup
.. _http_code: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
.. _http_method: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
@@ -35,7 +34,7 @@ This tutorial introduces you to the concepts and features of the Bottle web fram
* :ref:`tutorial-routing`: Web development starts with binding URLs to code. This section tells you how to do it.
* :ref:`tutorial-output`: You have to return something to the Browser. Bottle makes it easy for you, supporting more than just plain strings.
* :ref:`tutorial-request`: Each client request carries a lot of information. HTTP-headers, form data and cookies to name just three. Here is how to use them.
-* :ref:`tutorial-templates`: You don't want to write HTML within your python code, do you? Templates separate code from presentation.
+* :ref:`tutorial-templates`: You don't want to clutter your Python code with HTML fragments, do you? Templates separate code from presentation.
* :ref:`tutorial-debugging`: These tools and features will help you during development.
* :ref:`tutorial-deployment`: Get it up and running.
@@ -220,6 +219,8 @@ The ordering of this list is significant. You may for example return a subclass
Bottle uses the `charset` parameter of the ``Content-Type`` header to decide how to encode unicode strings. This header defaults to ``text/html; charset=UTF8`` and can be changed using the :attr:`Response.content_type` attribute or by setting the :attr:`Response.charset` attribute directly. (The :class:`Response` object is described in the section :ref:`tutorial-response`.)
+::
+
from bottle import response
@route('/iso')
def get_iso():
@@ -316,16 +317,54 @@ Add values to the :attr:`Response.headers` dictionary to add or change response
response.headers['Content-Language'] = 'en'
return get_wiki_page(page)
-.. _tutorial-secure-cookies:
+.. _tutorial-signed-cookies:
Cookies
-------------------------------------------------------------------------------
-TODO
+A cookie is a piece of text stored in the user's browser. You can access cookies via :meth:`Request.get_cookie` and set new cookies with the :meth:`Response.set_cookie` method::
+
+ @route('/hello')
+ def hello_again(self):
+ if request.get_cookie("visited"):
+ return "Welcome back! Nice to see you again"
+ else:
+ response.set_cookie("visited", "yes")
+ return "Hello there! Nice to meet you"
+
+But there are some gotchas:
+
+* Cookies are limited to 4kb of text in most browsers.
+* Some users configure their browsers to not accept cookies at all. Most search-engines ignore cookies, too. Make sure that your application is still usable without cookies.
+* Cookies are stored at client side and not encrypted in any way. Whatever you store in a cookie, the user can read it. Worth than that, an attacker might be able to steal a user's cookies through `XSS <http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking>`_ vulnerabilities on your side. Some viruses are known to read the browser cookies, too. Do not store confidential information in cookies, ever.
+* Cookies are easily forged by malicious clients. Do not trust cookies.
+
+.. rubric:: Signed Cookies
+
+As mentioned above, cookies are easily forged by malicious clients. Bottle can cryptographically sign your cookies to prevent this kind of manipulation. All you have to do is to provide a signature key whenever you read or set a cookie and keep that key a secret. As a result, :meth:`Request.get_cookie` will return ``None`` if the cookie is not signed or the signature keys don't match::
+
+ @route('/login')
+ def login():
+ username = request.forms.get('username')
+ password = request.forms.get('password')
+ if check_user_credentials(username, password):
+ response.set_cookie("account", username, secret='some-secret-key')
+ return "Welcome %s! You are now logged in." % username
+ else:
+ return "Login failed."
+
+ @route('/restricted')
+ def restricted_area(self):
+ username = request.get_cookie("account", secret='some-secret-key')
+ if username:
+ return "Hello %s. Welcome back." % username
+ else:
+ return "You are not logged in. Access denied."
+
+In addition, bottle automatically pickles and unpickles any data stored to signed cookies. This allows you to store any pickle-able object (not only strings) to cookies, as long as the pickled data does not exceed the 4kb limitation.
-.. rubric:: Secure Cookies
+.. warning:: Signed cookies are not encrypted (the client can still see the content) and not copy-protected (the client can restore an old cookie). The main intention is to make pickling and unpickling save, not to store secret information at client side.
-TODO
@@ -360,7 +399,7 @@ Header are stored in :attr:`Request.header`. The attribute is an instance of :cl
.. rubric:: Cookies
-Cookies are stored in :attr:`Request.COOKIES` as a normal dictionary. The :meth:`Request.get_cookie` method allows access to :ref:`tutorial-secure-cookies` as described in a separate section. This example shows a simple cookie-based view counter::
+Cookies are stored in :attr:`Request.COOKIES` as a normal dictionary. The :meth:`Request.get_cookie` method allows access to :ref:`tutorial-signed-cookies` as described in a separate section. This example shows a simple cookie-based view counter::
from bottle import route, request, response
@route('/counter')
@@ -408,7 +447,7 @@ Here is an example for a simple file upload form:
if name and data:
raw = data.file.read() # This is dangerous for big files
filename = data.filename
- return "Hello %s! Your uploaded %s (%d bytes)." % (name, filename, len(raw))
+ return "Hello %s! You uploaded %s (%d bytes)." % (name, filename, len(raw))
return "You missed a field."
@@ -594,41 +633,65 @@ finally clauses, etc., are not executed after a ``SIGTERM``.
Deployment
================================================================================
-Bottle uses the built-in ``wsgiref.SimpleServer`` by default. This non-threading
-HTTP server is perfectly fine for development and early production,
-but may become a performance bottleneck when server load increases.
+Bottle runs on the built-in `wsgiref WSGIServer <http://docs.python.org/library/wsgiref.html#module-wsgiref.simple_server>`_ by default. This non-threading HTTP server is perfectly fine for development and early production, but may become a performance bottleneck when server load increases.
There are three ways to eliminate this bottleneck:
-* Use a multi-threaded server adapter
-* Spread the load between multiple bottle instances
-* Do both
+* Use a multi-threaded or asynchronous HTTP server.
+* Spread the load between multiple bottle instances.
+* Do both.
Multi-Threaded Server
--------------------------------------------------------------------------------
-The easiest way to increase performance is to install a multi-threaded and
-WSGI-capable HTTP server like Paste_, flup_, cherrypy_
-or fapws3_ and use the corresponding bottle server-adapter.
-
-::
-
- from bottle import PasteServer, FlupServer, FapwsServer, CherryPyServer
- bottle.run(server=PasteServer) # Example
-
-If bottle is missing an adapter for your favorite server or you want to tweak
-the server settings, you may want to manually set up your HTTP server and use
-``bottle.default_app()`` to access your WSGI application.
-
-::
-
- def run_custom_paste_server(self, host, port):
- myapp = bottle.default_app()
- from paste import httpserver
- httpserver.serve(myapp, host=host, port=port)
-
+.. _flup: http://trac.saddi.com/flup
+.. _gae: http://code.google.com/appengine/docs/python/overview.html
+.. _wsgiref: http://docs.python.org/library/wsgiref.html
+.. _cherrypy: http://www.cherrypy.org/
+.. _paste: http://pythonpaste.org/
+.. _rocket: http://pypi.python.org/pypi/rocket
+.. _gunicorn: http://pypi.python.org/pypi/gunicorn
+.. _fapws3: http://www.fapws.org/
+.. _tornado: http://www.tornadoweb.org/
+.. _twisted: http://twistedmatrix.com/
+.. _diesel: http://dieselweb.org/
+.. _meinheld: http://pypi.python.org/pypi/meinheld
+.. _bjoern: http://pypi.python.org/pypi/bjoern
+
+The easiest way to increase performance is to install a multi-threaded or asynchronous WSGI server like paste_ or cherrypy_ and tell bottle to start it instead of the default single-threaded one::
+
+ bottle.run(server='paste') # Example
+
+Bottle ships with a lot of ready-to-use adapters for the most common WSGI servers and automates the setup process. Here is an incomplete list:
+
+======== ============ ======================================================
+Name Homepage Description
+======== ============ ======================================================
+cgi Run as CGI script
+flup flup_ Run as Fast CGI process
+gae gae_ Helper for Google App Engine deployments
+wsgiref wsgiref_ Single-threaded default server
+cherrypy cherrypy_ Multi-threaded and very stable
+paste paste_ Multi-threaded, stable, tried and tested
+rocket rocket_ Multi-threaded
+gunicorn gunicorn_ Pre-forked, partly written in C
+fapws3 fapws3_ Asynchronous, written in C
+tornado tornado_ Asynchronous, powers some parts of Facebook
+twisted twisted_ Asynchronous, well tested
+diesel diesel_ Asynchronous, based on greenlet
+meinheld meinheld_ Asynchronous, partly written in C
+bjoern bjoern_ Asynchronous, very fast and written in C
+auto Automatically selects an available server adapter
+======== ============ ======================================================
+
+The full list is available through :data:`server_names`.
+
+If there is no adapter for your favorite server or if you need more control over the server setup, you may want to start the server manually. Refer to the server documentation on how to mount WSGI applications. Here is an example for paste_::
+
+ from paste import httpserver
+ httpserver.serve(bottle.default_app(), host='0.0.0.0', port=80)
Multiple Server Processes
@@ -636,7 +699,7 @@ Multiple Server Processes
A single Python process can only utilise one CPU at a time, even if
there are more CPU cores available. The trick is to balance the load
-between multiple independent Python processes to utilise all of your
+between multiple independent Python processes to utilize all of your
CPU cores.
Instead of a single Bottle application server, you start one instance
@@ -647,42 +710,8 @@ a random Bottle processes, spreading the load between all available
back end server instances. This way you can use all of your CPU cores and
even spread out the load between different physical servers.
-But there are a few drawbacks:
-
-* You can't easily share data between multiple Python processes.
-* It takes a lot of memory to run several copies of Python and Bottle at the same time.
-
One of the fastest load balancers available is Pound_ but most common web servers have a proxy-module that can do the work just fine.
-I'll add examples for lighttpd_ and
-Apache_ web servers soon.
-
-Using WSGI and Middleware
---------------------------------------------------------------------------------
-
-A call to `bottle.default_app()` returns your WSGI application. After applying as many WSGI middleware modules as you like, you can tell
-`bottle.run()` to use your wrapped application, instead of the default one.
-
-::
-
- from bottle import default_app, run
- app = default_app()
- newapp = YourMiddleware(app)
- run(app=newapp)
-
-.. rubric: How default_app() works
-
-Bottle creates a single instance of `bottle.Bottle()` and uses it as a default for most of the module-level decorators and the `bottle.run()` routine.
-`bottle.default_app()` returns (or changes) this default. You may, however, create your own instances of `bottle.Bottle()`.
-
-::
-
- from bottle import Bottle, run
- mybottle = Bottle()
- @mybottle.route('/')
- def index():
- return 'default_app'
- run(app=mybottle)
Apache mod_wsgi
--------------------------------------------------------------------------------
@@ -701,7 +730,7 @@ File ``/var/www/yourapp/app.wsgi``::
os.chdir(os.path.dirname(__file__))
import bottle
- # ... add or import your bottle app code here ...
+ # ... build or import your bottle application here ...
# Do NOT use bottle.run() with mod_wsgi
application = bottle.default_app()
@@ -726,16 +755,27 @@ The Apache configuration may look like this::
Google AppEngine
--------------------------------------------------------------------------------
-I didn't test this myself but several Bottle users reported that this
-works just fine::
+.. versionadded:: 0.9
+
+The ``gae`` adapter completely automates the Google App Engine deployment. It even ensures that a ``main()`` function is present in your ``__main__`` module to enable `App Caching <http://code.google.com/appengine/docs/python/runtime.html#App_Caching>`_ (which drastically improves performance)::
import bottle
- from google.appengine.ext.webapp import util
- # ... add or import your bottle app code here ...
- # Do NOT use bottle.run() with AppEngine
- util.run_wsgi_app(bottle.default_app())
+ # ... build or import your bottle application here ...
+ bottle.run(server='gae')
+
+It is always a good idea to let GAE serve static files directly. Here is example ``app.yaml``::
+ application: myapp
+ version: 1
+ runtime: python
+ api_version: 1
+ handlers:
+ - url: /static
+ static_dir: static
+
+ - url: /.*
+ script: myapp.py
Good old CGI
@@ -744,7 +784,7 @@ Good old CGI
CGI is slow as hell, but it works::
import bottle
- # ... add or import your bottle app code here ...
+ # ... build or import your bottle application here ...
bottle.run(server=bottle.CGIServer)
@@ -778,12 +818,6 @@ Glossary
framework, the application is developed by attaching a handler function
as callback for each specific URL comprising the application.
- secure cookie
- Bottle creates signed cookies with objects that can be pickled. A secure
- cookie will be created automatically when a type that is not a string is
- used as the value in :meth:`request.set_cookie` and bottle's config
- includes a `securecookie.key` entry with a salt.
-
source directory
The directory which, including its subdirectories, contains all
source files for one Sphinx project.
diff --git a/bottle.py b/bottle.py
index 88f6fb9..acfe945 100755
--- a/bottle.py
+++ b/bottle.py
@@ -6,7 +6,7 @@ a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and
template engines - all in a single file and with no dependencies other than the
Python Standard Library.
-Homepage and documentation: http://bottle.paws.de/
+Homepage and documentation: http://bottlepy.org/
Copyright (c) 2011, Marcel Hellkamp.
License: MIT (see LICENSE.txt for details)
@@ -955,7 +955,7 @@ class Request(threading.local, DictMixin):
@DictProperty('environ', 'bottle.cookies', read_only=True)
def COOKIES(self):
- """ Cookies parsed into a dictionary. Secure cookies are NOT decoded
+ """ Cookies parsed into a dictionary. Signed cookies are NOT decoded
automatically. See :meth:`get_cookie` for details.
"""
raw_dict = SimpleCookie(self.headers.get('Cookie',''))
@@ -965,7 +965,7 @@ class Request(threading.local, DictMixin):
return cookies
def get_cookie(self, key, secret=None):
- """ Return the content of a cookie. To read a `Secure Cookies`, use the
+ """ Return the content of a cookie. To read a `Signed Cookies`, use the
same `secret` as used to create the cookie (see
:meth:`Response.set_cookie`). If anything goes wrong, None is
returned.
@@ -1045,12 +1045,12 @@ class Response(threading.local):
return self._COOKIES
def set_cookie(self, key, value, secret=None, **kargs):
- ''' Add a cookie. If the `secret` parameter is set, this creates a
- `Secure Cookie` (described below).
+ ''' Add a cookie or overwrite an old one. If the `secret` parameter is
+ set, create a `Signed Cookie` (described below).
:param key: the name of the cookie.
:param value: the value of the cookie.
- :param secret: required for secure cookies. (default: None)
+ :param secret: required for signed cookies. (default: None)
:param max_age: maximum age in seconds. (default: None)
:param expires: a datetime object or UNIX timestamp. (defaut: None)
:param domain: the domain that is allowed to read the cookie.
@@ -1060,11 +1060,11 @@ class Response(threading.local):
If neither `expires` nor `max_age` are set (default), the cookie
lasts only as long as the browser is not closed.
- Secure cookies may store any pickle-able object and are
+ Signed cookies may store any pickle-able object and are
cryptographically signed to prevent manipulation. Keep in mind that
cookies are limited to 4kb in most browsers.
-
- Warning: Secure cookies are not encrypted (the client can still see
+
+ Warning: Signed cookies are not encrypted (the client can still see
the content) and not copy-protected (the client can restore an old
cookie). The main intention is to make pickling and unpickling
save, not to store secret information at client side.
diff --git a/setup.py b/setup.py
index a250a01..a5bb2c9 100755
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ setup(name='bottle',
long_description='Bottle is a fast and simple micro-framework for small web-applications. It offers request dispatching (Routes) with url parameter support, Templates, a built-in HTTP Server and adapters for many third party WSGI/HTTP-server and template engines. All in a single file and with no dependencies other than the Python Standard Library.',
author='Marcel Hellkamp',
author_email='marc@gsites.de',
- url='http://bottle.paws.de/',
+ url='http://bottlepy.org/',
py_modules=['bottle'],
license='MIT',
platforms = 'any',