談到http接口調用,Requests大家並不陌生,例如,robotframework-requests、HttpRunner等HTTP接口測試庫/框架都是基於它開發。這里將介紹另一款http接口測試框架:httpx。
它的API和Requests高度一致。
github: https://github.com/encode/httpx
安裝:
> pip install httpx
httpx 簡單使用
當然,它是不支持python2.x的。
- 簡單的get調用
import httpx
r = httpx.get("http://httpbin.org/get")
print(r.status_code)
print(r.json())
執行結果:
200
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-httpx/0.12.1', 'X-Amzn-Trace-Id': 'Root=1-5ea5b58c-e446c44392ea090809e8a4bc'}, 'origin': '113.97.33.224', 'url': 'http://httpbin.org/get'}
- 帶參數的post調用
import httpx
payload = {'key1': 'value1', 'key2': 'value2'}
r = httpx.post("http://httpbin.org/post", data=payload)
print(r.json())
執行結果:
{'args': {}, 'data': '', 'files': {}, 'form': {'key1': 'value1', 'key2': 'value2'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '23', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'python-httpx/0.12.1', 'X-Amzn-Trace-Id': 'Root=1-5ea5b61d-1871d10e80b8324e48ea475e'}, 'json': None, 'origin': '113.97.33.224', 'url': 'http://httpbin.org/post'}
你會發現這幾乎和requests一模一樣,只不過把requests 換成了httpx。
httpx 異步調用
接下來認識httpx的異步調用:
import httpx
import asyncio
async def main():
async with httpx.AsyncClient() as client:
resp = await client.get('http://httpbin.org/get')
result = resp.json()
print(result)
asyncio.run(main())
這里用到了async 、await, asyncio等,等參考我關於python異步I/O的基礎介紹:https://www.cnblogs.com/fnng/p/12757395.html
異步的調用的優勢
我們發現,采用異步會讓接口的調用更加復雜,那為什么還要使用異步呢?當你要調用1000次接口時,那么異步調用可以讓你的調用更快。接下來我們通過簡單讓例子進行對比。
以我flask開發的簡單接口為例子:
https://github.com/defnngj/learning-API-test
為了測試的更加准確性,我將flask服務部署在了另一台電腦,測試機與被測服務分離。
- httpx 同步調用
# 同步調用
import time
import httpx
def make_request(client):
resp = client.get('http://192.168.0.7:5000')
result = resp.json()
# print(result)
assert result["code"] == 10200
def main():
session = httpx.Client()
# 1000 次調用
for _ in range(1000):
make_request(session)
if __name__ == '__main__':
# 開始
start = time.time()
main()
# 結束
end = time.time()
print(f'同步:發送1000次請求,耗時:{end - start}')
結果:
...
同步:發送1000次請求,耗時:52.948561906814575
- httpx 異步調用
# 異步調用
import httpx
import asyncio
import time
async def request(client):
resp = await client.get('http://192.168.0.7:5000')
result = resp.json()
# print(result)
assert result["code"] == 10200
async def main():
async with httpx.AsyncClient() as client:
# # 開始
# start = time.time()
# 1000 次調用
task_list = []
for _ in range(1000):
req = request(client)
task = asyncio.create_task(req)
task_list.append(task)
await asyncio.gather(*task_list)
if __name__ == "__main__":
#開始
start = time.time()
asyncio.run(main())
# 結束
end = time.time()
print(f'異步:發送1000次請求,耗時:{end - start}')
結果:
...
異步:發送1000次請求,耗時:3.903275728225708
將httpx用於請求端,同步與異步請求差距非常明顯。
以上的例子已經放到 learning-API-test github項目
總結
* 這里只是拿 flask 非異步框架做為接口服務端進行對比,如果如果將接口服務同樣換作前面介紹的 snaic 異步框架,上面的兩組測試對比並不明顯(snaic的異步接口服務處理同步請求更快),在安裝 snaic的時候會發現,他同樣也集成了 httpx 庫。
* 為什么要學習異步,因為我們公司有很多接口是異步調用的,所以,我想真正搞懂這個概念,就這么簡單!保持在工作中對技術的好奇心。
- 異步與多線程的區別?這是我在學習 異步時候的一個疑問,我找到了一個比較形象的例子。
以火車站購票場景為例:一個火車站為一個進程,一個窗口和售票員的組合為一個線程:
- 多線程:火車站開了N個窗口售票員,我們去買票,會有工作人員(CPU)指定我們去某個窗口買票,你被安排到某個窗口后,告訴售票員你的請求(咨詢或買票),售票員執行操作,如果這個過程中發送的阻塞,也是窗口售票員的阻塞(比如查票的過程),但是因為你開了很多個窗口,其他買票的人可以被安排去另外的空閑窗口,如果所有窗口都滿了,工作人員就不會給你安排了,直到有空的窗口出來;
- 多進程(並行):建多個火車站售票,火車站與火車站間互不影響,看買票的自己想去哪里(這里不討論負載均衡);
- 異步:火車站只有一個窗口售票員,但是窗口前有一個登記台(事件循環),你把你想買的票告訴給登記台,並留下你的手機(回調函數),然后你就可以走了,由於登記台只是登記了你的請求,並沒有做任何其他操作,所以這個耗時基本忽略不計的。之后售票員處理完了上一個任務了,就會自己去登記台取剩下的未完成的任務,直到取到你的任務,操作完后,有票沒票都會通過手機通知你,如果有票還會往你的手機發車票的二維碼;