0
点赞
收藏
分享

微信扫一扫

aiohttp进阶教程



在python后台——asyncio,aiohttp入门教程,多进程+asyncio文章中,我们介绍了asyncio,aiohttp的入门知识,现在我们这里详细介绍一下aiohttp

参考文档:https://www.bookstack.cn/read/aiohttp-chinese-documentation/aiohttp%E6%96%87%E6%A1%A3-ServerTutorial.md

aiohttp.web建立在这几个概念之上: 应用(application),路由(router),请求(request)和响应(response)。

底层服务器

aiohttp是基于asyncio来实现的。如果不是需要和其他的异步进程一块使用,我们一般用不到这么底层的实现方法。

import asyncio
from aiohttp import web


async def handler(request):
    return web.Response(text="OK")


async def main(loop):
    server = web.Server(handler)
    await loop.create_server(server, "127.0.0.1", 8080)
    print("======= Serving on http://127.0.0.1:8080/ ======")

    # pause here for very long time by serving HTTP requests and
    # waiting for keyboard interruption
    await asyncio.sleep(100*3600)


loop = asyncio.get_event_loop()

try:
    loop.run_until_complete(main(loop))
except KeyboardInterrupt:
    pass
loop.close()

创建应用程序

from aiohttp import web
app = web.Application()
web.run_app(app, host='127.0.0.1', port=8080)

添加路由

from views import index
def setup_routes(app):
    app.router.add_get('/', index)


from aiohttp import web
from routes import setup_routes
app = web.Application()
setup_routes(app)
web.run_app(app, host='127.0.0.1', port=8080)

可变性路由

使用Request.match_info来读取路由中变量

async def variable_handler(request):
    return web.Response(
        text="Hello, {}".format(request.match_info.get('name', "Anonymous")))
resource = app.router.add_resource('/{name}')
resource.add_route('GET', variable_handler)

默认情况下,每个可变部分所使用的正则表达式是[^{}/]+

当然你也可以自定义正则表达式{标识符: 正则}:

resource = app.router.add_resource(r'/{name:\d+}')

Flask风格路由修饰器

routes = web.RouteTableDef()
@routes.get('/get')
async def handle_get(request):
    ...
@routes.post('/post')
async def handle_post(request):
    ...
app.router.add_routes(routes)

资源视图

所有在路由中注册的资源都可使用UrlDispatcher.resources()查看:

for resource in app.router.resources():
    print(resource)

同样,有name的资源可以用UrlDispatcher.named_resources()来查看:

for name, resource in app.router.named_resources().items():
    print(name, resource)

0.21版本后请使用UrlDispatcher.named_routes() / UrlDispatcher.routes() 来代替 UrlDispatcher.named_resources() / UrlDispatcher.resources() 。

抽象路由类

路由(router)是一个可插拔的部分: 用户可以从头开始创建一个新的路由库,不过其他的部分必须与这个新路由无缝契合才行。
AbstractRouter只有一个必须(覆盖)的方法: AbstractRouter.resolve()协程方法。同时必须返回AbstractMatchInfo实例对象。
如果所请求的URL的处理器发现AbstractMatchInfo.handler()所返回的是web处理器,那AbstractMatchInfo.http_exception则为None。
否则的话AbstractMatchInfo.http_exception会是一个HTTPException实例,如404: NotFound 或 405: Method Not Allowed。如果调用AbstractMatchInfo.handler()则会抛出http_exception。

class aiohttp.abc.AbstractRouter
   此类是一个抽象路由类,可以指定aiohttp.web.Application中的router参数来传入此类的实例,使用aiohttp.web.Application.router来查看返回.
   coroutine resolve(request)
      URL处理方法。该方法是一个抽象方法,需要被继承的类所覆盖。
      参数:
         request - 用于处理请求的aiohttp.web.Request实例。在处理时aiohttp.web.Request.match_info会被设置为None。
      返回:
         返回AbstractMathcInfo实例对象。

class aiohttp.abc.AbstractMatchInfo
   抽象的匹配信息,由AbstractRouter.resolve()返回。
   http_exception
      如果没有匹配到信息则是aiohttp.web.HTTPException否则是None。
   coroutine handler(request)
      执行web处理器程序的抽象方法。
      参数:
         request - 用于处理请求的aiohttp.web.Request实例。在处理时aiohttp.web.Request.match_info会被设置为None。
      返回:
         返回aiohttp.web.StreamResponse或其派生类。
      可能会抛出的异常:
         所抛出的异常类型是aiohttp.web.HTTPException。
   coroutine expect_handler(request)
      用户处理100-continue的抽象方法。

创建视图

from aiohttp import web
async def index(request):
    return web.Response(text='Hello Aiohttp!')

视图函数可以放在类中,这些没有什么限制

class Handler:
    def __init__(self):
        pass
    def handle_intro(self, request):
        return web.Response(text="Hello, world")
    async def handle_greeting(self, request):
        name = request.match_info.get('name', "Anonymous")
        txt = "Hello, {}".format(name)
        return web.Response(text=txt)
handler = Handler()
app.router.add_get('/intro', handler.handle_intro)
app.router.add_get('/greet/{name}', handler.handle_greeting)

django风格的基础试图类。

class MyView(web.View):
    async def get(self):
        return await get_resp(self.request)
    async def post(self):
        return await post_resp(self.request)

处理器应是一个协程方法,且只接受self参数,并且返回标准web处理器的响应对象。请求对象可使用View.request中取出。

app.router.add_route('*', '/path/to', MyView)

抽象类基础视图

aiohttp提供抽象类 AbstractView来对基于类的视图进行支持,而且还是一个awaitable对象(可以应用在await Cls()或yield from Cls()中,同时还有一个request属性。)

class aiohttp.AbstarctView
    一个用于部署所有基于类的视图的基础类。
    __iter____await__方法需要被覆盖。
    request
      用于处理请求的aiohttp.web.Request实例。

请求(Request)和基础请求(BaseRequest)

Request 对象中包含所有的HTTP请求信息。
BaseRequest 用在底层服务器中(底层服务器没有应用,路由,信号和中间件)。Request对象拥有Request.app和Request.match_info属性。
BaseRequest和Reuqest都是类字典对象,以便在中间件和信号处理器中共享数据。

class aiohttp.web.BaseRequest

  • version
    发起请求的HTTP版本,该属性只读。
    返回aiohttp.protocol.HttpVersion实例对象。
  • method
    发起请求的HTTP方法,该属性只读。
    返回值为大写字符串,如”GET” ,”POST”,”PUT”。
  • url
    包含资源的绝对路径的URL实例对象。
    注意:如果是不合适的请求(如没有HOST HTTP头信息)则是不可用的。
  • rel_url
        包含资源的相对路径的URL实例对象。
        与.url.relative()相同。
  • scheme
      表示请求中的协议(scheme)部分。
           如果处理方式是SSL则为”https”,否则是”http”。
           该属性的值可能会被 clone()方法覆盖。
           该属性只读,类型为str。
           2.3版本时更改内容: Forwarded 和 X-Forwarded-Proto不在被使用。
           调用 .clone(scheme=new_scheme)来设置一个新的值。

参考:https://www.bookstack.cn/read/aiohttp-chinese-documentation/aiohttp%E6%96%87%E6%A1%A3-ServerReference.md

响应类

返回JSON 响应

def handler(request):
    data = {'some': 'data'}
    return web.json_response(data)

这个方法返回的是aiohttp.web.Response实例对象,所以你可以做些其他的事,比如设置cookies。

使用配置文件

# load config from yaml file in current dir
conf = load_config(str(pathlib.Path('.') / 'config' / 'polls.yaml'))
app['config'] = conf

# 使用变量
conf = app['config']
name=conf['name'],

数据库架构

import sqlalchemy as sa 
meta = sa.MetaData()
question - sq.Table(
    'question', meta,
    sa.Column('id', sa.Integer, nullable=False),
    sa.Column('question_text', sa.String(200), nullable=False),
    sa.Column('pub_date', sa.Date, nullable=False),
    # Indexes #
    sa.PrimaryKeyConstraint('id', name='question_id_pkey')
)
choice = sa.Table(
    'choice', meta,
    sa.Column('id', sa.Integer, nullable=False),
    sa.Column('question_id', sa.Integer, nullable=False),
    sa.Column('choice_text', sa.String(200), nullable=False),
    sa.Column('votes', server_default="0", nullable=False),
    # Indexes #
    sa.PrimayKeyConstraint('id', name='choice_id_pkey'),
    sa.ForeignKeyContraint(['question_id'], [question.c.id],
                            name='choice_question_id_fkey',
                            ondelete='CASCADE'),
)

创建连接引擎

async def init_pg(app):
    conf = app['config']
    engine = await aiopg.sa.create_engine(
        database=conf['database'],
        user=conf['user'],
        password=conf['password'],
        host=conf['host'],
        port=conf['host'],
        minsize=conf['minsize'],
        maxsize=conf['maxsize'])
    app['db'] = engine

最好将连接数据库的函数放在on_startup信号中:

app.on_startup.append(init_pg)

关闭数据库

程序退出时一块关闭所有的资源接口是一个很好的做法。
使用on_cleanup信号来关闭数据库接口:

async def close_pg(app):
    app['db'].close()
    await app['db'].wait_closed()
app.on_cleanup.append(close_pg)

使用模板

先安装

$ pip install aiohttp_jinja2

配置模板文件夹

我们将其放在polls/aiohttpdemo_polls/templates文件夹中。

import aiohttp_jinja2
import jinja2
aiohttp_jinja2.setup(
    app, loader=jinja2.PackageLoader('aiohttpdemo_polls', 'templates'))

或者
app = web.Application()
aiohttp_jinja2.setup(app,
    loader=jinja2.FileSystemLoader('/path/to/templates/folder'))

视图函数

@aiohttp_jinja2.template('detail.html')
async def poll(request):
    async with request['db'].acquire() as conn:
        question_id = request.match_info['question_id']
        try:
            question, choices = await db.get_question(conn,
                                                      question_id)
        except db.RecordNotFound as e:
            raise web.HTTPNotFound(text=str(e))
        return {
            'question': question,
            'choices': choices
        }

静态文件

app.router.add_static('/prefix/',
                      path=str(project_root / 'static'),
                      name='static')

当访问静态文件的目录时,默认服务器会返回 HTTP/403 Forbidden(禁止访问)。 使用show_index并将其设置为True可以显示出索引:

app.router.add_static('/prefix', path_to_static_folder, show_index=True)

当从静态文件目录访问一个符号链接(软链接)时,默认服务器会响应 HTTP/404 Not Found(未找到)。使用follow_symlinks并将其设置为True可以让服务器使用符号链接:

app.router.add_static('/prefix', path_to_static_folder, follow_symlinks=True)

如果你想允许缓存清除,使用append_version并设为True。

缓存清除会对资源文件像JavaScript 和 CSS文件等的文件名上添加一个hash后的版本。这样的好处是我们可以让浏览器无限期缓存这些文件而不用担心这些文件是否发布了新版本。

app.router.add_static('/prefix', path_to_static_folder, append_version=True)

使用中间件

中间件是每个web处理器必不可少的组件。它的作用是在处理器处理请求前预处理请求以及在得到响应后发送出去。相当于kong网关的插件。

我们下面来实现一个用于显示漂亮的404和500页面的简单中间件。

def setup_middlewares(app):
    error_middleware = error_pages({404: handle_404,
                                    500: handle_500})
    app.middlewares.append(error_middleware)

中间件(middleware)本身是一个接受应用程序(application)和后续处理器(next handler)的加工厂。

中间件工厂返回一个与web处理器一样接受请求并返回响应的中间件处理器。

def error_pages(overrides):
    async def middleware(app, handler):
        async def middleware_handler(request):
            try:
                response = await handler(request)
                override = overrides.get(response.status)
                if override is None:
                    return response
                else:
                    return await override(request, response)
            except web.HTTPException as ex:
                override = overrides.get(ex.status)
                if override is None:
                    raise
                else:
                    return await override(request, ex)
        return middleware_handler
    return middleware

这些overrides(handle_404和handle_500)只是简单的用Jinja2模板渲染:

async def handle_404(request, response):
    response = aiohttp_jinja2.render_template('404.html',
                                              request,
                                              {})
    return response
async def handle_500(request, response):
    response = aiohttp_jinja2.render_template('500.html',
                                              request,
                                              {})
    return response

以下代码演示了中间件的执行顺序:

from aiohttp import web

async def test(request):
    print('Handler function called')
    return web.Response(text="Hello")

@web.middleware
async def middleware1(request, handler):
    print('Middleware 1 called')
    response = await handler(request)
    print('Middleware 1 finished')
    return response

@web.middleware
async def middleware2(request, handler):
    print('Middleware 2 called')
    response = await handler(request)
    print('Middleware 2 finished')
    return response


app = web.Application(middlewares=[middleware1, middleware2])
app.router.add_get('/', test)
web.run_app(app)

输出内容为

Middleware 1 called
Middleware 2 called
Handler function called
Middleware 2 finished
Middleware 1 finished


举报

相关推荐

0 条评论