【Python Web】Pecan学习:框架原理分析和示例

本文深入剖析了Pecan框架的原理与使用方法,详细介绍了WSGI接口的基础知识及Pecan框架的核心特性,包括对象分发式路由、配置方式、根控制器的使用等,并演示了如何利用WSME规范API响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WSGI

在正式了解Pecan之前,需要介绍WSGI的基本原理:
WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server如何与web application通信的规范。server和application的规范在PEP 3333中有具体描述。要实现WSGI协议,必须同时实现web server和web application,当前运行在WSGI协议之上的web框架有Torando,Flask,Django等

Web应用的本质:

  1. 浏览器发送一个HTTP请求
  2. 服务器收到请求,生成一个HTML文档
  3. 服务器把HTML文档作为HTTP响应的body发给浏览器
  4. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示

上面的web应用过程,如果由我们自己来实现是复杂的,涉及到接收HTTP请求,解析HTTP请求,响应HTTP请求等。通常这些操作都由WSGI服务器来完成,WSGI(Web Server Gateway Interface)定义了WSGI服务器执行的接口规范,我们只需要编写符合WSGI规范的接口,然后由WSGI服务器来执行,就可以了

WSGI接口编写示例:

def application(environ,start_response):
    start_response('200 OK',[('Content-Type','text/html')])
    return '<h1>Hello,web!</h1>'

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,其中的参数:

  • environ:包含HTTP请求信息的dict对象
  • start_response:发送HTTP响应的函数

函数说明:start_response(‘200 OK’, [(‘Content-Type’, ‘text/html’)]):发送HTTP响应的Header,Header只能发送一次,意思是start_response函数只能执行一次。'200 OK’是HTTP响应码参数,[(‘Content-Type’, ‘text/html’)]表示HTTP Header,函数的返回值作为HTTP响应body发送给服务器。

为什么要使用Pecan?Pecan是一个路由对象分发的轻量级Python web框架。本质上可以将url通过分割为每一部分,然后对每一部分查找对应处理该URL部分的处理类,处理后,继续交给后面部分的URL处理,直到所有URL部分都被处理后,调用最后分割的URL对应的处理函数处理。

Pecan

以下内容翻译自Pecan官方教程:https://pecan.readthedocs.io/en/latest/

欢迎使用Pecan,这是一个受CherryPy,TurboGears和Pylons启发的轻量级Python Web框架。 Pecan最初由ShootQ的开发人员在Pictage工作时创建。

Pecan的创建填补了Python网络框架世界中的空白——因为它是一个非常轻巧的框架,它提供了对象分派(object-dispatch)样式路由。 Pecan并非旨在成为“全栈”框架,因此不提供对会话或数据库之类的开箱即用的支持(尽管一些教程包含了这些内容,使用者可以自己将其整合到几行代码中)。Pecan相反地专注于HTTP本身。

虽然它很轻巧,但是Pecan提供了广泛的功能集,可以将其用于构建基于HTTP的应用程序,包括:

  • 对路由基于对象分派策略
  • 全面支持REST风格的控制器
  • 可扩展的安全框架
  • 可扩展的模板语言支持
  • 可扩展的JSON支持
  • 简单的基于Python的配置

框架原理分析

1. 原理

Pecan是一个路由对象分发的python web框架。本质上可以将url通过分割为每一部分,然后对每一部分查找对应处理该URL部分的处理类,处理后,继续交给后面部分的URL处理,直到所有URL部分都被处理后,调用最后分割的URL对应的处理函数处理。

2. 处理流程

代码位于Pecan的core.py中。

1. 当一个请求从wsgiserver转发过来

当一个请求从wsgiserver转发过来,首先处理的是Pecan中的__call__方法。主要调用了find_controller和invoke_controller方法。find_controller根据对象分发机制找到url的处理方法,如果没找到,则抛出异常,由后面的except代码块处理,找到了就调用invoke_controller执行该处理方法,将处理结果保存到state中。

    def __call__(self, environ, start_response):
        '''
        Implements the WSGI specification for Pecan applications, utilizing
        ``WebOb``.
        '''

        # create the request and response object
        req = self.request_cls(environ)
        resp = self.response_cls()
        state = RoutingState(req, resp, self)
        environ['pecan.locals'] = {
            'request': req,
            'response': resp
        }
        controller = None

        # track internal redirects
        internal_redirect = False

        # handle the request
        try:
            # add context and environment to the request
            req.context = environ.get('pecan.recursive.context', {})
            req.pecan = dict(content_type=None)

            controller, args, kwargs = self.find_controller(state)
            self.invoke_controller(controller, args, kwargs, state)
        except Exception as e:
            # if this is an HTTP Exception, set it as the response
            if isinstance(e, exc.HTTPException):
                # if the client asked for JSON, do our best to provide it
                accept_header = acceptparse.create_accept_header(
                    getattr(req.accept, 'header_value', '*/*') or '*/*')
                offers = accept_header.acceptable_offers(
                    ('text/plain', 'text/html', 'application/json'))
                best_match = offers[0][0] if offers else None
                state.response = e
                if best_match == 'application/json':
                    json_body = dumps({
                        'code': e.status_int,
                        'title': e.title,
                        'description': e.detail
                    })
                    if isinstance(json_body, six.text_type):
                        e.text = json_body
                    else:
                        e.body = json_body
                    state.response.content_type = best_match
                environ['pecan.original_exception'] = e

            # note if this is an internal redirect
            internal_redirect = isinstance(e, ForwardRequestException)

            # if this is not an internal redirect, run error hooks
            on_error_result = None
            if not internal_redirect:
                on_error_result = self.handle_hooks(
                    self.determine_hooks(state.controller),
                    'on_error',
                    state,
                    e
                )

            # if the on_error handler returned a Response, use it.
            if isinstance(on_error_result, WebObResponse):
                state.response = on_error_result
            else:
                if not isinstance(e, exc.HTTPException):
                    raise

            # if this is an HTTP 405, attempt to specify an Allow header
            if isinstance(e, exc.HTTPMethodNotAllowed) and controller:
                allowed_methods = _cfg(controller).get('allowed_methods', [])
                if allowed_methods:
                    state.response.allow = sorted(allowed_methods)
        finally:
            # if this is not an internal redirect, run "after" hooks
            if not internal_redirect:
                self.handle_hooks(
                    self.determine_hooks(state.controller),
                    'after',
                    state
                )

        self._handle_empty_response_body(state)

        # get the response
        return state.response(environ, start_response)
2. find_controller方法中主要调用了route方法
    def find_controller(self, state):
        '''
        The main request handler for Pecan applications.
        '''
        # get a sorted list of hooks, by priority (no controller hooks yet)
        req = state.request
        pecan_state = req.pecan

        # store the routing path for the current application to allow hooks to
        # modify it
        pecan_state['routing_path'] = path = req.path_info

        # handle "on_route" hooks
        self.handle_hooks(self.hooks, 'on_route', state)

        # lookup the controller, respecting content-type as requested
        # by the file extension on the URI
        pecan_state['extension'] = None

        # attempt to guess the content type based on the file extension
        if self.guess_content_type_from_ext \
                and not pecan_state['content_type'] \
                and '.' in path:
            _, extension = splitext(path.rstrip('/'))

            # preface with a letter to ensure compat for 2.5
            potential_type = guess_type('x' + extension)[0]

            if extension and potential_type is not None:
                path = ''.join(path.rsplit(extension, 1))
                pecan_state['extension'] = extension
                pecan_state['content_type'] = potential_type

        controller, remainder = self.route(req, self.root, path)
        cfg = _cfg(controller)

        if cfg.get('generic_handler'):
            raise exc.HTTPNotFound

        # handle generic controllers
        im_self = None
        if cfg.get('generic'):
            im_self = six.get_method_self(controller)
            handlers = cfg['generic_handlers']
            controller = handlers.get(req.method, handlers['DEFAULT'])
            handle_security(controller, im_self)
            cfg = _cfg(controller)

        # add the controller to the state so that hooks can use it
        state.controller = controller

        # if unsure ask the controller for the default content type
        content_types = cfg.get('content_types', {})
        if not pecan_state['content_type']:
            # attempt to find a best match based on accept headers (if they
            # exist)
            accept = getattr(req.accept, 'header_value', '*/*') or '*/*'
            if accept == '*/*' or (
                    accept.startswith('text/html,') and
                    list(content_types.keys()) in self.SIMPLEST_CONTENT_TYPES):
                pecan_state['content_type'] = cfg.get(
                    'content_type',
                    'text/html'
                )
            else:
                best_default = None
                accept_header = acceptparse.create_accept_header(accept)
                offers = accept_header.acceptable_offers(
                    list(content_types.keys())
                )
                if offers:
                    # If content type matches exactly use matched type
                    best_default = offers[0][0]
                else:
                    # If content type doesn't match exactly see if something
                    # matches when not using parameters
                    for k in content_types.keys():
                        if accept.startswith(k):
                            best_default = k
                            break

                if best_default is None:
                    msg = "Controller '%s' defined does not support " + \
                          "content_type '%s'. Supported type(s): %s"
                    logger.error(
                        msg % (
                            controller.__name__,
                            pecan_state['content_type'],
                            content_types.keys()
                        )
                    )
                    raise exc.HTTPNotAcceptable()

                pecan_state['content_type'] = best_default
        elif cfg.get('content_type') is not None and \
                pecan_state['content_type'] not in content_types:

            msg = "Controller '%s' defined does not support content_type " + \
                  "'%s'. Supported type(s): %s"
            logger.error(
                msg % (
                    controller.__name__,
                    pecan_state['content_type'],
                    content_types.keys()
                )
            )
            raise exc.HTTPNotFound

        # fetch any parameters
        if req.method == 'GET':
            params = req.GET
        elif req.content_type in ('application/json',
                                  'application/javascript'):
            try:
                if not isinstance(req.json, dict):
                    raise TypeError('%s is not a dict' % req.json)
                params = NestedMultiDict(req.GET, req.json)
            except (TypeError, ValueError):
                params = req.params
        else:
            params = req.params

        # fetch the arguments for the controller
        args, varargs, kwargs = self.get_args(
            state,
            params.mixed(),
            remainder,
            cfg['argspec'],
            im_self
        )
        state.arguments = Arguments(args, varargs, kwargs)

        # handle "before" hooks
        self.handle_hooks(self.determine_hooks(controller), 'before', state)

        return controller, args + varargs, kwargs

self.route(req, self.root, path)

  • req:WebOb的Request对象,存储了请求的信息
  • self.root:是第一个处理对象(config.py中定义的root对象)
  • path:路径信息,如:/v1/books
3. route方法中调用了lookup_controller方法对截取后的路径进行继续处理

lookup_controller针对每一个controller对象,在其中查找对应的处理方法,如果没找到,则会继续找_default,如果没定义_default,
则找_lookup,然后继续循环调用lookup_controller,直到找到对应的方法,或notfound_handlers 为空抛出异常。

    def route(self, req, node, path):
        '''
        Looks up a controller from a node based upon the specified path.

        :param node: The node, such as a root controller object.
        :param path: The path to look up on this node.
        '''
        path = path.split('/')[1:]
        try:
            node, remainder = lookup_controller(node, path, req)
            return node, remainder
        except NonCanonicalPath as e:
            if self.force_canonical and \
                    not _cfg(e.controller).get('accept_noncanonical', False):
                if req.method == 'POST':
                    raise RuntimeError(
                        "You have POSTed to a URL '%s' which "
                        "requires a slash. Most browsers will not maintain "
                        "POST data when redirected. Please update your code "
                        "to POST to '%s/' or set force_canonical to False" %
                        (req.pecan['routing_path'],
                            req.pecan['routing_path'])
                    )
                redirect(code=302, add_slash=True, request=req)
            return e.controller, e.remainder
4. lookup_controller方法中调用find_object方法
def lookup_controller(obj, remainder, request=None):
    '''
    Traverses the requested url path and returns the appropriate controller
    object, including default routes.

    Handles common errors gracefully.
    '''
    if request is None:
        warnings.warn(
            (
                "The function signature for %s.lookup_controller is changing "
                "in the next version of pecan.\nPlease update to: "
                "`lookup_controller(self, obj, remainder, request)`." % (
                    __name__,
                )
            ),
            DeprecationWarning
        )

    notfound_handlers = []
    while True:
        try:
            obj, remainder = find_object(obj, remainder, notfound_handlers,
                                         request)
            handle_security(obj)
            return obj, remainder
        except (exc.HTTPNotFound, exc.HTTPMethodNotAllowed,
                PecanNotFound) as e:
            if isinstance(e, PecanNotFound):
                e = exc.HTTPNotFound()
            while notfound_handlers:
                name, obj, remainder = notfound_handlers.pop()
                if name == '_default':
                    # Notfound handler is, in fact, a controller, so stop
                    #   traversal
                    return obj, remainder
                else:
                    # Notfound handler is an internal redirect, so continue
                    #   traversal
                    result = handle_lookup_traversal(obj, remainder)
                    if result:
                        # If no arguments are passed to the _lookup, yet the
                        # argspec requires at least one, raise a 404
                        if (
                            remainder == [''] and
                            len(obj._pecan['argspec'].args) > 1
                        ):
                            raise e
                        obj_, remainder_ = result
                        return lookup_controller(obj_, remainder_, request)
            else:
                raise e

obj, remainder = find_object(obj, remainder, notfound_handlers, request)具体查找:
obj:当前的controller对象
remainder:路由信息,如[‘v1’, ‘books’]
notfound_handlers:该controller中没找到时,存储_default或者_lookup
request:请求信息

5. find_object方法

find_object 首先会处理自定义的路由信息,然后存储_default和_lookup,最后处理默认路由。

def find_object(obj, remainder, notfound_handlers, request):
    '''
    'Walks' the url path in search of an action for which a controller is
    implemented and returns that controller object along with what's left
    of the remainder.
    '''
    prev_obj = None
    while True:
        if obj is None:
            raise PecanNotFound
        if iscontroller(obj):
            if getattr(obj, 'custom_route', None) is None:
                return obj, remainder

        _detect_custom_path_segments(obj)

        if remainder:
            custom_route = __custom_routes__.get((obj.__class__, remainder[0]))
            if custom_route:
                return getattr(obj, custom_route), remainder[1:]

        # are we traversing to another controller
        cross_boundary(prev_obj, obj)
        try:
            next_obj, rest = remainder[0], remainder[1:]
            if next_obj == '':
                index = getattr(obj, 'index', None)
                if iscontroller(index):
                    return index, rest
        except IndexError:
            # the URL has hit an index method without a trailing slash
            index = getattr(obj, 'index', None)
            if iscontroller(index):
                raise NonCanonicalPath(index, [])

        default = getattr(obj, '_default', None)
        if iscontroller(default):
            notfound_handlers.append(('_default', default, remainder))

        lookup = getattr(obj, '_lookup', None)
        if iscontroller(lookup):
            notfound_handlers.append(('_lookup', lookup, remainder))

        route = getattr(obj, '_route', None)
        if iscontroller(route):
            if len(getargspec(route).args) == 2:
                warnings.warn(
                    (
                        "The function signature for %s.%s._route is changing "
                        "in the next version of pecan.\nPlease update to: "
                        "`def _route(self, args, request)`." % (
                            obj.__class__.__module__,
                            obj.__class__.__name__
                        )
                    ),
                    DeprecationWarning
                )
                next_obj, next_remainder = route(remainder)
            else:
                next_obj, next_remainder = route(remainder, request)
            cross_boundary(route, next_obj)
            return next_obj, next_remainder

        if not remainder:
            raise PecanNotFound

        prev_remainder = remainder
        prev_obj = obj
        remainder = rest
        try:
            obj = getattr(obj, next_obj, None)
        except UnicodeEncodeError:
            obj = None

        # Last-ditch effort: if there's not a matching subcontroller, no
        # `_default`, no `_lookup`, and no `_route`, look to see if there's
        # an `index` that has a generic method defined for the current request
        # method.
        if not obj and not notfound_handlers and hasattr(prev_obj, 'index'):
            if request.method in _cfg(prev_obj.index).get('generic_handlers',
                                                          {}):
                return prev_obj.index, prev_remainder

routing.py中的lookup_controller 和 find_object是核心路由方式的实现,从代码中可以看出,最终找到处理方法的方式是根据路径(/v1/books)中每一个segment来查找对应的对象,然后根据当前对象再查找下一个对象,所以pecan的路由机制叫做对象分发

3. expose()装饰器

标识了这个被装饰的方法可以被路由找到。在routing.py中find_object方法会返回找到的subcontroller,它是有@expose装饰的一个方法

4. 小结

首先,Pecan库根据配置文件中配置的主入口Controller类获取到请求,然后对url分割,查找对应的Controller对象,经过处理后,根据default或者lookup方法,把除了当前路径外剩余的路径给当前Controller下一个处理的Controller对象处理,直到所有URL都被处理。

使用Pecan

1. 安装Pecan

创建虚拟环境并激活:

$ virtualenv pecan-env
$ cd pecan-env
$ source bin/activate

使用pip安装pecan:

$ pip install pecan

2.创建第一个Pecan应用

  1. 对于一个新的Pecan应用,Pecan框架包含一个基本的模板,这里创建一个名为test_project的Pecan项目
$ pecan create pecandemo
$ cd pecandemo

如果想要在“开发模式”下部署它,这样就可以“sys.path”就可以用了:

$ python setup.py develop

新的项目包含的文件如下:

$ ls

├── MANIFEST.in
├── config.py
├── public							# 静态文件,包括CSS 、JS、images
│   ├── css
│   │   └── style.css
│   └── images
├── setup.cfg
├── setup.py
└── test_project					# 基于MVC模型生成的结构
    ├── __init__.py
    ├── app.py						# 决定应用是如何创造的,必须包含set_app()并返回WSGI应用程序,一般不变
    ├── controllers					# 控制层实现
    │   ├── __init__.py
    │   └── root.py
    ├── model						# 模型实现
    │   └── __init__.py				# 在这里可以加入与database交互,定义表和ORM等
    ├── templates					# 模板实现
    │   ├── error.html
    │   ├── index.html
    │   └── layout.html
    └── tests						# 单元测试
        ├── __init__.py
        ├── config.py
        ├── test_functional.py
        └── test_units.py

可能由于不同版本文件有所不同,但是基本结构不变。
2. 文件描述:

  • public:存放所有静态文件,例如CSS、Javascript和images。

Pecan应用程序结构通常遵循MVC模式。项目下的目录包含模型、控制器和模板。

  • test_project/controllers:控制器文件的容器目录
  • test_project/templates:模板文件
  • test_project/model:控制器文件的容器目录
  • test_project/tests:测试脚本目录

pecandemo/app.py文件控制如何创建Pecan应用程序。此文件必须包含返回WSGI应用程序对象的setup_app()函数。通常不需要修改。

为了避免不必要的依赖关系并尽可能保持灵活性,Pecan不强制任何数据库或ORM(Object-Relational Mapper)。如果您的项目将与数据库交互,则可以将代码添加到model/_init_.py以从配置文件加载数据库绑定,并定义表和ORM定义。

  1. 运行Pecan应用
    运行Pecan应用程序需要配置文件config.py. 此文件包括运行服务器的主机和端口、控制器和模板在磁盘上的存储位置,以及包含任何静态文件的目录的名称。
    运行Pecan的命令如下:
$ pecan serve config.py
Starting server in PID 000.
serving on 0.0.0.0:8080, view at http://127.0.0.1:8080

3. Python-Based配置

Pecan的的配置文件是一个纯Python语言脚本,默认的config.py的文件内容如下:

# Server Specific Configurations
server = {
    'port': '8080',
    'host': '0.0.0.0'
}

# Pecan Application Configurations
app = {
    'root': 'pecandemo.controllers.root.RootController',   # RootController所在的路径
    'modules': ['pecandemo'],							   # 其中包含的python包,是app.py所在的包,即setup_app方法所在的包
    'static_root': '%(confdir)s/public',
    'template_path': '%(confdir)s/pecandemo/templates',
    'debug': True,										   # 是否开启调式
    'errors': {
        404: '/error/404',
        '__force_dict__': True
    }
}

logging = {
    'root': {'level': 'INFO', 'handlers': ['console']},
    'loggers': {
        'pecandemo': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False},
        'pecan': {'level': 'DEBUG', 'handlers': ['console'], 'propagate': False},
        'py.warnings': {'handlers': ['console']},
        '__force_dict__': True
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'color'
        }
    },
    'formatters': {
        'simple': {
            'format': ('%(asctime)s %(levelname)-5.5s [%(name)s]'
                       '[%(threadName)s] %(message)s')
        },
        'color': {
            '()': 'pecan.log.ColorFormatter',
            'format': ('%(asctime)s [%(padded_color_levelname)s] [%(name)s]'
                       '[%(threadName)s] %(message)s'),
        '__force_dict__': True
        }
    }
}

# Custom Configurations must be in Python dictionary format::
#
# foo = {'bar':'baz'}
#
# All configurations are accessible at::
# pecan.conf

4. Pecan的根控制器

Root Controller是Pecan应用的单一入口,可以认为它类似于应用程序的根URL路径。在上面的例子中为http://localhost:8080/
该文件的主要内容示例如下:

from pecan import expose
from webob.exc import status_map

class RootController(object):

    @expose(generic=True, template='index.html')
    def index(self):
        return dict()

    @index.when(method='POST')
    def index_post(self, q):
        redirect('https://pecan.readthedocs.io/en/latest/search.html?q=%s' % q)

    @expose('error.html')
    def error(self, status):
        try:
            status = int(status)
        except ValueError:
            status = 0
        message = getattr(status_map.get(status), 'explanation', '')
        return dict(status=status, message=message)

如果需要,可以指定其他类和方法,但现在,让我们逐个解释示例中的每一项:

@expose(generic=True, template='index.html')
def index(self):
    return dict()

这里的index方法名是不能改的,routing的时候会查找有没有这个方法,而且必须使用装饰器 @pecan.expose()
注意:有些时候会把@pecan.expose**()**写成@pecan.expose,没有带括号。一旦运行起来,界面上就报:“A server error occurred. Please contact the administrator.”,控制台会打出“AttributeError: ‘instancemethod’ object has no attribute ‘_pecan’”。这就是没用到装饰器,一些属性没有加上。装饰器使用时带不带括号是有区别的。

5. 对象分发式路由

示例如下:

class v1Controller(rest.RestController):
    @pecan.expose()
    def get(self):
        return 'This is v1Controller GET.'


class RootController(rest.RestController):
    
    v1 = v1Controller()

在RootController类中,生成了一个v1Controller对象,这个对象就是用来做分发式路由的,因此可以使用如下命令去调用它:

curl -X GET http://127.0.0.1:8080/v1

使用WSME来规范API的响应值

Pecan可以比较好的处理HTTP请求中的参数以及控制HTTP返回值。那么为什么我们还需要WSME呢?因为Pecan在做下面这个事情的时候比较麻烦:请求参数和响应内容的类型检查(英文简称就是typing)。当然,做是可以做的,不过你需要自己访问pecan.request和pecan.response,然后检查指定的值的类型。WSME就是为解决这个问题而生的,而且适用场景就是RESTful API。

1. WSME简介

WSME的全称是Web Service Made Easy,是专门用于实现REST服务的typing库,让你不需要直接操作请求和响应,而且刚好和Pecan结合得非常好,所以OpenStack的很多项目都使用了Pecan + WSME的组合来实现API。

WSME的理念是:在大部分情况下,Web服务的输入和输出对数据类型的要求都是严格的。所以它就专门解决了这个事情,然后把其他事情都交给其他框架去实现。因此,一般WSME都是和其他框架配合使用的,支持Pecan、Flask等。

WSME的文档地址是http://wsme.readthedocs.org/en/latest/index.html。

2. WSME的使用

用了WSME后的好处是什么呢?WSME会自动帮你检查HTTP请求和响应中的数据是否符合预先设定好的要求
WSME的主要方式是通过装饰器来控制controller方法的输入和输出
WSME中主要使用两个控制器:

  • @signature: 这个装饰器用来描述一个函数的输入和输出。
  • @wsexpose: 这个装饰器包含@signature的功能,同时会把函数的路由信息暴露给Web框架,效果就像Pecan的expose装饰器。

这里我们结合Pecan来讲解WSME的使用。先来看一个原始类型的例子:

from wsmeext.pecan import wsexpose
 
class RootController(rest.RestController):
    _custom_actions = {
        'test': ['GET'],
    }
 
    @wsexpose(int, int)
    def test(self, number):
        return number

如果不提供参数,访问会失败:

$ curl http://localhost:8080/test
{"debuginfo": null, "faultcode": "Client", "faultstring": "Missing argument: \"number\""}% 

如果提供的参数不是整型,访问也会失败:

$ curl http://localhost:8080/test\?number\=a
{"debuginfo": null, "faultcode": "Client", "faultstring": "Invalid input for field/attribute number. Value: 'a'. unable to convert to int"}% 

上面这些错误信息都是由WSME框架直接返回的,还没有执行到你写的方法。
如果请求正确,那么会是这样的:

$ curl -v http://localhost:8080/test\?number\=1
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /test?number=1 HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:8080
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Wed, 16 Sep 2015 15:06:35 GMT
< Server: WSGIServer/0.1 Python/2.7.9
< Content-Length: 1
< Content-Type: application/json; charset=UTF-8
<
* Closing connection 0
1% 

请注意返回的content-type,这里返回JSON是因为我们使用的wsexpose设置的返回类型是XML和JSON,并且JSON是默认值。上面这个例子就是WSME最简单的应用了。

那么现在有下面这些问题需要思考一下:

  • 如果想用POST的方式来传递参数,要怎么做呢?提示:要阅读WSME中@signature装饰器的文档。
  • 如果我希望使用/test/1这种方式来传递参数要怎么做呢?提示:要阅读Pecan文档中关于路由的部分。
  • WSME中支持对哪些类型的检查呢?WSME支持整型、浮点型、字符串、布尔型、日期时间等,甚至还支持用户自定义类型。提示:要阅读WSME文档中关于类型的部分。
  • WSME支持数组类型么?支持。

参考资料

  1. https://blog.youkuaiyun.com/qq527631128/article/details/90245555
  2. https://blog.youkuaiyun.com/meisanggou/article/details/88559446
  3. https://blog.youkuaiyun.com/warrior_0319/article/details/86611883
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值