一次点击多个 API,tornado 和 python

Hitting multiple APIs at once, tornado and python

我正在尝试制作一个 API,它将收集其他几个 API 的回复,并将结果合并为一个回复。我想异步发送 get 请求,以便它运行得更快,但即使我使用协同程序并产生,我的代码似乎仍然一次发出一个请求。想知道是不是因为我使用的是请求库而不是 tornado 的 AsyncHTTPClient,或者是因为我在循环内调用 self.path_get,还是因为我将结果存储在实例变量中?

API 我正在访问 return 个 JSON 对象数组,我想将它们全部组合成一个数组并将其写入响应。

from tornado import gen, ioloop, web
from tornado.gen import Return
import requests


PATHS = [
    "http://firsturl",
    "http://secondurl",
    "http://thirdurl"
]


class MyApi(web.RequestHandler):

    @gen.coroutine
    def get(self):
        self.results = []
        for path in PATHS:
            x = yield self.path_get(path)

        self.write({
            "results": self.results,
        })

    @gen.coroutine
    def path_get(self, path):
        resp = yield requests.get(path)
        self.results += resp.json()["results"]
        raise Return(resp)


ROUTES = [
    (r"/search", MyApi),
]


def run():
    app = web.Application(
        ROUTES,
        debug=True,
    )

    app.listen(8000)

    ioloop.IOLoop.current().start()


if __name__ == "__main__":
    run()

您的代码不起作用的原因有很多。首先,requests 通常会阻塞事件循环并且不让任何其他内容执行。将 requests 替换为 AsyncHTTPClient.fetch。此外,您产生每个请求的方式也会使请求顺序进行,而不是像您想象的那样同时进行。以下是如何重构您的代码的示例:

import json
from tornado import gen, httpclient, ioloop, web

# ...

class MyApi(web.RequestHandler):

    @gen.coroutine
    def get(self):
        futures_list = []
        for path in PATHS:
            futures_list.append(self.path_get(path))

        yield futures_list
        result = json.dumps({'results': [x.result() for x in futures_list]})
        self.write(result)

    @gen.coroutine
    def path_get(self, path):
        request = httpclient.AsyncHTTPClient()
        resp = yield request.fetch(path)
        result = json.loads(resp.body.decode('utf-8'))
        raise gen.Return(result)

我们正在创建一个 Futures 的列表,从 gen.coroutine 函数返回并生成整个列表,直到请求的结果可用。然后,一旦所有请求都完成,futures_list 将被迭代,结果用于创建一个新列表,该列表附加到 JSON 对象。