pythonweb项目实例-Python Web项目实战Day05 - 编写Web框架

在正式开始 Web 开发前,我们需要编写一个 Web 框架。

aiohttp已经是一个 Web 框架了,为什么我们还需要自己封装一个?

原因是从使用者的角度来说,aiohttp相对比较底层,编写一个 URL 的处理函数需要这么几步:

第一步,编写一个用 @asyncio.coroutine装饰的函数:

@asyncio.coroutine

def handle_url_xxx(request):

pass

第二步,传入的参数需要自己从 request中获取:

url_param = request.match_info["key"]

query_params = parse_qs(request.query_string)

最后,需要自己构造 Response对象:

text = render("template", data)

return web.Response(text.encode("utf-8"))

这些重复的工作可以由框架完成。

例如,处理带参数的 URL/blog/{id}可以这么写:

@get("/blog/{id}")

def get_blog(id):

pass

处理 query_string参数可以通过关键字参数 **kw或者 命名关键字参数 接收:

@get("/api/comments")

def api_comments(*, page="1"):

pass

对于函数的返回值,不一定是 web.Response对象,可以是 str、bytes或 dict。

如果希望渲染模板,我们可以这么返回一个 dict:

return {

"__template__": "index.html",

"data": "..."

}

因此,Web 框架的设计是完全从使用者出发,目的是让使用者编写尽可能少的代码。

编写简单的函数而非引入 request和 web.Response还有一个额外的好处,就是可以单独测试,否则,需要模拟一个 request才能测试。

@get和@post

要把一个函数映射为一个 URL 处理函数,我们先定义 @get():

def get(path):

"""

Define decorator @get("/path")

"""

def decorator(func):

@functools.wraps(func)

def wrapper(*args, **kw):

return func(*args, **kw)

wrapper.__method__ = "GET"

wrapper.__route__ = path

return wrapper

return decorator

这样,一个函数通过 @get()的装饰就附带了 URL 信息。

@post与 @get定义类似。

定义RequestHandler

URL 处理函数不一定是一个 coroutine,因此我们用 RequestHandler()来封装一个 URL 处理函数。

RequestHandler是一个类,由于定义了 __call__()方法,因此可以将其实例视为函数。

RequestHandler目的就是从 URL 函数中分析其需要接收的参数,从 request中获取必要的参数,调用 URL 函数,然后把结果转换为web.Response对象,这样,就完全符合 aiohttp框架的要求:

class RequestHandler(object):

def __init__(self, app, fn):

self._app = app

self._func = fn

...

@asyncio.coroutine

def __call__(self, request):

kw = ... 获取参数

r = yield from self._func(**kw)

return r

再编写一个 add_route函数,用来注册一个 URL 处理函数:

def add_route(app, fn):

method = getattr(fn, "__method__", None)

path = getattr(fn, "__route__", None)

if path is None or method is None:

raise ValueError("@get or @post not defined in %s." % str(fn))

if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):

fn = asyncio.coroutine(fn)

logging.info("add route %s %s => %s(%s)" % (method, path, fn.__name__, ", ".join(inspect.signature(fn).parameters.keys())))

app.router.add_route(method, path, RequestHandler(app, fn))

最后一步,把很多次 add_route()注册的调用:

add_route(app, handles.index)

add_route(app, handles.blog)

add_route(app, handles.create_comment)

...

变成自动扫描:

# 自动把handler模块的所有符合条件的函数注册了:

add_routes(app, "handlers")

add_routes()定义如下:

def add_routes(app, module_name):

n = module_name.rfind(".")

if n == (-1):

mod = __import__(module_name, globals(), locals())

else:

name = module_name[n+1:]

mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)

for attr in dir(mod):

if attr.startswith("_"):

continue

fn = getattr(mod, attr)

if callable(fn):

method = getattr(fn, "__method__", None)

path = getattr(fn, "__route__", None)

if method and path:

add_route(app, fn)

最后,在 app.py中加入 middleware、jinja2模板和自注册的支持:

app = web.Application(loop=loop, middlewares=[

logger_factory, response_factory

])

init_jinja2(app, filters=dict(datetime=datetime_filter))

add_routes(app, "handlers")

add_static(app)

middleware

middleware是一种拦截器,一个 URL 在被某个函数处理前,可以经过一系列的 middleware的处理。

一个 middleware可以改变 URL 的输入、输出,甚至可以决定不继续处理而直接返回。middleware 的用处就在于把通用的功能从每个 URL 处理函数中拿出来,集中放到一个地方。例如,一个记录 URL 日志的 logger可以简单定义如下:

@asyncio.coroutine

def logger_factory(app, handler):

@asyncio.coroutine

def logger(request):

# 记录日志:

logging.info("Request: %s %s" % (request.method, request.path))

# 继续处理请求:

return (yield from handler(request))

return logger

而 response这个 middleware把返回值转换为 web.Response对象再返回,以保证满足 aiohttp的要求:

@asyncio.coroutine

def response_factory(app, handler):

@asyncio.coroutine

def response(request):

# 结果:

r = yield from handler(request)

if isinstance(r, web.StreamResponse):

return r

if isinstance(r, bytes):

resp = web.Response(body=r)

resp.content_type = "application/octet-stream"

return resp

if isinstance(r, str):

resp = web.Response(body=r.encode("utf-8"))

resp.content_type = "text/html;charset=utf-8"

return resp

if isinstance(r, dict):

...

有了这些基础设施,我们就可以专注地往 handlers模块不断添加 URL 处理函数了,可以极大地提高开发效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值