简介

在工作中,有的时候需要对 API 做一些转发。普通的转发,使用 Nginx 或者 Caddy 即可。但如果我们需要对转发数据做一些修改,比如在 API 中注入某些附加信息,就需要通过程序来实现了。本文就简单介绍使用 SanicAIOHTTP 来实现 Github API 的转发。

AIOHTTP 异步请求

首先,我们需要先通过 AIOHTTP 来请求 Github 数据,并做相应的解析,示例代码如下:

from aiohttp import ClientSession, TCPConnector

GITHUB_BASE_URL = 'https://api.github.com/'
GITHUB_HEADER = 'application/vnd.github.v3+json'


async def fetch_github(url, params, method='GET'):
    '''
    Fetch github data
    '''
    req_url = GITHUB_BASE_URL + url
    
    # Need disable ssl verify
    session = ClientSession(
        headers={'Accept': GITHUB_HEADER},
        connector=TCPConnector(verify_ssl=False))

    async with session:
        async with session.get(req_url, params=params) as r:
            if r.status != 200:
                return None
            resp = await r.json()
            return resp

这里,我们使用了 AIOHTTP 的 ClientSession 进行 GET 请求。由于是 https 请求,所以需要设置 verify_ssl=False

Sanic 转发

在使用 Sanic 进行转发的时候,我们需要匹配全部的 Github URL。如果每一个 URL 都写一遍,工作量太大。好在 Sanic 提供了path 这样一个任意 url 匹配方法,可以大幅度节省我们的工作量。详情可以查看这个 issue,示例代码如下:

from sanic import Sanic
from sanic.response import json
from sanic.exceptions import abort

app = Sanic(__name__)


@app.route("/")
async def index(request):
    '''
    Index url
    '''
    return json({
        'status': 0,
        'data': 'forward github api',
        'forward': 'Sanic & aiohttp',
    })


@app.route('/favicon.ico')
async def favicon(request):
    '''
    Disable /favicon.ico for chrome
    '''
    abort(404)


@app.route('/<path:[^/].*?>')
async def forward(request, path):
    '''
    Match any url
    '''
    params = request.raw_args if request.raw_args else {}
    github_data = await fetch_github(path, params)
    if github_data is None:
        return json({
            'status': 1,
            'error': 'forward error!',
            'forward': 'Sanic & aiohttp',
        })
    return json({
        'status': 0,
        'data': github_data,
        'forward': 'Sanic & aiohttp',
    })

这样,就完成了 Sanic 对于 aiohttp 请求数据的转发。

定义 eventloop

由于 Sanic 在默认运行的时候,采用的是自己的 eventloop,这样会和 AIOHTTP 使用的 eventloop 产生冲突,所以我们需要使用统一的 eventloop 来同时支持 Sanic 和 AIOHTTP。代码如下:

loop = asyncio.get_event_loop()
server = app.create_server(host='0.0.0.0', port=8000, debug=True)
asyncio.ensure_future(server)
loop.run_forever()

完整代码

最终的完整版代码如下所示:

import asyncio
from sanic import Sanic
from sanic.response import json
from sanic.exceptions import abort
from aiohttp import ClientSession, TCPConnector

GITHUB_BASE_URL = 'https://api.github.com/'
GITHUB_HEADER = 'application/vnd.github.v3+json'


async def fetch_github(url, params, method='GET'):
    '''
    Fetch github data
    '''
    req_url = GITHUB_BASE_URL + url

    # Need disable ssl verify
    session = ClientSession(
        headers={'Accept': GITHUB_HEADER},
        connector=TCPConnector(verify_ssl=False))

    async with session:
        async with session.get(req_url, params=params) as r:
            if r.status != 200:
                return None
            resp = await r.json()
            return resp


app = Sanic(__name__)


@app.route("/")
async def index(request):
    '''
    Index url
    '''
    return json({
        'status': 0,
        'data': 'forward github api',
        'forward': 'Sanic & aiohttp',
    })


@app.route('/favicon.ico')
async def favicon(request):
    '''
    Disable /favicon.ico for chrome
    '''
    abort(404)


@app.route('/<path:[^/].*?>')
async def forward(request, path):
    '''
    Match any url
    '''
    params = request.raw_args if request.raw_args else {}
    github_data = await fetch_github(path, params)
    if github_data is None:
        return json({
            'status': 1,
            'error': 'forward error!',
            'forward': 'Sanic & aiohttp',
        })
    return json({
        'status': 0,
        'data': github_data,
        'forward': 'Sanic & aiohttp',
    })


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    server = app.create_server(host='0.0.0.0', port=8000, debug=True)
    asyncio.ensure_future(server)
    loop.run_forever()

可以看出,使用 Sanic 和 AIOHTTP 来做转发,非常简单。而且采用协程做并发,转发效率也比较高,是一种值得参考的 API 转发方法。

参考文献

  1. support regex routes in routing