一次点击多个 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 对象。
我正在尝试制作一个 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 对象。