diff options
author | Jason R. Coombs <jaraco@jaraco.com> | 2014-06-27 16:36:28 -0400 |
---|---|---|
committer | Jason R. Coombs <jaraco@jaraco.com> | 2014-06-27 16:36:28 -0400 |
commit | b01148e71c1a514cd5bf207dba5ea518f5b9fcaf (patch) | |
tree | 32596c1b71053a21e66ec097a484b6e91ccb2b5e /docs/basics.rst | |
parent | cafda16167a4b6a2ea64313597b76c2cfe0cbc0a (diff) | |
download | cherrypy-git-b01148e71c1a514cd5bf207dba5ea518f5b9fcaf.tar.gz |
Move docs to docs/
Diffstat (limited to 'docs/basics.rst')
-rw-r--r-- | docs/basics.rst | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/docs/basics.rst b/docs/basics.rst new file mode 100644 index 00000000..8129fc45 --- /dev/null +++ b/docs/basics.rst @@ -0,0 +1,732 @@ +.. _basics: + +Basics +------ + +The following sections will drive you through the basics of +a CherryPy application, introducing some essential concepts. + +.. contents:: + :depth: 4 + +The one-minute application example +################################## + +The most basic application you can write with CherryPy +involves almost all its core concepts. + +.. code-block:: python + :linenos: + + import cherrypy + + class Root(object): + @cherrypy.expose + def index(self): + return "Hello World!" + + if __name__ == '__main__': + cherrypy.quickstart(Root(), '/') + + +First and foremost, for most tasks, you will never need more than +a single import statement as demonstrated in line 1. + +Before discussing the meat, let's jump to line 9 which shows, +how to host your application with the CherryPy application server +and serve it with its builtin HTTP server at the `'/'` path. +All in one single line. Not bad. + +Let's now step back to the actual application. Even though CherryPy +does not mandate it, most of the time your applications +will be written as Python classes. Methods of those classes will +be called by CherryPy to respond to client requests. However, +CherryPy needs to be aware that a method can be used that way, we +say the method needs to be :term:`exposed`. This is precisely +what the :func:`cherrypy.expose()` decorator does in line 4. + +Save the snippet in a file named `myapp.py` and run your first +CherryPy application: + +.. code-block:: bash + + $ python myapp.py + +Then point your browser at http://127.0.0.1:8080. Tada! + + +.. note:: + + CherryPy is a small framework that focuses on one single task: + take a HTTP request and locate the most appropriate + Python function or method that match the request's URL. + Unlike other well-known frameworks, CherryPy does not + provide a built-in support for database access, HTML + templating or any other middleware nifty features. + + In a nutshell, once CherryPy has found and called an + :term:`exposed` method, it is up to you, as a developer, to + provide the tools to implement your application's logic. + + CherryPy takes the opinion that you, the developer, know best. + +.. warning:: + + The previous example demonstrated the simplicty of the + CherryPy interface but, your application will likely + contain a few other bits and pieces: static service, + more complex structure, database access, etc. + This will be developed in the tutorial section. + + +CherryPy is a minimal framework but not a bare one, it comes +with a few basic tools to cover common usages that you would +expect. + +Hosting one or more applications +################################ + +A web application needs an HTTP server to be accessed to. CherryPy +provides its own, production ready, HTTP server. There are two +ways to host an application with it. The simple one and the almost-as-simple one. + +Single application +^^^^^^^^^^^^^^^^^^ + +The most straightforward way is to use :func:`cherrypy.quickstart` +function. It takes at least one argument, the instance of the +application to host. Two other settings are optionals. First, the +base path at which the application will be accessible from. Second, +a config dictionary or file to configure your application. + +.. code-block:: python + + cherrypy.quickstart(Blog()) + cherrypy.quickstart(Blog(), '/blog') + cherrypy.quickstart(Blog(), '/blog', {'/': {'tools.gzip.on': True}}) + +The first one means that your application will be available at +http://hostname:port/ whereas the other two will make your blog +application available at http://hostname:port/blog. In addition, +the last one provides specific settings for the application. + +.. note:: + + Notice in the third case how the settings are still + relative to the application, not where it is made available at, + hence the `{'/': ... }` rather than a `{'/blog': ... }` + + +Multiple applications +^^^^^^^^^^^^^^^^^^^^^ + +The :func:`cherrypy.quickstart` approach is fine for a single application, +but lacks the capacity to host several applications with the server. +To achieve this, one must use the :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>` +function as follows: + +.. code-block:: python + + cherrypy.tree.mount(Blog(), '/blog', blog_conf) + cherrypy.tree.mount(Forum(), '/forum', forum_conf) + + cherrypy.engine.start() + cherrypy.engine.block() + +Essentially, :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>` +takes the same parameters as :func:`cherrypy.quickstart`: an :term:`application`, +a hosting path segment and a configuration. The last two lines +are simply starting application server. + +.. important:: + + :func:`cherrypy.quickstart` and :meth:`cherrypy.tree.mount <cherrypy._cptree.Tree.mount>` + are not exclusive. For instance, the previous lines can be written as: + + .. code-block:: python + + cherrypy.tree.mount(Blog(), '/blog', blog_conf) + cherrypy.quickstart(Forum(), '/forum', forum_conf) + +.. note:: + + You can also :ref:`host foreign WSGI application <hostwsgiapp>`. + + +Logging +####### + +Logging is an important task in any application. CherryPy will +log all incoming requests as well as protocol errors. + +To do so, CherryPy manages two loggers: + +- an access one that logs every incoming requests +- an application/error log that traces errors or other application-level messages + +Your application may leverage that second logger by calling +:func:`cherrypy.log() <cherrypy._cplogging.LogManager.error>`. + +.. code-block:: python + + cherrypy.log("hello there") + +You can also log an exception: + +.. code-block:: python + + try: + ... + except: + cherrypy.log("kaboom!", traceback=True) + +Both logs are writing to files identified by the following keys +in your configuration: + +- ``log.access_file`` for incoming requests using the + `common log format <http://en.wikipedia.org/wiki/Common_Log_Format>`_ +- ``log.error_file`` for the other log + +.. seealso:: + + Refer to the :mod:`cherrypy._cplogging` module for more + details about CherryPy's logging architecture. + +Disable logging +^^^^^^^^^^^^^^^ + +You may be interested in disabling either logs. + +To disable file logging, simply set a en empty string to the +``log.access_file`` or ``log.error_file`` keys in your +:ref:`global configuration <globalsettings>`. + +To disable, console logging, set ``log.screen`` to `False`. + + +Play along with your other loggers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Your application may obviously already use the :mod:`logging` +module to trace application level messages. CherryPy will not +interfere with them as long as your loggers are explicitely +named. This would work nicely: + +.. code-block:: python + + import logging + logger = logging.getLogger('myapp.mypackage') + logger.setLevel(logging.INFO) + stream = logging.StreamHandler() + stream.setLevel(logging.INFO) + logger.addHandler(stream) + +.. _config: + +Configuring +########### + +CherryPy comes with a fine-grained configuration mechanism and +settings can be set at various levels. + +.. seealso:: + + Once you have the reviewed the basics, please refer + to the :ref:`in-depth discussion <configindepth>` + around configuration. + +.. _globalsettings: + +Global server configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To configure the HTTP and application servers, +use the :meth:`cherrypy.config.update() <cherrypy._cpconfig.Config.update>` +method. + +.. code-block:: python + + cherrypy.config.update({'server.socket_port': 9090}) + +The :mod:`cherrypy.config <cherrypy._cpconfig>` object is a dictionary and the +update method merges the passed dictionary into it. + +You can also pass a file instead (assuming a `server.conf` +file): + +.. code-block:: ini + + [global] + server.socket_port: 9090 + +.. code-block:: python + + cherrypy.config.update("server.conf") + +.. warning:: + + :meth:`cherrypy.config.update() <cherrypy._cpconfig.Config.update>` + is not meant to be used to configure the application. + It is a common mistake. It is used to configure the server and engine. + +.. _perappconf: + +Per-application configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To configure your application, pass in a dictionary or a file +when you associate their application to the server. + +.. code-block:: python + + cherrypy.quickstart(myapp, '/', {'/': {'tools.gzip.on': True}}) + +or via a file (called `app.conf` for instance): + +.. code-block:: ini + + [/] + tools.gzip.on: True + +.. code-block:: python + + cherrypy.quickstart(myapp, '/', "app.conf") + +Although, you can define most of your configuration in a global +fashion, it is sometimes convenient to define them +where they are applied in the code. + +.. code-block:: python + + class Root(object): + @cherrypy.expose + @cherrypy.tools.gzip() + def index(self): + return "hello world!" + +A variant notation to the above: + +.. code-block:: python + + class Root(object): + @cherrypy.expose + def index(self): + return "hello world!" + index._cp_config = {'tools.gzip.on': True} + +Both methods have the same effect so pick the one +that suits your style best. + +Additional application settings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can add settings that are not specific to a request URL +and retrieve them from your page handler as follows: + +.. code-block:: ini + + [/] + tools.gzip.on: True + + [googleapi] + key = "..." + appid = "..." + +.. code-block:: python + + class Root(object): + @cherrypy.expose + def index(self): + google_appid = cherrypy.request.app.config['googleapi']['appid'] + return "hello world!" + + cherrypy.quickstart(Root(), '/', "app.conf") + + +Cookies +####### + +CherryPy uses the :mod:`Cookie` module from python and in particular the +:class:`Cookie.SimpleCookie` object type to handle cookies. + +- To send a cookie to a browser, set ``cherrypy.response.cookie[key] = value``. +- To retrieve a cookie sent by a browser, use ``cherrypy.request.cookie[key]``. +- To delete a cookie (on the client side), you must *send* the cookie with its + expiration time set to `0`: + +.. code-block:: python + + cherrypy.response.cookie[key] = value + cherrypy.response.cookie[key]['expires'] = 0 + +It's important to understand that the request cookies are **not** automatically +copied to the response cookies. Clients will send the same cookies on every +request, and therefore ``cherrypy.request.cookie`` should be populated each +time. But the server doesn't need to send the same cookies with every response; +therefore, ``cherrypy.response.cookie`` will usually be empty. When you wish +to “delete” (expire) a cookie, therefore, you must set +``cherrypy.response.cookie[key] = value`` first, and then set its ``expires`` +attribute to 0. + +Extended example: + +.. code-block:: python + + import cherrypy + + class MyCookieApp(object): + @cherrypy.expose + def set(self): + cookie = cherrypy.response.cookie + cookie['cookieName'] = 'cookieValue' + cookie['cookieName']['path'] = '/' + cookie['cookieName']['max-age'] = 3600 + cookie['cookieName']['version'] = 1 + return "<html><body>Hello, I just sent you a cookie</body></html>" + + @cherrypy.expose + def read(self): + cookie = cherrypy.request.cookie + res = """<html><body>Hi, you sent me %s cookies.<br /> + Here is a list of cookie names/values:<br />""" % len(cookie) + for name in cookie.keys(): + res += "name: %s, value: %s<br>" % (name, cookie[name].value) + return res + "</body></html>" + + if __name__ == '__main__': + cherrypy.quickstart(MyCookieApp(), '/cookie') + + +.. _basicsession: + +Using sessions +############## + +Sessions are one of the most common mechanism used by developers to +identify users and synchronize their activity. By default, CherryPy +does not activate sessions because it is not a mandatory feature +to have, to enable it simply add the following settings in your +configuration: + +.. code-block:: ini + + [/] + tools.sessions.on: True + +.. code-block:: python + + cherrypy.quickstart(myapp, '/', "app.conf") + +Sessions are, by default, stored in RAM so, if you restart your server +all of your current sessions will be lost. You can store them in memcached +or on the filesystem instead. + +Using sessions in your applications is done as follows: + +.. code-block:: python + + import cherrypy + + @cherrypy.expose + def index(self): + if 'count' not in cherrypy.session: + cherrypy.session['count'] = 0 + cherrypy.session['count'] += 1 + +In this snippet, everytime the the index page handler is called, +the current user's session has its `'count'` key incremented by `1`. + +CherryPy knows which session to use by inspecting the cookie +sent alongside the request. This cookie contains the session +identifier used by CherryPy to load the user's session from +the storage. + +.. seealso:: + + Refer to the :mod:`cherrypy.lib.sessions` module for more + details about the session interface and implementation. + Notably you will learn about sessions expiration. + +Filesystem backend +^^^^^^^^^^^^^^^^^^ + +Using a filesystem is a simple to not lose your sessions +between reboots. Each session is saved in its own file within +the given directory. + +.. code-block:: ini + + [/] + tools.sessions.on: True + tools.sessions.storage_type = "file" + tools.sessions.storage_path = "/some/directorys" + +Memcached backend +^^^^^^^^^^^^^^^^^ + +`Memcached <http://memcached.org/>`_ is a popular key-store on top of your RAM, +it is distributed and a good choice if you want to +share sessions outside of the process running CherryPy. + +.. code-block:: ini + + [/] + tools.sessions.on: True + tools.sessions.storage_type = "memcached" + +.. _staticontent: + +Static content serving +###################### + +CherryPy can serve your static content such as images, javascript and +CSS resources, etc. + +.. note:: + + CherryPy uses the :mod:`mimetypes` module to determine the + best content-type to serve a particular resource. If the choice + is not valid, you can simply set more media-types as follows: + + .. code-block:: python + + import mimetypes + mimetypes.types_map['.csv'] = 'text/csv' + + +Serving a single file +^^^^^^^^^^^^^^^^^^^^^ + +You can serve a single file as follows: + +.. code-block:: ini + + [/style.css] + tools.staticfile.on = True + tools.staticfile.filename = "/home/site/style.css" + +CherryPy will automatically respond to URLs such as +`http://hostname/style.css`. + +Serving a whole directory +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Serving a whole directory is similar to a single file: + +.. code-block:: ini + + [/static] + tools.staticdir.on = True + tools.staticdir.dir = "/home/site/static" + +Assuming you have a file at `static/js/my.js`, +CherryPy will automatically respond to URLs such as +`http://hostname/static/js/my.js`. + + +.. note:: + + CherryPy always requires the absolute path to the files or directories + it will serve. If you have several static sections to configure + but located in the same root directory, you can use the following + shortcut: + + + .. code-block:: ini + + [/] + tools.staticdir.root = "/home/site" + + [/static] + tools.staticdir.on = True + tools.staticdir.dir = "static" + +Allow files downloading +^^^^^^^^^^^^^^^^^^^^^^^ + +Using ``"application/x-download"`` response content-type, +you can tell a browser that a resource should be downloaded +onto the user's machine rather than displayed. + +You could for instance write a page handler as follows: + +.. code-block:: python + + from cherrypy.lib.static import serve_file + + @cherrypy.expose + def download(self, filepath): + return serve_file(filepath, "application/x-download", "attachment") + +Assuming the filepath is a valid path on your machine, the +response would be considered as a downloadable content by +the browser. + +.. warning:: + + The above page handler is a security risk on its own since any file + of the server could be accessed (if the user running the + server had permissions on them). + + +Dealing with JSON +################# + +CherryPy has built-in support for JSON encoding and decoding +of the request and/or response. + +Decoding request +^^^^^^^^^^^^^^^^ + +To automatically decode the content of a request using JSON: + +.. code-block:: python + + class Root(object): + @cherrypy.expose + @cherrypy.tools.json_in() + def index(self): + data = cherrypy.request.json + +The `json` attribute attached to the request contains +the decoded content. + +Encoding response +^^^^^^^^^^^^^^^^^ + +To automatically encode the content of a response using JSON: + +.. code-block:: python + + class Root(object): + @cherrypy.expose + @cherrypy.tools.json_out() + def index(self): + return {'key': 'value'} + +CherryPy will encode any content returned by your page handler +using JSON. Not all type of objects may natively be +encoded. + +Authentication +############## + +CherryPy provides support for two very simple authentication mechanisms, +both described in :rfc:`2617`: Basic and Digest. They are most commonly +known to trigger a browser's popup asking users their name +and password. + +Basic +^^^^^ + +Basic authentication is the simplest form of authentication however +it is not a secure one as the user's credentials are embedded into +the request. We advise against using it unless you are running on +SSL or within a closed network. + +.. code-block:: python + + from cherrypy.lib import auth_basic + + USERS = {'jon': 'secret'} + + def validate_password(username, password): + if username in USERS and USERS[username] == password: + return True + return False + + conf = { + '/protected/area': { + 'tools.auth_basic.on': True, + 'tools.auth_basic.realm': 'localhost', + 'tools.auth_basic.checkpassword': validate_password + } + } + + cherrypy.quickstart(myapp, '/', conf) + +Simply put, you have to provide a function that will +be called by CherryPy passing the username and password +decoded from the request. + +The function can read its data from any source it has to: a file, +a database, memory, etc. + + +Digest +^^^^^^ + +Digest authentication differs by the fact the credentials +are not carried on by the request so it's a little more secure +than basic. + +CherryPy's digest support has a similar interface to the +basic one explained above. + +.. code-block:: python + + from cherrypy.lib import auth_digest + + USERS = {'jon': 'secret'} + + conf = { + '/protected/area': { + 'tools.auth_digest.on': True, + 'tools.auth_digest.realm': 'localhost', + 'tools.auth_digest.get_ha1': auth_digest.get_ha1_dict_plain(USERS), + 'tools.auth_digest.key': 'a565c27146791cfb' + } + } + + cherrypy.quickstart(myapp, '/', conf) + +Favicon +####### + +CherryPy serves its own sweet red cherrypy as the default +`favicon <http://en.wikipedia.org/wiki/Favicon>`_ using the static file +tool. You can serve your own favicon as follows: + +.. code-block:: python + + import cherrypy + + class HelloWorld(object): + @cherrypy.expose + def index(self): + return "Hello World!" + + if __name__ == '__main__': + cherrypy.quickstart(HelloWorld(), '/', + { + '/favicon.ico': + { + 'tools.staticfile.on': True, + 'tools.staticfile.filename:' '/path/to/myfavicon.ico' + } + } + ) + +Please refer to the :ref:`static serving <staticontent>` section +for more details. + +You can also use a file to configure it: + +.. code-block:: ini + + [/favicon.ico] + tools.staticfile.on: True + tools.staticfile.filename: "/path/to/myfavicon.ico" + + +.. code-block:: python + + import cherrypy + + class HelloWorld(object): + @cherrypy.expose + def index(self): + return "Hello World!" + + if __name__ == '__main__': + cherrypy.quickstart(HelloWorld(), '/', app.conf) |