Python从入门到进阶(二十)——Web开发基础知识

一 Python Web基础

Web应用的本质:
1. 浏览器发送一个HTTP请求
2. 服务器收到请求,生成一个HTML文档
3. 服务器把HTML文档作为HTTP响应的Body发送给浏览器
4. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示
所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。除此外Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。

如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。

1. WSGI

Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口,自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。

以前,如何选择合适的Web应用程序框架成为困扰Python初学者的一个问题,这是因为,一般而言,Web应用框架的选择将限制可用的Web服务器的选择,反之亦然。那时的Python应用程序通常是为CGI,FastCGI,mod_python中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计的。
WSGI(有时发音作’wiz-gee’)是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。WSGI没有官方的实现, 因为WSGI更像一个协议。只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行, 反之亦然。WSGI就是Python的CGI包装,相对于Fastcgi是PHP的CGI包装

1.1 概述

WSGI区分为两个部分
1. 为"服务器""网关".它用于接收,整理客户端发送的请求.
2. 为"应用程序""应用框架".处理服务器程序传递过来的请求.

在这里插入图片描述
如上图,Web服务器即第一部分,接收、整理客户端发送的请求;Web框架即为第二部分,即所谓的Web应用程序。开发Web应用程序的时候,通常会把常用的功能封装起来,成为各种框架,比如Flask,Django,Tornado(使用某框架进行web开发,相当于开发服务端的应用程序,处理后台逻辑)。但是,服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时,我们就需要一个标准,让服务器程序和应用程序都支持这个标准,那么,二者就能很好的配合了,这个标准就是 WSGI。

在处理一个WSGI请求时,服务器会为应用程序提供环境信息及一个回调函数(Callback Function).当应用程序完成处理请求后,透过前述的回调函数,将结果回传给服务器.
所谓的WSGI中间件同时实现了API的两方,因此可以在WSGI服务器和WSGI应用之间起调解作用.从Web服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器."中间件"组件可以执行以下功能:
1.重写环境变量后,根据目标URL,将请求消息路由到不同的应用对象.
2.允许在一个进程中同时运行多个应用程序或应用框架.
3.负载均衡和远程处理,通过在网络上转发请求和响应消息.
4.进行内容后处理,例如应用XSLT样式表.

1.2 实现原理

WSGI将Web组件分为三类
  • web服务器
  • web中间件
  • web应用程序
wsgi基本处理模式为:
WSGI Server-> WSGI Middleware ->WSGI Application
  1. WSGI Server/gateway
    wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端.以Python自带的wsgiref为例,wsgiref是按照wsgi规范实现的一个简单wsgi server.它的代码不复杂.
    在这里插入图片描述
    1. 服务器创建Socket,监听端口,等待客户端连接.
    2. 当有请求来时,服务器解析客户端信息放到环境变量environ中.并调用绑定的handler来处理请求.
    3. handler解析这个http请求,将请求信息例如method,path等放到environ中.
    4. wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中.
    5. wsgi handler调用注册的wsgi app,并将environ和回调函数传给wsgi app
    6. wsgi app将response header/status/body 回传给wsgi handler
    7. 最终handler还是通过socket将response信息塞回给客户端.
  2. WSGI Application
    wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。此外这个wsgi app会return 一个iterator对象 ,这个iterator就是response body。这么空讲感觉很虚,对着下面这个简单的例子看就明白很多了
  3. WSGI MiddleWare
    有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL,不同的URL需要交由不同的函数处理,这个功能叫做URL Routing,这个功能就可以放在二者中间实现,这个中间层就是middleware.middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器.这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传递给服务器程序.
无论是服务器程序,middleware还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象为不同层,就是为了控制复杂度,使得每一次都不复杂,各司其职.

1.3 测试WSGI服务器

原理说的太多未免过于抽象.现在使用Python内置的纯Python代码编写的wsgiref服务器来体验一把WSGI服务器是如何工作的.

  • 编写hello.py 作为一个Web应用程序
class hello(object):
    """description of class"""
    def application(environ,start_response):
        start_response('200 OK',[('Content-Type','text/html')])
        return [b'<h1>Hello,World!</h1>']
  • 编写server.py作为一个WSGI服务器
from wsgiref.simple_server import make_server
# 导入编写的application函数
from hello import hello
class webkfserver(object):
    # 创建一个服务器,IP地址为空,端口是8000,传入函数application
    httpd=make_server('',8000,hello.application)
    print('Serving HTTP on port 8000...')
    # 开始监听HTTP请求:
    httpd.serve_forever()
    pass
  • 启动WSGI服务器
python server.py
  • 使用客户端访问
    打开浏览器,输入http://localhost:8000/,在浏览器正常显示"Hello,World!"
    在这里插入图片描述
    代码简析
上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
  • environ:一个包含所有HTTP请求信息的dict对象
  • start_response:一个发送HTTP响应的函数
    而在application()函数中又调用了start_response函数
该函数发送了http响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。、
start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示.

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。然后,函数的返回值b’

Hello, web!

'将作为HTTP响应的Body发送给浏览器。
有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

需要注意的是,application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是我们仅将内置的wsgiref服务器用于测试,使我们编写的Web应用程序立马跑起来。

1.4 实现WSGI服务器

为了了解wsgi的工作原理,我们可以参照wsgiref源码,使用Python简单实现一个WSGI服务器

import socket
import StringIO
import sys

class WSGIServer(object):

    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    request_queue_size = 1

    def __init__(self, server_address):
        # 创建socket,利用socket获取客户端的请求
        self.listen_socket = listen_socket = socket.socket(self.address_family, self.socket_type)
        # 设置socket的工作模式
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 绑定socket地址
        listen_socket.bind(server_address)
        # socket active, 监听文件描述符
        listen_socket.listen(self.request_queue_size)

        # 获得serve的host name和port
        host, port = self.listen_socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

        self.headers_set = []

    def set_app(self, application):
        self.application = application 

    #启动WSGI server服务,不停的监听并获取socket数据。
    def serve_forever(self):
        listen_socket = self.listen_socket
        while True:
            self.client_connection, client_address = listen_socket.accept() #接受客户端请求
            #处理请求
            self.handle_one_request()

    def handle_one_request(self):
        self.request_data = request_data = self.client_connection.recv(1024)
        self.parse_request(request_data)

        # Construct environment dictionary using request data
        env = self.get_environ()

        #给flask\tornado传递两个参数,environ,start_response
        result = self.application(env, self.start_response)
        self.finish_response(result)

    #处理socket的http协议
    def parse_request(self, data):
        format_data = data.splitlines()
        if len(format_data):
            request_line = data.splitlines()[0]
            request_line = request_line.rstrip('\r\n')
            (self.request_method, self.path, self.request_version) = request_line.split() ## ['GET', '/', 'HTTP/1.1']

    # 获取environ数据并设置当前server的工作模式
    def get_environ(self):
        env = {}
        env['wsgi.version']      = (1, 0)
        env['wsgi.url_scheme']   = 'http'
        env['wsgi.input']        = StringIO.StringIO(self.request_data)
        env['wsgi.errors']       = sys.stderr
        env['wsgi.multithread']  = False
        env['wsgi.multiprocess'] = False
        env['wsgi.run_once']     = False
        # Required CGI variables
        env['REQUEST_METHOD']    = self.request_method    # GET
        env['PATH_INFO']         = self.path              # /hello
        env['SERVER_NAME']       = self.server_name       # localhost
        env['SERVER_PORT']       = str(self.server_port)  # 8888
        return env

    def start_response(self, status, response_headers, exc_info=None):
        server_headers = [('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'), ('Server', 'WSGIServer 0.2')]
        self.headers_set = [status, response_headers + server_headers]

    #把application返回给WSGI的数据返回给客户端。
    def finish_response(self, result):
        try:
            status, response_headers = self.headers_set
            response = 'HTTP/1.1 {status}\r\n'.format(status=status)
            for header in response_headers:
                response += '{0}: {1}\r\n'.format(*header)
            response += '\r\n'
            for data in result:
                response += data
            self.client_connection.sendall(response)
            print(''.join(['> {line}\n'.format(line=line) for line in response.splitlines()]))
        finally:
            self.client_connection.close()

SERVER_ADDRESS = (HOST, PORT) = '', 8888

def make_server(server_address, application):
    server = WSGIServer(server_address)
    server.set_app(application)
    return server


if __name__ == '__main__':
    if len(sys.argv) < 2:
        sys.exit('Provide a WSGI application object as module:callable')
    app_path = sys.argv[1]
    module, application = app_path.split(':') # 第一个参数是文件名,第二个参数时长文件内app的命名
    module = __import__(module)
    application = getattr(module, application) # getattr(object, name[, default]) -> value
    httpd = make_server(SERVER_ADDRESS, application)
    print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
    httpd.serve_forever()


1.5 生产环境中的Web服务器

每个web框架都不是专注于实现服务器方面的,因此,在生产环境部署的时候,使用的服务器也不会简单的使用web框架自带的服务器,那么用于生产环境的服务器有哪些呢?

Gunicorn

Gunicorn(从Ruby下面的Unicorn得到的启发)应运而生:依赖Nginx的代理行为,同Nginx进行功能上的分离。由于不需要直接处理用户来的请求(都被Nginx先处理),Gunicorn不需要完成相关的功能,其内部逻辑非常简单:接受从Nginx来的动态请求,处理完之后返回给Nginx,由后者返回给用户。

由于功能定位很明确,Gunicorn得以用于纯Python开发,大大缩短了开发时间的同时,性能上也不会很掉链子.同时,他也可以配合Nginx的代理之外的别的Proxy模块工作,其配置也相应比较简单.
uWSGI

使用C语言开发,和底层接触的更好,配置也比较方便,目前和gunicorn两个算是部署时的唯二之选。由于其可扩展的架构,它能够被无限制的扩展用来支持更多的平台和语言。目前,可以使用C,C++和Objective-C来编写插件
uWSGI 既不使用wsgi协议也不用FastCGI协议,而是自创了一个uwsgi的协议,uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,它与WSGI相比是两样东西。据说该协议大约是fcgi协议的10倍那么快

主要特点如下:

  • 超快的性能
  • 低内存占用(实测为apache2的mod_wsgi的一半左右)
  • 多app管理
  • 详尽的日志功能(可以用来分析app性能和瓶颈)
  • 高度可定制(内存大小限制,服务一定次数后重启等)
  • uWSGI 服务器自己实现了基于uwsgi协议的server部分,因此我们只需要在uwsgi的配置文件中指定application的地址,uWSGI 就能直接和应用框架中的WSGI application通信

bjoern

是一个用C语言编写的,快速超轻量级的Python WSGI服务器
它是最快速的,最小的并且是最轻量级的WSGI服务器,有以下特性:
  • 1000 行的C代码
  • 占用内存 600KB
  • 单线程没有其他协同程序
  • 可以绑定到TCP主机:端口地址和Unix套接字
  • 支持HTTP1.0/1.1,包含支持HTTP1.1的分块响应

如果单纯追求性能,那uWSGI会更好一点,而Gunicorn则会更易安装和结合gevent。在阻塞响应较多的情况下,Gunicorn的gevent模式无疑性能会更加强大。功能实现方面,uWSGI会更多一些,配置也会更加复杂一些。

2. Web应用开发

常见的Python Web应用框架
  • Django:全能型Web框架
  • flask:一个使用Python编写的轻量级Web框架
  • web.py:一个小巧的Web框架
  • Bottle:和Flask类似的Web框架
  • Tormado:FaceBook的开源异步Web框架

2.1 服务器架构
在这里插入图片描述
2.1.1 Nginx

Nginx(发音同engine x)是一个异步框架的 Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存。该软件由 Igor Sysoev 创建,并于2004年首次公开发布。同名公司成立于2011年,以提供支持。

Nginx是一款免费的开源软件,根据类BSD许可证的条款发布。一大部分Web服务器使用Nginx,通常作为负载均衡器。

Nginx是一款面向性能设计的HTTP服务器,相较于Apache、lighttpd具有占有内存少,稳定性高等优势。与旧版本(<=2.2)的Apache不同,Nginx不采用每客户机一线程的设计模型,而是充分使用异步逻辑从而削减了上下文调度开销,所以并发服务能力更强。整体采用模块化设计,有丰富的模块库和第三方模块库,配置灵活。 在Linux操作系统下,Nginx使用epoll事件模型,得益于此,Nginx在Linux操作系统下效率相当高。同时Nginx在OpenBSD或FreeBSD操作系统上采用类似于epoll的高效事件模型kqueue。

Nginx在官方测试的结果中,能够支持五万个并行连接,而在实际的运作中,可以支持二万至四万个并行连接
反向代理
在这里插入图片描述

正向代理是指浏览器主动请求代理服务器,代理服务器转发请求到对应的目标服务器。而反向代理则部署在Web服务器上,代理所有外部网络对内部网络的访问。浏览器访问服务器,必须经过这个代理,是被动的。正向代理的主动方是客户端,反向代理的主动方是Web服务器

在Python的Web开发中,较为成熟稳定的服务器架构一般是Nginx + uWSGI + Django。而实际上Nginx服务器并不是必须的,直接使用uWSGI + Djang完全是可以的,但这样一来,直接将uWSGI服务器暴露给了浏览器客户端,由此会导致诸多隐患。
Nginx的优势
1. 安全问题.客户端对Web服务器的访问需要先经过反向代理服务器,Nginx则只需开放某个接口,uWSGI本身是内网接口,这样可以防止外部程序对Web服务器的直接攻击
2. 负载均衡.反向代理服务器可以根据web服务器的负载情况,动态地把HTTP请求交给不同的Web服务器来处理,前提是要有多个Web服务器.
3. 提升Web服务器的IO性能.一个HTTP请求的数据.从客户端传输给服务器,是需要时间的,例如N秒,如果直接传给Web服务器,Web服务器就需要让一个进程阻塞N秒,来接收IO,这样会降低Web服务器的性能,如果使用Nginx作为反向代理服务器,先让反向代理服务器接收完整个HTTP请求,再把请求发给Web服务器,就能提升Web服务器的性能.将静态资源发送(js,css,图片等),动态请求转发以及结果的回复交给Nginx处理,就不需要经过Web服务器.

附录

支持WSG的Web应用框架有很多
  • BlueBream
  • bobo
  • Bottle
  • CherryPy
  • Django
  • Flask
  • Google App Engine’s webapp2
  • Gunicorn
  • prestans
  • Pylons
  • Pyramid
  • restlite
  • Tornado
  • Trac
  • TurboGears
  • Uliweb
  • web.py
  • web2py
  • weblayer
  • Werkzeug

3 路由器

# 路由器
'''
下面是一个 HTTP 状态代码的定义:

2xx:成功:

200 正常,请求已完成。
201 正常,紧接POST命令。
202 正常,已接受用于处理,但处理尚未完成。
203 正常,部分信息—返回的信息只是一部分。
204 正常,无响应—已接收请求,但不存在要回送的信息。
3xx:重定向:

301 已移动,请求的数据具有新的位置且更改是永久的。
302 已找到,请求的数据临时具有不同 URI。
303 请参阅其它,可在另一 URI 下找到对请求的响应,且应使用 GET 方法检索此响应。
304 未修改,未按预期修改文档。
305 使用代理,必须通过位置字段中提供的代理来访问请求的资源。
306 未使用,不再使用,保留此代码以便将来使用。
4xx:客户机中出现的错误:

400 错误请求,请求中有语法问题,或不能满足请求。
401 未授权,未授权客户机访问数据。
402 需要付款,表示计费系统已有效。
403 禁止,即使有授权也不需要访问。
404 找不到,服务器找不到给定的资源;文档不存在。
407 代理认证请求,客户机首先必须使用代理认证自身。
415 介质类型不受支持,服务器拒绝服务请求,因为不支持请求实体的格式。
5xx:服务器中出现的错误:

500 内部错误,因为意外情况,服务器不能完成请求。
501 未执行,服务器不支持请求的工具。
502 错误网关,服务器接收到来自上游服务器的无效响应。
503 无法获得服务,由于临时过载或维护,服务器无法处理请求。
编写一个路由服务,支持注入路由配置,正确处理请求参数
'''
# Ztcc.py
from enum import Enum

class ErrorCode(Enum):
    SUCCESS=0
    FAILED=1
    NOT_FOUND=2
    ALREADY_EXIST=3
    INVALID_PARAMETERS=4

    @staticmethod
    def internal_ret_2_http(ret):
        ret['err']=ret['err'].name.lower()

class Store:
    def __init__(self,config) -> None:
        self.config=config
        self.records={}

    def __where(self,key,condition):
        if condition is None:
            return True
        return self.records.get(key)==condition

    def create(self,key,value):
        if self.records.get(key) is None:
            self.records[key]=value
            return {'err':ErrorCode.SUCCESS}
        else:
            return {'err':ErrorCode.ALREADY_EXIST}

    def update(self,key,value,condition=None):
        if self.__where(key,condition):
            self.records[key]=value
            return {'err':ErrorCode.SUCCESS}
        else:
            return {'err':ErrorCode.NOT_FOUND}

    def remove(self,key,condition=None):
        if self.__where(key,condition):
            del self.records[key]
            return {'err':ErrorCode.SUCCESS}
        else:
            return {'err':ErrorCode.NOT_FOUND}

    def count(self):
        return {'err':ErrorCode.SUCCESS,'result':len(self.records.keys())}

    def fetch(self,key,condition=None):
        if self.__where(key,condition):
            result=self.records.get[key]
            if result is None:
                return {'err':ErrorCode.NOT_FOUND}
            else:
                return {'err':ErrorCode.SUCCESS,'result':{result}}
        else:
            return {'err':ErrorCode.NOT_FOUND}
            
# Lyq.py
import json
import logging
import traceback
from Ztcc import ErrorCode
logger=logging.getLogger(__name__)
class Router(object):
    def __init__(self,routes) -> None:
        self.routes=routes
    
    def dispatch(self,http_request_str):
        # 实现路由逻辑,返回符合语义的HTTP状态码
        req_path=None
        req=None
        try:
            http_request=json.loads(http_request_str)
            req_path=http_request.get('path')
            req=http_request.get('data')
        except Exception as e:
            logger.error(f'parse data exception:{str(e)}')
            return {'err':ErrorCode.INVALID_PARAMETERS},400

        if req_path is None or self.routes.get(req_path) is None:
            return {'err':ErrorCode.NOT_FOUND},404

        try:
            handler=self.routes.get(req_path)
            return handler(req),200
        except Exception as e:
            logger.error(f"route to '{req_path}' exception:{str(e)}")
            logger.error(traceback.format_exc())
            return {'err':ErrorCode.FAILED},500
        


if __name__=='__main__':
    # 注册路由
    router=Router({
        '/':lambda req:print(f'goto home:{str(req)}')
    })

    # 分发路由
    router.dispatch(json.dumps({
        'path':'/',
        'data':["Hello","World!"]
    }))

4 参数校验

使用 jsonschema 校验参数,key:字符串类型, value:数字, condition: 数字

from Ztcc import ErrorCode
import json
import logging
import traceback
from jsonschema import validate
logger=logging.getLogger(__name__)
class KeyValueValidator(object):
    """使用 jsonschema 校验参数,
    key:字符串类型, value:数字, condition: 数字"""
    def __init__(self) -> None:
        pass

    def validatetest(self,req,required):
        '''使用jsonschema校验参数
        req:请求参数
        required:必须要有的字段'''
        schema={
            "type":"object",
            "properites":
            {
                "key":{"type":"string"},
                "value":{"type":"number"},
                "condition":{"type":"number"},
            },
            "required":required
        }
        
        try:
            validate(instance=req,schema=schema)
            return {
                'err':ErrorCode.SUCCESS
                }
        except Exception as e:
            logger.error(f"validate exception:{str(e)}")
            logger.error(traceback.format_exc())
            return {
                'err':ErrorCode.INVALID_PARAMETERS
                }
        pass


if __name__=='__main__':
    v= KeyValueValidator()
    ret= v.validatetest({'key':'test','value':100},['key','value'])
    assert ret['err']==ErrorCode.SUCCESS

    ret=v.validatetest({'key':'test'},['key','value'])
    assert ret['err']==ErrorCode.INVALID_PARAMETERS

5 Python Web 服务模拟器

综合使用ErrorCode、Router两个类,模拟一个 Web 服务,支持:
创建资源
删除资源
统计资源个数
那么,先创建一个资源,接着删除同一个资源,最后统计资源个数,总数应该为0


from Ztcc import ErrorCode,Store
from Lyq import Router

import json
from Kvr import KeyValueValidator
import logging
import traceback
logger=logging.getLogger(__name__)
class Pwebapp(object):
    def __init__(self) -> None:
        self.store=Store({})
        self.validator= KeyValueValidator()

        # 创建一个路由器,支持3个目标API
        self.router=Router({
            '/':lambda req:self.__home(req),
            '/kv/create':lambda req:self.__create(req),
            '/kv/remove':lambda req:self.__remove(req),
            '/kv/count':lambda req:self.__count(req)
            })

    def post(self,path,data):
        '''HTTP POST方法模拟实现
        path:请求路径
        data:请求数据,使用JSON模拟'''
        http_request={
            'path':path,
            'data':data
            }
        ret,status_code=self.router.dispatch(json.dumps(http_request))
        ErrorCode.internal_ret_2_http(ret)
        resp=""
        try:
            resp=json.dumps(ret)
        except Exception as e:
            logger.error("parse resp exception:%s",str(e))
            logger.error(traceback.format_exc())
        return resp,status_code
    
    def __home(self,req):
        # 首页
        return {'err':ErrorCode.SUCCESS,'result':'Welcome!'}

    def __create(self,req):
        # 创建资源
        ret=self.validator.validatetest(req,['key','value'])
        if ret['err']!=ErrorCode.SUCCESS:
            return ret

        return self.store.create(req['key'],req['value'])

    def __remove(self,req):
        # 移除资源
        ret=self.validator.validatetest(req,['key','condition'])
        if ret['err']!=ErrorCode.SUCCESS:
            return ret

        return self.store.remove(req['key'],req['condition'])

    def __count(self,req):
        # 统计资源个数
        return self.store.count()


if __name__=='__main__':
    app= Pwebapp()
    resp,status=app.post('/kv/create',{
        'key':'test',
        'value':1000
        })
    
    resp,status=app.post('/kv/remove',{
        'key':'test',
        'condition':1000,
        })

    resp,status=app.post('/kv/count',{})
    assert status==200
    assert json.loads(resp)['err']=='success'
    assert json.loads(resp)['result']==0
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值