CherryPy 基础


翻译自 CherryPy Basics

以下各节将引导您完成 CherryPy 应用程序的基础知识,并介绍一些基本概念。

1 一分钟的应用示例

您可以用 CherryPy 编写的最基本的应用程序几乎涉及其所有核心概念。

import cherrypy


class Root:
    @cherrypy.expose
    def index(self):
        return "Hello World!"


if __name__ == '__main__':
    cherrypy.quickstart(Root(), '/')

首先,对于大多数任务,您将只需要第 1 行中所示的单个 import 语句即可。在讨论这些内容之前,让我们跳到第 11 行,该行显示如何使用 CherryPy 服务器应用程序托管您的应用程序,以及如何在 / 路径中将其与内置的 HTTP 服务器一起使用。

现在回到实际的应用程序。即使 CherryPy 没有强制要求,大多数时候您的应用程序仍将被编写为 Python 类。这些类的方法将由 CherryPy 调用以响应客户端请求。但是,CherryPy 需要意识到可以使用这种方法,我们说该方法需要公开。这正是 cherrypy.expose 装饰器在第 5 行中所做的。

执行此程序,在你的浏览器定位到:http://127.0.0.1:8080 可以预览效果。

注意

CherryPy 是一个小型框架,专注于一项任务:接收 HTTP 请求并找到与请求的 URL 匹配的最合适的 Python 函数或方法。与其他知名框架不同,CherryPy 不提供对数据库访问,HTML 模板或任何其他中间件漂亮功能的内置支持。

简而言之,一旦 CherryPy 找到并调用了公开的方法,作为开发人员,您就应自行提供工具来实现应用程序的逻辑。

CherryPy 认为您(开发人员)最了解。

警告

前面的示例演示了 CherryPy 接口的简单性,但是您的应用程序可能还会包含其他一些细节:静态服务,更复杂的结构,数据库访问等。这将在教程部分中进行开发。

CherryPy 是一个微型框架,但不是一个裸露的框架,它带有一些基本工具来涵盖您期望的常用用法。

2 托管一个或多个应用程序

Web 应用程序需要访问 HTTP 服务器。 CherryPy 提供了自己的,可投入生产的 HTTP 服务器。有两种方法来托管应用程序。

2.1 单一应用

最直接的方法是使用 cherrypy.quickstart 函数。它需要至少一个参数,即要托管的应用程序实例。另外两个设置是可选的。首先,可以从中访问应用程序的基本路径。其次,使用配置字典或文件来配置您的应用程序。

cherrypy.quickstart(Blog())
cherrypy.quickstart(Blog(), '/blog')
cherrypy.quickstart(Blog(), '/blog', {'/': {'tools.gzip.on': True}})

第一个意味着您的应用程序将在 http://hostname:port/ 上可用,而另两个将使您的博客应用程序在 http://hostname:port/blog 上可用。此外,最后一个为应用程序提供了特定的设置。

注意

注意在第三种情况下,设置如何仍然相对于应用程序,而不是在何处可用,因此使用 {'/': ... } 而不是 {'/blog': ... }。

2.2 多元应用

cherrypy.quickstart 方法适用于单个应用程序,但缺乏使用服务器托管多个应用程序的能力。 为此,必须使用 cherrypy.tree.mount 函数,如下所示:

cherrypy.tree.mount(Blog(), '/blog', blog_conf)
cherrypy.tree.mount(Forum(), '/forum', forum_conf)

cherrypy.engine.start()
cherrypy.engine.block()

本质上,cherrypy.tree.mount 具有与 cherrypy.quickstart 相同的参数:应用程序,托管路径段和配置。最后两行只是启动应用程序服务器。

重要

cherrypy.quickstartcherrypy.tree.mount 不是唯一的。例如,前几行可以写成:

cherrypy.tree.mount(Blog(), '/blog', blog_conf)
cherrypy.quickstart(Forum(), '/forum', forum_conf)

3 Logging

日志记录(Logging)是任何应用程序中的重要任务。CherryPy 将记录所有传入的请求以及协议错误。

为此,CherryPy 管理着两个记录器:

  1. 记录每个传入请求的访问权限
  2. 跟踪错误或其他应用程序级别消息的应用程序/错误日志

您的应用程序可以通过调用 cherrypy.log 来利用第二个记录器。

cherrypy.log("hello there")

您还可以记录异常:

try:
   ...
except Exception:
   cherrypy.log("kaboom!", traceback=True)

这两个日志都将写入由配置中的以下键标识的文件:

  • 使用通用日志格式的传入请求的 log.access_file
  • 其他日志的 log.error_file

也可以参考

有关 CherryPy 的日志记录体系结构的更多详细信息,请参阅 cherrypy._cplogging 模块。

3.1 Disable logging

您可能有兴趣禁用某个日志。

要禁用文件日志记录,只需在全局配置中为 log.access_filelog.error_file 键对应更多值设置一个空字符串。

要禁用控制台日志记录,请将 log.screen 设置为 False

cherrypy.config.update({'log.screen': False,
                        'log.access_file': '',
                        'log.error_file': ''})

3.2 与您的其他记录器一起玩

您的应用程序可能显然已经在使用日志记录模块来跟踪应用程序级别的消息。下面是一个简单的设置示例。

import logging
import logging.config

import cherrypy

logger = logging.getLogger()
db_logger = logging.getLogger('db')

LOG_CONF = {
    'version': 1,

    'formatters': {
        'void': {
            'format': ''
        },
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level':'INFO',
            'class':'logging.StreamHandler',
            'formatter': 'standard',
            'stream': 'ext://sys.stdout'
        },
        'cherrypy_console': {
            'level':'INFO',
            'class':'logging.StreamHandler',
            'formatter': 'void',
            'stream': 'ext://sys.stdout'
        },
        'cherrypy_access': {
            'level':'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'void',
            'filename': 'access.log',
            'maxBytes': 10485760,
            'backupCount': 20,
            'encoding': 'utf8'
        },
        'cherrypy_error': {
            'level':'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'void',
            'filename': 'errors.log',
            'maxBytes': 10485760,
            'backupCount': 20,
            'encoding': 'utf8'
        },
    },
    'loggers': {
        '': {
            'handlers': ['default'],
            'level': 'INFO'
        },
        'db': {
            'handlers': ['default'],
            'level': 'INFO' ,
            'propagate': False
        },
        'cherrypy.access': {
            'handlers': ['cherrypy_access'],
            'level': 'INFO',
            'propagate': False
        },
        'cherrypy.error': {
            'handlers': ['cherrypy_console', 'cherrypy_error'],
            'level': 'INFO',
            'propagate': False
        },
    }
}

class Root:
    @cherrypy.expose
    def index(self):

        logger.info("boom")
        db_logger.info("bam")
        cherrypy.log("bang")

        return "hello world"

if __name__ == '__main__':
    cherrypy.config.update({'log.screen': False,
                            'log.access_file': '',
                            'log.error_file': ''})
cherrypy.engine.unsubscribe('graceful', cherrypy.log.reopen_files)
    logging.config.dictConfig(LOG_CONF)
    cherrypy.quickstart(Root())

在此代码段中,我们创建一个配置字典,然后将其传递到 logging 模块以配置记录器:

  • 默认的根记录器与单个流处理程序关联
  • db 后端的记录器,还有一个流处理程序

另外,我们重新配置 CherryPy 记录器:

  • 顶级 cherrypy.access 记录器,将请求记录到文件中
  • cherrypy.error 记录器,将其他所有内容记录到文件中并登录到控制台

当自动重新加载程序启动时,我们还阻止 CherryPy 尝试打开其日志文件。由于我们甚至都不让 CherryPy 首先打开它们,因此这不是严格要求的。但是,这样可以避免浪费时间在无用的东西上。

4 Configuring

CherryPy 带有细粒度的配置机制,可以在各种级别上进行设置。

也可以参考

复习了基础知识后,请参考有关配置的深入讨论

4.1 Global server configuration

要配置 HTTP 和应用程序服务器,请使用 cherrypy.config.update 方法。

cherrypy.config.update({'server.socket_port': 9090})

cherrypy.config 对象是一个字典,update 方法将传递的字典合并到其中。您也可以改为传递文件(假设使用 server.conf 文件):

[global]
server.socket_port: 9090
cherrypy.config.update("server.conf")

警告

cherrypy.config.update 并非用于配置应用程序。这是一个常见的错误。它用于配置服务器和引擎。

4.2 Per-application configuration

要配置您的应用程序,请在将应用程序与服务器关联时传入字典或文件。

cherrypy.quickstart(myapp, '/', {'/': {'tools.gzip.on': True}})

或通过文件(例如,称为 app.conf):

[/]
tools.gzip.on: True
cherrypy.quickstart(myapp, '/', "app.conf")

尽管您可以全局方式定义大多数配置,但有时在代码中应用它们的位置定义它们很方便。

class Root:
    @cherrypy.expose
    @cherrypy.tools.gzip()
    def index(self):
        return "hello world!"

上面的变体符号:

class Root:
    @cherrypy.expose
    def index(self):
        return "hello world!"
    index._cp_config = {'tools.gzip.on': True}

两种方法具有相同的效果,因此请选择最适合您的样式的方法。

4.3 Additional application settings

您可以添加非特定于请求URL的设置,并从页面处理程序中检索它们,如下所示:

[/]
tools.gzip.on: True

[googleapi]
key = "..."
appid = "..."
class Root:
    @cherrypy.expose
    def index(self):
        google_appid = cherrypy.request.app.config['googleapi']['appid']
        return "hello world!"

cherrypy.quickstart(Root(), '/', "app.conf")

5 Cookies

CherryPy 使用 Python 中的 Cookie 模块,尤其是 Cookie.SimpleCookie 对象类型来处理 Cookie。

  • 要将 Cookie 发送到浏览器,请设置 cherrypy.response.cookie[key] = value
  • 要检索浏览器发送的 cookie,请使用 cherrypy.request.cookie[key]
  • 要删除 cookie(在客户端),必须发送其有效时间设置为 0 的 cookie:
cherrypy.response.cookie[key] = value
cherrypy.response.cookie[key]['expires'] = 0

请务必注意,请求 Cookie 不会自动复制到响应 Cookie 中。客户端将在每个请求上发送相同的 cookie,因此每次都应填充 cherrypy.request.cookie
但是服务器不需要在每次响应时都发送相同的 cookie;因此,cherrypy.response.cookie 通常为空。故而,当您希望“delete”(过期)cookie 时,必须首先设置
cherrypy.response.cookie[key] = value,然后将其 expires 属性设置为 0

扩展示例:


class MyCookieApp:
    @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')

6 Using sessions

会话是开发人员用来识别用户并同步其活动的最常用机制之一。默认情况下,CherryPy 不激活会话,因为它不是必需的功能,要使其启用,只需在配置中添加以下设置:

[/]
tools.sessions.on: True
cherrypy.quickstart(myapp, '/', "app.conf")

默认情况下,会话存储在 RAM 中,因此,如果重新启动服务器,则所有当前会话都将丢失。您可以将它们存储在 memcached 或文件系统中。

在应用程序中使用会话的操作如下:

import cherrypy

@cherrypy.expose
def index(self):
    if 'count' not in cherrypy.session:
       cherrypy.session['count'] = 0
    cherrypy.session['count'] += 1

在此代码段中,每次调用索引页面处理程序时,当前用户的会话的 'count' 键都增加 1。

CherryPy 通过检查与请求一起发送的 cookie 来知道要使用哪个会话。此 Cookie 包含 CherryPy 用于从存储中加载用户会话的会话标识符。

也可以看看

有关会话接口和实现的更多详细信息,请参阅 cherrypy.lib.sessions 模块。值得注意的是,您将了解会话到期。

6.1 Filesystem backend

使用文件系统很简单,不会在重新启动之间丢失会话。每个会话都保存在给定目录中的自己的文件中。

[/]
tools.sessions.on: True
tools.sessions.storage_class = cherrypy.lib.sessions.FileSession
tools.sessions.storage_path = "/some/directory"

6.2 Memcached backend

Memcached 是 RAM 上流行的密钥库,它是分布式的,如果您想在运行 CherryPy 的进程之外共享会话,它是一个不错的选择。要求安装 Python memcached 软件包,这可以通过安装 cherrypy[memcached_session] 来指示。

[/]
tools.sessions.on: True
tools.sessions.storage_class = cherrypy.lib.sessions.MemcachedSession

6.3 Other backends

任何其他库都可以实现会话后端。只需将 cherrypy.lib.sessions.Session 子类化,并将该子类表示为 tools.sessions.storage_class

7 Static content serving

CherryPy 可以提供您的静态内容,例如图像,JavaScript 和 CSS 资源等。

笔记

CherryPy 使用 mimetypes 模块来确定服务特定资源的最佳内容类型。如果选择无效,则可以如下设置更多的媒体类型:

import mimetypes
mimetypes.types_map['.csv'] = 'text/csv'

7.1 Serving a single file

您可以按以下方式提供单个文件:

[/style.css]
tools.staticfile.on = True
tools.staticfile.filename = "/home/site/style.css"

CherryPy 将自动响应 URL,例如 http://hostname/style.css

7.2 Serving a whole directory

服务整个目录类似于单个文件:

[/static]
tools.staticdir.on = True
tools.staticdir.dir = "/home/site/static"

假设您在 static/js/my.js 中有一个文件,CherryPy 将自动响应 URL,例如 http://hostname/static/js/my.js

注意

CherryPy 始终需要将要服务的文件或目录的绝对路径。如果要配置多个静态部分,但它们位于同一根目录中,则可以使用以下快捷方式:

[/]
tools.staticdir.root = "/home/site"

[/static]
tools.staticdir.on = True
tools.staticdir.dir = "static"

7.3 指定 index 文件

默认情况下,指示未找到路径“/”的静态目录的根,CherryPy 将响应 404 错误。要指定索引文件,可以使用以下命令:

[/static]
tools.staticdir.on = True
tools.staticdir.dir = "/home/site/static"
tools.staticdir.index = "index.html"

假设您在 static/index.html 上有一个文件,CherryPy 将通过返回其内容自动响应 URL,例如 http://hostname/static/

7.4 允许下载文件

使用 "application/x-download" 响应内容类型,您可以告诉浏览器应该将资源下载到用户的计算机上而不是显示。

例如,您可以编写一个页面处理程序,如下所示:

from cherrypy.lib.static import serve_file

@cherrypy.expose
def download(self, filepath):
    return serve_file(filepath, "application/x-download", "attachment")

假设文件路径是您计算机上的有效路径,那么浏览器会将响应视为可下载的内容。

警告

上面的页面处理程序本身就有安全风险,因为可以访问服务器的任何文件(如果运行服务器的用户对其具有权限)。

8 Dealing with JSON

CherryPy 具有对请求和/或响应的 JSON 编码和解码的内置支持。

8.1 Decoding request

要使用 JSON 自动解码请求的内容,请执行以下操作:

class Root:
    @cherrypy.expose
    @cherrypy.tools.json_in()
    def index(self):
        data = cherrypy.request.json

附加到请求的 json 属性包含解码后的内容。

8.2 Encoding response

要使用 JSON 自动编码响应的内容,请执行以下操作:

@cherrypy.expose
@cherrypy.tools.json_out()
def index(self):
    return {'key': 'value'}

CherryPy 将使用 JSON 对您的页面处理程序返回的所有内容进行编码。并非所有类型的对象都可以本地编码。

9 Authentication

CherryPy 支持以下两种非常简单的基于 HTTP 的身份验证机制,在 RFC 7616RFC 7617(已淘汰 RFC 2617)中进行了描述:Basic 和 Digest。众所周知,它们会触发浏览器的弹出窗口,询问用户其名称和密码。

9.1 Basic

基本身份验证是最简单的身份验证形式,但是由于用户的凭据已嵌入到请求中,因此它不是安全的形式。除非您在 SSL 上或封闭的网络中运行,否则我们建议不要使用它。

from cherrypy.lib import auth_basic

USERS = {'jon': 'secret'}

def validate_password(realm, 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,
       'tools.auth_basic.accept_charset': 'UTF-8',
    }
}

cherrypy.quickstart(myapp, '/', conf)

简而言之,您必须提供一个由 CherryPy 调用的函数,该函数传递从请求中解码的用户名和密码。

该函数可以从其必须具有的任何源中读取其数据:文件,数据库,内存等。

9.2 Digest

摘要式身份验证的不同之处在于,凭据不是由请求携带的,因此它比基本身份验证更为安全。

CherryPy 的摘要支持具有与上述基本支持类似的界面。

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',
        'tools.auth_digest.accept_charset': 'UTF-8',
   }
}

cherrypy.quickstart(myapp, '/', conf)

9.3 SO_PEERCRED

UNIX 文件和抽象套接字还具有低级身份验证。这是启用它的方式:

[global]
server.peercreds: True
server.peercreds_resolve: True
server.socket_file: /var/run/cherrypy.sock

server.peercreds 允许查找连接的进程 ID,用户 ID 和组ID。它们可以作为 WSGI 环境变量进行访问:

  • X_REMOTE_PID
  • X_REMOTE_UID
  • X_REMOTE_GID

server.peercreds_resolve 将其解析为用户名和组名。它们可以作为 WSGI 环境变量进行访问:

  • X_REMOTE_USER and REMOTE_USER
  • X_REMOTE_GROUP

10 Favicon

CherryPy 使用静态文件工具将其自己的甜红色 cherrypy 作为默认图标提供服务。您可以按以下方式提供自己的网站图标:

import cherrypy

class HelloWorld:
   @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'
            }
        }
    )

有关更多详细信息,请参阅静态服务部分。

您还可以使用文件进行配置:

[/favicon.ico]
tools.staticfile.on: True
tools.staticfile.filename: "/path/to/myfavicon.ico"
import cherrypy

class HelloWorld:
@cherrypy.expose
def index(self):
    return "Hello World!"

if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld(), '/', "app.conf")

文章作者: xinetzone
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 xinetzone !
评论
  目录