diff options
-rw-r--r-- | homepage/pages/docs.md | 388 |
1 files changed, 297 insertions, 91 deletions
diff --git a/homepage/pages/docs.md b/homepage/pages/docs.md index b755670..9c86dff 100644 --- a/homepage/pages/docs.md +++ b/homepage/pages/docs.md @@ -1,15 +1,29 @@ [TOC] -Bottle Documentation -==================== + [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 + [mako]: http://www.makotemplates.org/ + [mod_wsgi]: http://code.google.com/p/modwsgi/ + [paste]: http://pythonpaste.org/ + [wsgi]: http://www.wsgi.org/wsgi/ -This document is a work in progress. If you have questions not answered here, -please file a ticket at bottle's [issue -tracker](http://github.com/defnull/bottle/issues). -## Basic Routes +# Bottle Documentation -Routes are used to map request URLs to callables that generate the response content. Bottle has a `route()` decorator to do that. +__This document is a work in progress__ and intended to be a tutorial, howto and an api documentation at the same time. If you have questions not answered here, +please file a ticket at bottles [issue tracker](http://github.com/defnull/bottle/issues). + + + + +## "Hello World" in a Bottle + +Lets start with a very basic example: Hello World #!Python from bottle import route, run @@ -18,50 +32,89 @@ Routes are used to map request URLs to callables that generate the response cont return "Hello World!" run() # This starts the HTTP server -Run this script, visit <http://localhost:8080/hello> and you will see "Hello World!" in your Browser. +Run this script, visit <http://localhost:8080/hello> and you will see "Hello World!" in your Browser. So, what happened here? + + 1. First we imported some bottle components. The `route()` decorator and the `run()` function. + 2. The `route()` [decorator][] is used do bind a piece of code to an URL. In this example we want to answer requests to the `/hello` URL. + 3. This function will be called every time someone hits the `/hello` URL on the web server. It is called a __handler function__. + 4. The return value of a handler function will be sent back to the Browser. + 5. Now it is time to start the actual HTTP server. The default is a development server running on *localhost* port *8080* and serving requests until you hit __Ctrl-C__ -### GET, POST, HEAD, ... -The route decorator has an optional keyword argument `method` which defaults to `method='GET'`. -Possible values are `POST`, `PUT`, `DELETE`, `HEAD` or any other HTTP request method you want to listen to. +# Routing + +Routes are used to map URLs to __handler callbacks__ that generate the content for the specific URL. Bottle has a `route()` decorator to do that. You can add any number of routes to a callback. + + #!Python + from bottle import route + @route('/') + @route('/index.html') + def index(): + return "<a href='/hello'>Go to Hello World page</a>" + + @route('/hello') + def hello(): + return "Hello World!" + +As you can see, URLs and routes have nothing to do with actual files on the web server. Routes are unique names for your handler callbacks, nothing more and nothing less. + +URLs not matching any routes are answered by a 404 error. Exceptions within your handler callbacks will cause 500 errors. + + + + + +## Request Methods + +The `route()` decorator has an optional keyword argument `method` which defaults to `method='GET'`, so only GET requests get answered. +Possible values are `POST`, `PUT`, `DELETE`, `HEAD` or any other [HTTP request method][http_method] you want to listen to. #!Python from bottle import route, request @route('/form/submit', method='POST') def form_submit(): - form_data = request.POST - do_something(form_data) + form_data = request.POST # see "GET and POST values" + do_something_with(form_data) return "Done" - - - +In this example we used `request.POST` which is described [here](#get-and-post-values) ## Dynamic Routes -You can extract parts of the URL and create dynamic routes with an easy syntax. +Static routes are fine, but URLs may carry information as well. Let's add a `:name` placeholder to our route. #!Python + from bottle import route @route('/hello/:name') def hello(name): return "Hello %s!" % name -By default, a `:placeholder` matches everything up to the next -slash. To change that, you can add some regular expression in between `#`s: +This dynamic route matches `/hello/alice` as well as `/hello/bob`. In fact, the `:name` part of the route matches everything but a slash (`/`), so any name is possible. `/hello/bob/and/alice` or `/hellobob` won't match. + +Each part of the URL covered by a placeholder is provided to your handler callback as a keyword parameter. + + + + +### Regular Expressions + +The default placeholder matches everything up to the next slash. To change that, you can add some regular expression: #!Python + from bottle import route @route('/get_object/:id#[0-9]+#') def get(id): return "Object ID: %d" % int(id) -or even use full features regular expressions with named groups: +or even use full featured regular expressions with named groups: #!Python + from bottle import route @route('/get_object/(?P<id>[0-9]+)') def get(id): return "Object ID: %d" % int(id) @@ -70,12 +123,14 @@ As you can see, URL parameters remain strings, even if they are configured to only match digits. You have to explicitly cast them into the type you need. -### The @validate() decorator + + + +## The @validate() decorator Bottle offers a handy decorator called `validate()` to check and manipulate URL parameters. -It takes callables as keyword arguments and filters every URL parameter -through the corresponding callable before they are passed to your -request handler. +It takes callables (function or class objects) as keyword arguments and filters every URL parameter +through the corresponding callable before they are passed to your request handler. #!Python from bottle import route, validate @@ -85,54 +140,103 @@ request handler. def validate_test(i, f, csv): return "Int: %d, Float:%f, List:%s" % (i, f, repr(csv)) -You may raise `ValueError` in your custom callable if the parameter +You may raise `ValueError` in your custom callable if a parameter does not validate. +# Generating content + +TODO + + + +## Output Casting + +The [WSGI specification][wsgi] expects an iterable list of byte strings to be returned from your application and can't handle file objects, unicode, dictionaries or exceptions. + + #!Python + from bottle import route + @route('/wsgi') + def wsgi(): + return ['WSGI','wants a','list of','strings'] + +Bottle automatically tries to convert anything to a WSGI supported type, so you +don't have to. The following examples will work with Bottle, but won't work with +pure WSGI. -## Returning files and JSON +### Strings and Unicode -The WSGI specification expects an iterable list of strings and can't handle file -objects or plain strings. Bottle automatically converts them to iterables, so -you don't have to. The following example will work with Bottle, but won't work -with pure WSGI. +Returning strings (bytes) is not a problem. Unicode however needs to be encoded into a byte stream before +the webserver can send it to the client. Ths default encoding is utf-8, so if that fits your needs, you can +simply return unicode or unicode iterables. #!Python - @route('/get_string') + from bottle import route, response + @route('/string') def get_string(): - return "This is not a list of strings, but a single string" + return 'Bottle converts strings to iterables' + + @route('/unicode') + def get_unicode(): + return u'Unicode is encoded with UTF-8 by default' + +You can change Bottles default encoding by setting `response.content_type` to a value +containing a `charset=...` parameter or by changing `response.charset` directly. + + #!Python + from bottle import route, response + @route('/iso') + def get_iso(): + response.charset = 'ISO-8859-15' + return u'This will be sent with ISO-8859-15 encoding.' + + @route('/latin9') + def get_latin(): + response.content_type = 'text/html; charset=latin9' + return u'ISO-8859-15 is also known as latin9.' + +In some rare cases the Python encoding names differ from the names supported by the HTTP specification. +Then, you have to do both: First set the `response.content_type` header (which is sent to the client +unchanged) and then set the `response.charset` option (which is used to decode unicode). + + +### File Objects and Streams + +Bottle wrapps everything that has a `read()` method (file objects) with the `wsgi.file_wrapper` +provided by your WSGI server implementation. This wrapper should use highly optimised system calls for +your operating system (`sendfile` on UNIX) to transfer the file. + + #!Python @route('/file') def get_file(): return open('some/file.txt','r') + + +### JSON + Even dictionaries are allowed. They are converted to [json](http://de.wikipedia.org/wiki/JavaScript_Object_Notation) and returned -as `Content-Type: application/json`. +with `Content-Type` header set to `application/json`. To disable this feature (and pass dicts to your +middleware) you can set `bottle.default_app().autojson` to `False`. #!Python @route('/api/status') def api_status(): return {'status':'online', 'servertime':time.time()} -You can turn off this feature: `bottle.default_app().autojson = False` -## HTTP errors and redirects - #!Python - from bottle import redirect, abort - - @route('/wrong/url') - def wrong(): - redirect("/right/url") - - @route('/restricted') - def restricted(): - abort(401, "Sorry, access denied.") +### Static Files -## Static files +You can directly return file objects, but `bottle.send_file()` is the recommended way to serve static files. +It automatically guesses a mime-type, adds a `Last-Modified` header, restricts paths to a `root` directory +for security reasons and generates appropriate error pages (401 on permission errors, 404 on missing files). It +even supports the `If-Modified-Since` header and eventually generates a `304 Not modified` response. +You can pass a custom mimetype to disable mimetype guessing. #!Python from bottle import send_file @@ -141,6 +245,45 @@ You can turn off this feature: `bottle.default_app().autojson = False` def static_file(filename): send_file(filename, root='/path/to/static/files') + @route('/images/:filename#.*\.png#') + def static_image(filename): + send_file(filename, root='/path/to/image/files', mimetype='image/png') + + + + +## HTTP Errors and Redirects + +The `bottle.abort(code[, message])` function is used to generate [HTTP error pages][http_code]. + + #!Python + from bottle import route, redirect, abort + @route('/restricted') + def restricted(): + abort(401, "Sorry, access denied.") + +To redirect a client to a different URL, you can send a `307 Temporary Redirect` response +with the `Location` header set to the new URL. `bottle.redirect(url[, code])` does that for you. +You may provide a different HTTP status code as a second parameter. + + #!Python + from bottle import route, redirect, abort + @route('/wrong/url') + def wrong(): + redirect("/right/url") + +Both functions interrupt your handler code (by throwing a `bottle.HTTPError` exception) so you don't +have to return anything. + +All unhandled exceptions other than `bottle.HTTPError` will result in a `500 Internal Server Error` +response, so they won't crash your WSGI server. + + + +# HTTP Stuff + +TODO + ## Cookies @@ -164,13 +307,14 @@ To set the `max-age` attribute (which is not a valid Python parameter name) you +## GET and POST values +TODO - -## Templates +# Templates Bottle uses its own little template engine by default. You can use a template by calling `template(template_name, **template_arguments)` and returning @@ -189,23 +333,22 @@ The `hello_template.tpl` file could look like this: <h1>Hello {{username}}</h1> <p>How are you?</p> -### Template search path -The list `bottle.TEMPLATE_PATH` is used to map template names to actual -file names. By default, this list contains `['./%s.tpl', './views/%s.tpl']`. -### Template caching - -Templates are cached in memory after compilation. Modifications made to -the template file will have no affect until you clear the template -cache. Call `bottle.TEMPLATES.clear()` to do so. +## Template search path +The list `bottle.TEMPLATE_PATH` is used to map template names to actual +file names. By default, this list contains `['./%s.tpl', './views/%s.tpl']`. +## Template caching +Templates are cached in memory after compilation. Modifications made to +the template file will have no affect until you clear the template +cache. Call `bottle.TEMPLATES.clear()` to do so. @@ -250,16 +393,9 @@ Example: +# Key/Value Databases - - - - - - - - -## Key/Value Databases +<div style="color:darkred">Warning: The included key/value database is depreciated.</div> Please switch to a [real](http://code.google.com/p/redis/) [key](http://couchdb.apache.org/) [value](http://www.mongodb.org/) [database](http://docs.python.org/library/anydbm.html). Bottle (>0.4.6) offers a persistent key/value database accessible through the `bottle.db` module variable. You can use key or attribute syntax to store or @@ -277,14 +413,20 @@ Printing a bucket object doesn't print the keys and values, and the `items()` and `values()` methods are not supported. Missing keys will raise `KeyError` as expected. -### Persistence + + + +## Persistence During a request live-cycle, all changes are cached in thread-local memory. At the end of the request, the changes are saved automatically so the next request will have access to the updated values. Each bucket is stored in a separate file in `bottle.DB_PATH`. Be sure to allow write-access to this path and use bucket names that are allowed in filenames. -### Race conditions + + + +## Race conditions You don't have do worry about file corruption but race conditions are still a problem in multi-threaded or forked environments. You can call `bottle.db.save()` or `botle.db.bucket_name.save()` to flush the thread-local @@ -292,7 +434,10 @@ memory cache to disk, but there is no way to detect database changes made in other threads until these threads call `bottle.db.save()` or leave the current request cycle. -### Example + + + +## Example #!Python from bottle import route, db @@ -306,13 +451,7 @@ request cycle. - - - - - - -## Using WSGI and Middleware +# 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. @@ -323,7 +462,10 @@ A call to `bottle.default_app()` returns your WSGI application. After applying a newapp = YourMiddleware(app) run(app=newapp) -### How default_app() works + + + +## How default_app() works Bottle creates a single instance of `bottle.Bottle()` and uses it as a default for most of the modul-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()`. @@ -337,7 +479,61 @@ Bottle creates a single instance of `bottle.Bottle()` and uses it as a default f run(app=mybottle) -## Deployment + + +# Development +Bottle has two features that may be helpfull during development. + +## Debug Mode + +In debug mode, bottle is much more verbose and tries to help you finding +bugs. You should never use debug mode in production environments. + + #!Python + import bottle + bottle.debug(True) + +This does the following: + + * Exceptions will print a stacktrace + * Error pages will contain that stacktrace + * Templates will not be cached. + + + + +## Auto Reloading + +During development, you have to restart the server a lot to test your +recent changes. The auto reloader can do this for you. Every time you +edit a module file, the reloader restarts the server process and loads +the newest version of your code. + + #!Python + from bottle import run + run(reloader=True) + +How it works: The main process will not start a server, but spawn a new +child process using the same command line agruments used to start the +main process. All module level code is executed at least twice! Be +carefull. + +The child process will have `os.environ['BOTTLE_CHILD']` set to `true` +and start as a normal non-reloading app server. As soon as any of the +loaded modules changes, the child process is terminated and respawned by +the main process. Changes in template files will not trigger a reload. +Please use debug mode to deactivate template caching. + +The reloading depends on the ability to stop the child process. If you are +running on Windows or any other operating system not supporting +`signal.SIGINT` (which raises `KeyboardInterrupt` in Python), +`signal.SIGTERM` is used to kill the child. Note that exit handlers and +finally clauses, etc., are not executed after a `SIGTERM`. + + + + +# Deployment Bottle uses the build-in `wsgiref.SimpleServer` by default. This non-threading HTTP server is perfectly fine for development and early production, @@ -349,7 +545,10 @@ There are three ways to eliminate this bottleneck: * Spread the load between multiple bottle instances * Do both -### Multi-Threaded Server + + + +## Multi-Threaded Server The easiest way to increase performance is to install a multi-threaded and WSGI-capable HTTP server like [Paste][paste], [flup][flup], [cherrypy][cherrypy] @@ -369,11 +568,14 @@ the server settings, you may want to manually set up your HTTP server and use from paste import httpserver httpserver.serve(myapp, host=host, port=port) -### Multiple Server Processes -A single Python process can only utilize one CPU at a time, even if + + +## 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 utilize all of your +between multiple independent Python processes to utilise all of your CPU cores. Instead of a single Bottle application server, you start one instances @@ -395,7 +597,10 @@ One of the fastest load balancer available is [pound](http://www.apsis.ch/pound/ I'll add examples for [lighttpd](http://www.lighttpd.net/) and [Apache](http://www.apache.org/) web servers soon. -### Apache mod_wsgi + + + +## Apache mod_wsgi Instead of running your own HTTP server from within Bottle, you can attach Bottle applications to an [Apache server][apache] using @@ -434,7 +639,12 @@ The Apache configuration may look like this: </VirtualHost> -### Google AppEngine + + +## Google AppEngine + +I didn't test this myself but several Bottle users reported that this +works just fine. #!Python import bottle @@ -443,7 +653,10 @@ The Apache configuration may look like this: # Do NOT use bottle.run() with AppEngine util.run_wsgi_app(bottle.default_app()) -### Good old CGI + + + +## Good old CGI CGI is slow as hell, but it works. @@ -452,10 +665,3 @@ CGI is slow as hell, but it works. # ... add or import your bottle app code here ... bottle.run(server=bottle.CGIServer) -[mako]: http://www.makotemplates.org/ -[cherrypy]: http://www.cherrypy.org/ -[flup]: http://trac.saddi.com/flup -[paste]: http://pythonpaste.org/ -[fapws3]: http://github.com/william-os4y/fapws3 -[apache]: http://www.apache.org/ -[mod_wsgi]: http://code.google.com/p/modwsgi/ |